Skip to content

Commit 00728be

Browse files
feat(filter): add the possibility to filter on a smart field (#410)
1 parent 2fc7308 commit 00728be

File tree

14 files changed

+478
-303
lines changed

14 files changed

+478
-303
lines changed

app/helpers/forest_liana/schema_helper.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,11 @@ def self.find_collection_from_model(active_record_class)
44
collection_name = ForestLiana.name_for(active_record_class)
55
ForestLiana.apimap.find { |collection| collection.name.to_s == collection_name }
66
end
7+
8+
def self.is_smart_field?(model, field_name)
9+
collection = self.find_collection_from_model(model)
10+
field_found = collection.fields.find { |collection_field| collection_field[:field].to_s == field_name } if collection
11+
field_found && field_found[:is_virtual]
12+
end
713
end
814
end

app/services/forest_liana/filters_parser.rb

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -47,38 +47,64 @@ def parse_aggregation(node)
4747
end
4848

4949
def parse_condition(condition)
50-
ensure_valid_condition(condition)
50+
where = parse_condition_without_smart_field(condition)
5151

52-
operator = condition['operator']
53-
value = condition['value']
54-
field = condition['field']
52+
field_name = condition['field']
5553

56-
if @operator_date_parser.is_date_operator?(operator)
57-
condition = @operator_date_parser.get_date_filter(operator, value)
58-
return "#{parse_field_name(field)} #{condition}"
54+
if ForestLiana::SchemaHelper.is_smart_field?(@resource, field_name)
55+
schema = ForestLiana.schema_for_resource(@resource)
56+
field_schema = schema.fields.find do |field|
57+
field[:field].to_s == field_name
58+
end
59+
60+
unless field_schema.try(:[], :filter)
61+
raise ForestLiana::Errors::NotImplementedMethodError.new("method filter on smart field '#{field_name}' not found")
62+
end
63+
64+
return field_schema[:filter].call(condition, where)
5965
end
6066

61-
if is_belongs_to(field)
62-
association = field.partition(':').first.to_sym
63-
association_field = field.partition(':').last
67+
where
68+
end
69+
70+
def get_association_field_and_resource(field_name)
71+
if is_belongs_to(field_name)
72+
association = field_name.partition(':').first.to_sym
73+
association_field = field_name.partition(':').last
6474

6575
unless @resource.reflect_on_association(association)
6676
raise ForestLiana::Errors::HTTP422Error.new("Association '#{association}' not found")
6777
end
6878

6979
current_resource = @resource.reflect_on_association(association).klass
80+
81+
return association_field, current_resource
7082
else
71-
association_field = field
72-
current_resource = @resource
83+
return field_name, @resource
7384
end
85+
end
86+
87+
def parse_condition_without_smart_field(condition)
88+
ensure_valid_condition(condition)
89+
90+
operator = condition['operator']
91+
value = condition['value']
92+
field_name = condition['field']
93+
94+
if @operator_date_parser.is_date_operator?(operator)
95+
condition = @operator_date_parser.get_date_filter(operator, value)
96+
return "#{parse_field_name(field_name)} #{condition}"
97+
end
98+
99+
association_field, current_resource = get_association_field_and_resource(field_name)
74100

75101
# NOTICE: Set the integer value instead of a string if "enum" type
76102
# NOTICE: Rails 3 do not have a defined_enums method
77103
if current_resource.respond_to?(:defined_enums) && current_resource.defined_enums.has_key?(association_field)
78104
value = current_resource.defined_enums[association_field][value]
79105
end
80106

81-
parsed_field = parse_field_name(field)
107+
parsed_field = parse_field_name(field_name)
82108
parsed_operator = parse_operator(operator)
83109
parsed_value = parse_value(operator, value)
84110
field_and_operator = "#{parsed_field} #{parsed_operator}"
@@ -149,16 +175,16 @@ def parse_field_name(field)
149175

150176
association = get_association_name_for_condition(field)
151177
quoted_table_name = ActiveRecord::Base.connection.quote_column_name(association)
152-
quoted_field_name = ActiveRecord::Base.connection.quote_column_name(field.split(':')[1])
178+
field_name = field.split(':')[1]
153179
else
154180
quoted_table_name = @resource.quoted_table_name
155-
quoted_field_name = ActiveRecord::Base.connection.quote_column_name(field)
156181
current_resource = @resource
182+
field_name = field
157183
end
184+
quoted_field_name = ActiveRecord::Base.connection.quote_column_name(field_name)
158185

159186
column_found = current_resource.columns.find { |column| column.name == field.split(':').last }
160-
161-
if column_found.nil?
187+
if column_found.nil? && !ForestLiana::SchemaHelper.is_smart_field?(current_resource, field_name)
162188
raise ForestLiana::Errors::HTTP422Error.new("Field '#{field}' not found")
163189
end
164190

config/initializers/errors.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ def initialize(message = "Unprocessable Entity")
4646
end
4747
end
4848

49+
class NotImplementedMethodError < ExpectedError
50+
def initialize(message = "Method not implemented")
51+
super(501, :internal_server_error, message, 'MethodNotImplementedError')
52+
end
53+
end
54+
4955
class InconsistentSecretAndRenderingError < ExpectedError
5056
def initialize(message=ForestLiana::MESSAGES[:SERVER_TRANSACTION][:SECRET_AND_RENDERINGID_INCONSISTENT])
5157
super(500, :internal_server_error, message, 'InconsistentSecretAndRenderingError')

spec/dummy/app/models/island.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ class Island < ActiveRecord::Base
22
self.table_name = 'isle'
33

44
has_many :trees
5+
has_one :location
56
end

spec/dummy/app/models/location.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class Location < ActiveRecord::Base
2+
belongs_to :island
3+
end

spec/dummy/app/models/reference.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class Reference < ActiveRecord::Base
2+
end
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
class CreateReferences < ActiveRecord::Migration[6.0]
2+
def change
3+
create_table :references do |t|
4+
5+
t.timestamps
6+
end
7+
end
8+
end
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
class CreateLocations < ActiveRecord::Migration[6.0]
2+
def change
3+
create_table :locations do |t|
4+
t.string :coordinates
5+
t.references :island, index: true
6+
7+
t.timestamps
8+
end
9+
end
10+
end

spec/dummy/db/schema.rb

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
#
1111
# It's strongly recommended that you check this file into your version control system.
1212

13-
ActiveRecord::Schema.define(version: 2019_07_16_135241) do
13+
ActiveRecord::Schema.define(version: 2021_03_26_140855) do
1414

1515
create_table "isle", force: :cascade do |t|
1616
t.string "name"
@@ -19,6 +19,19 @@
1919
t.datetime "updated_at"
2020
end
2121

22+
create_table "locations", force: :cascade do |t|
23+
t.string "coordinates"
24+
t.integer "island_id"
25+
t.datetime "created_at", precision: 6, null: false
26+
t.datetime "updated_at", precision: 6, null: false
27+
t.index ["island_id"], name: "index_locations_on_island_id"
28+
end
29+
30+
create_table "references", force: :cascade do |t|
31+
t.datetime "created_at", precision: 6, null: false
32+
t.datetime "updated_at", precision: 6, null: false
33+
end
34+
2235
create_table "trees", force: :cascade do |t|
2336
t.string "name"
2437
t.integer "owner_id"
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
class Forest::Location
2+
include ForestLiana::Collection
3+
4+
collection :Location
5+
6+
field :alter_coordinates, type: 'String' do
7+
object.name + 'XYZ'
8+
end
9+
10+
end

0 commit comments

Comments
 (0)