forest_liana 9.11.3 → 9.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/controllers/forest_liana/router.rb +2 -0
- data/app/serializers/forest_liana/serializer_factory.rb +27 -4
- data/app/services/forest_liana/aggregation_helper.rb +42 -0
- data/app/services/forest_liana/base_getter.rb +6 -3
- data/app/services/forest_liana/has_many_getter.rb +4 -3
- data/app/services/forest_liana/leaderboard_stat_getter.rb +7 -15
- data/app/services/forest_liana/pie_stat_getter.rb +12 -27
- data/app/services/forest_liana/resources_getter.rb +178 -19
- data/app/services/forest_liana/schema_adapter.rb +16 -2
- data/app/services/forest_liana/schema_utils.rb +22 -0
- data/app/services/forest_liana/search_query_builder.rb +1 -1
- data/lib/forest_liana/active_record_override.rb +65 -0
- data/lib/forest_liana/engine.rb +7 -0
- data/lib/forest_liana/schema_file_updater.rb +1 -1
- data/lib/forest_liana/version.rb +1 -1
- data/spec/dummy/app/models/tree.rb +3 -1
- data/spec/helpers/forest_liana/query_helper_spec.rb +9 -4
- data/spec/requests/resources_spec.rb +30 -4
- data/spec/services/forest_liana/pie_stat_getter_spec.rb +182 -0
- data/spec/services/forest_liana/resources_getter_spec.rb +118 -23
- data/spec/services/forest_liana/schema_adapter_spec.rb +12 -1
- data/spec/services/forest_liana/serializer_factory_spec.rb +53 -0
- metadata +6 -2
@@ -121,7 +121,7 @@ module ForestLiana
|
|
121
121
|
|
122
122
|
# NOTICE: Sort keys
|
123
123
|
@collections = @collections.map do |collection|
|
124
|
-
collection['fields'].sort do |field1, field2|
|
124
|
+
collection['fields'].sort! do |field1, field2|
|
125
125
|
[field1['field'], field1['type'].inspect] <=> [field2['field'], field2['type'].inspect]
|
126
126
|
end
|
127
127
|
|
data/lib/forest_liana/version.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
class Tree < ActiveRecord::Base
|
2
2
|
belongs_to :owner, class_name: 'User', inverse_of: :trees_owned
|
3
3
|
belongs_to :cutter, class_name: 'User', inverse_of: :trees_cut
|
4
|
-
belongs_to :island
|
4
|
+
belongs_to :island, optional: true
|
5
5
|
belongs_to :eponymous_island,
|
6
6
|
->(record) { where(name: record.name) },
|
7
7
|
class_name: 'Island',
|
8
8
|
inverse_of: :eponymous_tree,
|
9
9
|
optional: true
|
10
|
+
|
11
|
+
has_one :location, through: :island
|
10
12
|
end
|
@@ -37,6 +37,7 @@ module ForestLiana
|
|
37
37
|
{ name: :cutter, klass: User },
|
38
38
|
{ name: :island, klass: Island },
|
39
39
|
{ name: :eponymous_island, klass: Island },
|
40
|
+
{ name: :location, klass: Location },
|
40
41
|
]
|
41
42
|
end
|
42
43
|
|
@@ -52,13 +53,17 @@ module ForestLiana
|
|
52
53
|
|
53
54
|
describe 'get_one_association_names_symbol' do
|
54
55
|
it 'should return the one-one associations names as symbols' do
|
55
|
-
expect(QueryHelper.get_one_association_names_symbol(Tree)).to eq(
|
56
|
+
expect(QueryHelper.get_one_association_names_symbol(Tree)).to eq(
|
57
|
+
[:owner, :cutter, :island, :eponymous_island, :location]
|
58
|
+
)
|
56
59
|
end
|
57
60
|
end
|
58
61
|
|
59
62
|
describe 'get_one_association_names_string' do
|
60
63
|
it 'should return the one-one associations names as strings' do
|
61
|
-
expect(QueryHelper.get_one_association_names_string(Tree)).to eq(
|
64
|
+
expect(QueryHelper.get_one_association_names_string(Tree)).to eq(
|
65
|
+
['owner', 'cutter', 'island', 'eponymous_island', 'location']
|
66
|
+
)
|
62
67
|
end
|
63
68
|
end
|
64
69
|
|
@@ -71,12 +76,12 @@ module ForestLiana
|
|
71
76
|
end
|
72
77
|
end
|
73
78
|
|
74
|
-
context 'on a model having
|
79
|
+
context 'on a model having 3 belongsTo/hasOne associations' do
|
75
80
|
tables_associated_to_relations_name =
|
76
81
|
QueryHelper.get_tables_associated_to_relations_name(Tree)
|
77
82
|
|
78
83
|
it 'should return the one-one associations' do
|
79
|
-
expect(tables_associated_to_relations_name.keys.length).to eq(
|
84
|
+
expect(tables_associated_to_relations_name.keys.length).to eq(3)
|
80
85
|
end
|
81
86
|
|
82
87
|
it 'should return relationships having a name different than the targeted model' do
|
@@ -4,7 +4,9 @@ describe 'Requesting Tree resources', :type => :request do
|
|
4
4
|
let(:scope_filters) { {'scopes' => {}, 'team' => {'id' => '1', 'name' => 'Operations'}} }
|
5
5
|
before do
|
6
6
|
user = User.create(name: 'Michel')
|
7
|
-
Tree.create(name: 'Lemon Tree', owner: user, cutter: user)
|
7
|
+
tree = Tree.create(name: 'Lemon Tree', owner: user, cutter: user)
|
8
|
+
island = Island.create(name: 'Lemon Island', trees: [tree])
|
9
|
+
Location.create(coordinates: '1,2', island: island)
|
8
10
|
|
9
11
|
Rails.cache.write('forest.users', {'1' => { 'id' => 1, 'roleId' => 1, 'rendering_id' => '1' }})
|
10
12
|
Rails.cache.write('forest.has_permission', true)
|
@@ -32,6 +34,8 @@ describe 'Requesting Tree resources', :type => :request do
|
|
32
34
|
after do
|
33
35
|
User.destroy_all
|
34
36
|
Tree.destroy_all
|
37
|
+
Island.destroy_all
|
38
|
+
Location.destroy_all
|
35
39
|
end
|
36
40
|
|
37
41
|
token = JWT.encode({
|
@@ -54,7 +58,7 @@ describe 'Requesting Tree resources', :type => :request do
|
|
54
58
|
describe 'index' do
|
55
59
|
describe 'without any filter' do
|
56
60
|
params = {
|
57
|
-
fields: { 'Tree' => 'id,name' },
|
61
|
+
fields: { 'Tree' => 'id,name,location' },
|
58
62
|
page: { 'number' => '1', 'size' => '10' },
|
59
63
|
searchExtended: '0',
|
60
64
|
sort: '-id',
|
@@ -98,7 +102,8 @@ describe 'Requesting Tree resources', :type => :request do
|
|
98
102
|
|
99
103
|
it 'should respond the tree data' do
|
100
104
|
get '/forest/Tree', params: params, headers: headers
|
101
|
-
|
105
|
+
|
106
|
+
expect(JSON.parse(response.body)).to include({
|
102
107
|
"data" => [{
|
103
108
|
"type" => "Tree",
|
104
109
|
"id" => "1",
|
@@ -108,9 +113,30 @@ describe 'Requesting Tree resources', :type => :request do
|
|
108
113
|
},
|
109
114
|
"links" => {
|
110
115
|
"self" => "/forest/tree/1"
|
116
|
+
},
|
117
|
+
"relationships" => {
|
118
|
+
"location" => {
|
119
|
+
"data" => { "id" => "1", "type" => "Location" },
|
120
|
+
"links" => { "related" => {} }
|
121
|
+
}
|
111
122
|
}
|
112
123
|
}],
|
113
|
-
"included" => [
|
124
|
+
"included" => [{
|
125
|
+
"type" => "Location",
|
126
|
+
"id" => "1",
|
127
|
+
"attributes" => include(
|
128
|
+
"id" => 1,
|
129
|
+
"created_at" => nil,
|
130
|
+
"updated_at" => nil,
|
131
|
+
"coordinates" => nil
|
132
|
+
),
|
133
|
+
"links" => { "self" => "/forest/location/1" },
|
134
|
+
"relationships" => {
|
135
|
+
"island" => {
|
136
|
+
"links" => { "related" => {} }
|
137
|
+
}
|
138
|
+
}
|
139
|
+
}]
|
114
140
|
})
|
115
141
|
end
|
116
142
|
end
|
@@ -133,5 +133,187 @@ module ForestLiana
|
|
133
133
|
end
|
134
134
|
end
|
135
135
|
end
|
136
|
+
|
137
|
+
describe 'aggregation methods behavior' do
|
138
|
+
let(:scopes) { {'scopes' => {}, 'team' => {'id' => '1', 'name' => 'Operations'}} }
|
139
|
+
let(:model) { Tree }
|
140
|
+
let(:collection) { 'trees' }
|
141
|
+
let(:groupByFieldName) { 'age' }
|
142
|
+
|
143
|
+
describe 'aggregation_sql method' do
|
144
|
+
subject { PieStatGetter.new(model, params, user) }
|
145
|
+
|
146
|
+
context 'with COUNT aggregator' do
|
147
|
+
let(:params) {
|
148
|
+
{
|
149
|
+
type: 'Pie',
|
150
|
+
sourceCollectionName: collection,
|
151
|
+
timezone: 'Europe/Paris',
|
152
|
+
aggregator: 'Count',
|
153
|
+
groupByFieldName: groupByFieldName
|
154
|
+
}
|
155
|
+
}
|
156
|
+
|
157
|
+
it 'should generate correct COUNT SQL' do
|
158
|
+
sql = subject.send(:aggregation_sql, 'count', nil)
|
159
|
+
expect(sql).to eq 'COUNT(DISTINCT trees.id)'
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'should generate correct COUNT SQL with specific field' do
|
163
|
+
sql = subject.send(:aggregation_sql, 'count', 'age')
|
164
|
+
expect(sql).to eq 'COUNT(DISTINCT trees.age)'
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
context 'with SUM aggregator' do
|
169
|
+
let(:params) {
|
170
|
+
{
|
171
|
+
type: 'Pie',
|
172
|
+
sourceCollectionName: collection,
|
173
|
+
timezone: 'Europe/Paris',
|
174
|
+
aggregator: 'Sum',
|
175
|
+
aggregateFieldName: 'age',
|
176
|
+
groupByFieldName: groupByFieldName
|
177
|
+
}
|
178
|
+
}
|
179
|
+
|
180
|
+
it 'should generate correct SUM SQL' do
|
181
|
+
sql = subject.send(:aggregation_sql, 'sum', 'age')
|
182
|
+
expect(sql).to eq 'SUM(trees.age)'
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
context 'with association field' do
|
187
|
+
let(:params) {
|
188
|
+
{
|
189
|
+
type: 'Pie',
|
190
|
+
sourceCollectionName: collection,
|
191
|
+
timezone: 'Europe/Paris',
|
192
|
+
aggregator: 'Count',
|
193
|
+
groupByFieldName: 'owner:name'
|
194
|
+
}
|
195
|
+
}
|
196
|
+
|
197
|
+
it 'should handle association fields correctly' do
|
198
|
+
# Assuming Tree belongs_to :owner
|
199
|
+
allow(model).to receive(:reflect_on_association).with(:owner).and_return(
|
200
|
+
double(table_name: 'owners')
|
201
|
+
)
|
202
|
+
|
203
|
+
sql = subject.send(:aggregation_sql, 'count', 'owner:id')
|
204
|
+
expect(sql).to eq 'COUNT(DISTINCT owners.id)'
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
context 'with unsupported aggregator' do
|
209
|
+
let(:params) {
|
210
|
+
{
|
211
|
+
type: 'Pie',
|
212
|
+
sourceCollectionName: collection,
|
213
|
+
timezone: 'Europe/Paris',
|
214
|
+
aggregator: 'Invalid',
|
215
|
+
groupByFieldName: groupByFieldName
|
216
|
+
}
|
217
|
+
}
|
218
|
+
|
219
|
+
it 'should raise an error for unsupported aggregator' do
|
220
|
+
expect {
|
221
|
+
subject.send(:aggregation_sql, 'invalid', 'age')
|
222
|
+
}.to raise_error(ForestLiana::Errors::HTTP422Error)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
describe 'aggregation_alias method' do
|
228
|
+
subject { PieStatGetter.new(model, params, user) }
|
229
|
+
|
230
|
+
context 'with COUNT aggregator' do
|
231
|
+
let(:params) {
|
232
|
+
{
|
233
|
+
type: 'Pie',
|
234
|
+
sourceCollectionName: collection,
|
235
|
+
timezone: 'Europe/Paris',
|
236
|
+
aggregator: 'Count',
|
237
|
+
groupByFieldName: groupByFieldName
|
238
|
+
}
|
239
|
+
}
|
240
|
+
|
241
|
+
it 'should return correct alias for count' do
|
242
|
+
alias_name = subject.send(:aggregation_alias, 'count', nil)
|
243
|
+
expect(alias_name).to eq 'count_id'
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
context 'with SUM aggregator' do
|
248
|
+
let(:params) {
|
249
|
+
{
|
250
|
+
type: 'Pie',
|
251
|
+
sourceCollectionName: collection,
|
252
|
+
timezone: 'Europe/Paris',
|
253
|
+
aggregator: 'Sum',
|
254
|
+
aggregateFieldName: 'age',
|
255
|
+
groupByFieldName: groupByFieldName
|
256
|
+
}
|
257
|
+
}
|
258
|
+
|
259
|
+
it 'should return correct alias for sum' do
|
260
|
+
alias_name = subject.send(:aggregation_alias, 'sum', 'age')
|
261
|
+
expect(alias_name).to eq 'sum_age'
|
262
|
+
end
|
263
|
+
|
264
|
+
it 'should handle field names with mixed case' do
|
265
|
+
alias_name = subject.send(:aggregation_alias, 'sum', 'TreeAge')
|
266
|
+
expect(alias_name).to eq 'sum_treeage'
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
describe 'results ordering' do
|
272
|
+
subject { PieStatGetter.new(model, params, user) }
|
273
|
+
|
274
|
+
context 'with COUNT aggregator' do
|
275
|
+
let(:params) {
|
276
|
+
{
|
277
|
+
type: 'Pie',
|
278
|
+
sourceCollectionName: collection,
|
279
|
+
timezone: 'Europe/Paris',
|
280
|
+
aggregator: 'Count',
|
281
|
+
groupByFieldName: groupByFieldName
|
282
|
+
}
|
283
|
+
}
|
284
|
+
|
285
|
+
it 'should return results ordered by count descending' do
|
286
|
+
subject.perform
|
287
|
+
|
288
|
+
expect(subject.record.value).to eq [
|
289
|
+
{ :key => 3, :value => 5},
|
290
|
+
{ :key => 15, :value => 4 }
|
291
|
+
]
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
context 'with SUM aggregator' do
|
296
|
+
let(:params) {
|
297
|
+
{
|
298
|
+
type: 'Pie',
|
299
|
+
sourceCollectionName: collection,
|
300
|
+
timezone: 'Europe/Paris',
|
301
|
+
aggregator: 'Sum',
|
302
|
+
aggregateFieldName: 'age',
|
303
|
+
groupByFieldName: groupByFieldName
|
304
|
+
}
|
305
|
+
}
|
306
|
+
|
307
|
+
it 'should return results ordered by sum descending' do
|
308
|
+
subject.perform
|
309
|
+
|
310
|
+
expect(subject.record.value).to eq [
|
311
|
+
{ :key => 15, :value => 60 },
|
312
|
+
{ :key => 3, :value => 15 }
|
313
|
+
]
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
136
318
|
end
|
137
319
|
end
|
@@ -4,7 +4,7 @@ module ForestLiana
|
|
4
4
|
let(:pageSize) { 10 }
|
5
5
|
let(:pageNumber) { 1 }
|
6
6
|
let(:sort) { 'id' }
|
7
|
-
let(:fields) {}
|
7
|
+
let(:fields) { { resource.name => 'id' } }
|
8
8
|
let(:filters) {}
|
9
9
|
let(:scopes) { {'scopes' => {}, 'team' => {'id' => '1', 'name' => 'Operations'}} }
|
10
10
|
let(:rendering_id) { 13 }
|
@@ -38,7 +38,7 @@ module ForestLiana
|
|
38
38
|
clean_database
|
39
39
|
|
40
40
|
users = ['Michel', 'Robert', 'Vince', 'Sandro', 'Olesya', 'Romain', 'Valentin', 'Jason', 'Arnaud', 'Jeff', 'Steve', 'Marc', 'Xavier', 'Paul', 'Mickael', 'Mike', 'Maxime', 'Gertrude', 'Monique', 'Mia', 'Rachid', 'Edouard', 'Sacha', 'Caro', 'Amand', 'Nathan', 'Noémie', 'Robin', 'Gaelle', 'Isabelle']
|
41
|
-
|
41
|
+
.map { |name| User.create(name: name) }
|
42
42
|
|
43
43
|
islands = [
|
44
44
|
{ :name => 'Skull', :updated_at => Time.now - 1.years },
|
@@ -190,6 +190,7 @@ module ForestLiana
|
|
190
190
|
|
191
191
|
describe 'when on a model having a reserved SQL word as name' do
|
192
192
|
let(:resource) { Reference }
|
193
|
+
let(:fields) { { resource.name => 'id' } }
|
193
194
|
|
194
195
|
it 'should get the ressource properly' do
|
195
196
|
getter.perform
|
@@ -219,6 +220,7 @@ module ForestLiana
|
|
219
220
|
|
220
221
|
describe 'when sorting by a belongs_to association' do
|
221
222
|
let(:resource) { Tree }
|
223
|
+
let(:fields) { { resource.name => 'id' } }
|
222
224
|
let(:sort) { 'owner.name' }
|
223
225
|
|
224
226
|
it 'should get only the expected records' do
|
@@ -288,21 +290,37 @@ module ForestLiana
|
|
288
290
|
end
|
289
291
|
end
|
290
292
|
|
293
|
+
describe 'when getting a has_one through association' do
|
294
|
+
let(:resource) { Tree }
|
295
|
+
let(:fields) { { 'Tree' => 'id,location' } }
|
296
|
+
|
297
|
+
it 'should get the expected records, including the foreign_key for the direct association' do
|
298
|
+
getter.perform
|
299
|
+
records = getter.records
|
300
|
+
count = getter.count
|
301
|
+
|
302
|
+
expect(records.count).to eq Tree.count
|
303
|
+
expect(count).to eq Tree.count
|
304
|
+
expect(records.map(&:id)).to match_array(Tree.pluck(:id))
|
305
|
+
expect(records.map(&:island_id)).to match_array(Tree.pluck(:island_id))
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
291
309
|
describe 'when filtering on an ambiguous field' do
|
292
310
|
let(:resource) { Tree }
|
293
311
|
let(:pageSize) { 5 }
|
294
|
-
let(:fields) { { 'Tree' => 'id' } }
|
312
|
+
let(:fields) { { 'Tree' => 'id,name', 'cutter' => 'id' } }
|
295
313
|
let(:filters) { {
|
296
314
|
aggregator: 'and',
|
297
315
|
conditions: [{
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
316
|
+
field: 'created_at',
|
317
|
+
operator: 'after',
|
318
|
+
value: "#{Time.now - 6.year}",
|
319
|
+
}, {
|
320
|
+
field: 'cutter:name',
|
321
|
+
operator: 'equal',
|
322
|
+
value: 'Michel'
|
323
|
+
}]
|
306
324
|
}.to_json }
|
307
325
|
|
308
326
|
it 'should get only the expected records' do
|
@@ -314,7 +332,6 @@ module ForestLiana
|
|
314
332
|
expect(count).to eq 1
|
315
333
|
expect(records.first.id).to eq 3
|
316
334
|
expect(records.first.name).to eq 'Apple Tree'
|
317
|
-
expect(records.first.cutter.name).to eq 'Michel'
|
318
335
|
end
|
319
336
|
end
|
320
337
|
|
@@ -361,7 +378,7 @@ module ForestLiana
|
|
361
378
|
describe 'when sorting on an ambiguous field name with a filter' do
|
362
379
|
let(:resource) { Tree }
|
363
380
|
let(:sort) { '-name' }
|
364
|
-
let(:fields) { { 'Tree' => 'id' } }
|
381
|
+
let(:fields) { { 'Tree' => 'id,name' } }
|
365
382
|
let(:filters) { {
|
366
383
|
field: 'cutter:name',
|
367
384
|
operator: 'equal',
|
@@ -399,7 +416,7 @@ module ForestLiana
|
|
399
416
|
|
400
417
|
describe 'when filtering on an updated_at field of an associated collection' do
|
401
418
|
let(:resource) { Tree }
|
402
|
-
let(:fields) { { 'Tree' => 'id' } }
|
419
|
+
let(:fields) { { 'Tree' => 'id,name' } }
|
403
420
|
let(:filters) { {
|
404
421
|
field: 'island:updated_at',
|
405
422
|
operator: 'previous_year'
|
@@ -492,16 +509,16 @@ module ForestLiana
|
|
492
509
|
|
493
510
|
it 'should raise the right error' do
|
494
511
|
expect { getter }.to raise_error(
|
495
|
-
|
496
|
-
|
497
|
-
|
512
|
+
ForestLiana::Errors::NotImplementedMethodError,
|
513
|
+
"method filter on smart field 'alter_coordinates' not found"
|
514
|
+
)
|
498
515
|
end
|
499
516
|
end
|
500
517
|
|
501
518
|
describe 'when scopes are defined' do
|
502
519
|
let(:resource) { Island }
|
503
520
|
let(:pageSize) { 15 }
|
504
|
-
let(:fields) { }
|
521
|
+
let(:fields) { { resource.name => 'id' } }
|
505
522
|
let(:filters) { }
|
506
523
|
let(:scopes) {
|
507
524
|
{
|
@@ -537,10 +554,10 @@ module ForestLiana
|
|
537
554
|
let(:filters) { {
|
538
555
|
aggregator: 'and',
|
539
556
|
conditions: [{
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
557
|
+
field: 'name',
|
558
|
+
operator: 'contains',
|
559
|
+
value: 'a',
|
560
|
+
}]
|
544
561
|
}.to_json }
|
545
562
|
|
546
563
|
it 'should get only the records matching the scope' do
|
@@ -554,6 +571,84 @@ module ForestLiana
|
|
554
571
|
expect(records.second.name).to eq 'Treasure'
|
555
572
|
end
|
556
573
|
end
|
574
|
+
|
575
|
+
describe '#perform' do
|
576
|
+
let(:resource) { User }
|
577
|
+
let(:fields) { nil }
|
578
|
+
let(:sort) { nil }
|
579
|
+
let(:filters) { nil }
|
580
|
+
|
581
|
+
it 'does not raise an error when params[:fields] is nil' do
|
582
|
+
expect {
|
583
|
+
getter.perform
|
584
|
+
}.not_to raise_error
|
585
|
+
|
586
|
+
records = getter.records
|
587
|
+
count = getter.count
|
588
|
+
|
589
|
+
expect(records).to all(be_a(User))
|
590
|
+
expect(count).to eq User.count
|
591
|
+
end
|
592
|
+
end
|
593
|
+
|
594
|
+
describe '#extract_associations_from_filter' do
|
595
|
+
let(:resource) { Tree }
|
596
|
+
|
597
|
+
before { init_scopes }
|
598
|
+
|
599
|
+
context 'with a single filter as JSON string' do
|
600
|
+
let(:filters) {
|
601
|
+
{
|
602
|
+
field: 'island:updated_at',
|
603
|
+
operator: 'equal',
|
604
|
+
value: '2024-01-01'
|
605
|
+
}.to_json
|
606
|
+
}
|
607
|
+
|
608
|
+
it 'extracts the correct association' do
|
609
|
+
expect(getter.send(:extract_associations_from_filter)).to eq [:island]
|
610
|
+
end
|
611
|
+
end
|
612
|
+
|
613
|
+
context 'with grouped conditions as JSON string' do
|
614
|
+
let(:filters) {
|
615
|
+
{
|
616
|
+
aggregator: 'and',
|
617
|
+
conditions: [
|
618
|
+
{ field: 'island:updated_at', operator: 'equal', value: '2024-01-01' },
|
619
|
+
{ field: 'owner:name', operator: 'equal', value: 'Michel' },
|
620
|
+
{ field: 'id', operator: 'present', value: nil }
|
621
|
+
]
|
622
|
+
}.to_json
|
623
|
+
}
|
624
|
+
|
625
|
+
it 'extracts all unique associations' do
|
626
|
+
expect(getter.send(:extract_associations_from_filter)).to match_array [:island, :owner]
|
627
|
+
end
|
628
|
+
end
|
629
|
+
|
630
|
+
context 'when filters has no association field' do
|
631
|
+
let(:filters) {
|
632
|
+
{
|
633
|
+
field: 'id',
|
634
|
+
operator: 'equal',
|
635
|
+
value: 1
|
636
|
+
}.to_json
|
637
|
+
}
|
638
|
+
|
639
|
+
it 'returns an empty array' do
|
640
|
+
expect(getter.send(:extract_associations_from_filter)).to eq []
|
641
|
+
end
|
642
|
+
end
|
643
|
+
|
644
|
+
context 'when filters is nil' do
|
645
|
+
let(:filters) { nil }
|
646
|
+
|
647
|
+
it 'returns an empty array' do
|
648
|
+
expect(getter.send(:extract_associations_from_filter)).to eq []
|
649
|
+
end
|
650
|
+
end
|
651
|
+
end
|
557
652
|
end
|
558
653
|
end
|
559
|
-
end
|
654
|
+
end
|
@@ -61,7 +61,18 @@ module ForestLiana
|
|
61
61
|
end
|
62
62
|
|
63
63
|
expect(collection.fields.map { |field| field[:field].to_s}).to eq(
|
64
|
-
[
|
64
|
+
[
|
65
|
+
"age",
|
66
|
+
"created_at",
|
67
|
+
"cutter",
|
68
|
+
"eponymous_island",
|
69
|
+
"id",
|
70
|
+
"island",
|
71
|
+
"location",
|
72
|
+
"name",
|
73
|
+
"owner",
|
74
|
+
"updated_at"
|
75
|
+
]
|
65
76
|
)
|
66
77
|
end
|
67
78
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module ForestLiana
|
2
|
+
describe SerializerFactory do
|
3
|
+
describe '#serializer_for has_one_relationships patch' do
|
4
|
+
let(:user) { User.create!(name: 'PatchTest') }
|
5
|
+
let(:island) { Island.create!(name: 'TestIsland') }
|
6
|
+
let(:tree) { Tree.create!(name: 'TestTree', island: island, owner: user) }
|
7
|
+
|
8
|
+
it 'simulates has_one relation with only primary key' do
|
9
|
+
factory = described_class.new
|
10
|
+
serializer_class = factory.serializer_for(Tree)
|
11
|
+
|
12
|
+
serializer_class.send(:has_one, :island) { }
|
13
|
+
|
14
|
+
instance = serializer_class.new(tree, fields: {
|
15
|
+
'Island' => [:id],
|
16
|
+
'Tree' => [:island]
|
17
|
+
})
|
18
|
+
|
19
|
+
relationships = instance.send(:has_one_relationships)
|
20
|
+
expect(relationships).to have_key(:island)
|
21
|
+
relation_data = relationships[:island]
|
22
|
+
expect(relation_data[:attr_or_block]).to be_a(Proc)
|
23
|
+
model = relation_data[:attr_or_block].call
|
24
|
+
|
25
|
+
expect(model).to be_a(Island)
|
26
|
+
expect(model.id).to eq(island.id)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'returns nil if foreign key is nil' do
|
30
|
+
tree_without_island = Tree.create!(name: 'NoIslandTree', island_id: nil, owner: user)
|
31
|
+
|
32
|
+
factory = described_class.new
|
33
|
+
serializer_class = factory.serializer_for(Tree)
|
34
|
+
|
35
|
+
serializer_class.send(:has_one, :island) { }
|
36
|
+
|
37
|
+
instance = serializer_class.new(tree_without_island, fields: {
|
38
|
+
'Island' => [:id],
|
39
|
+
'Tree' => [:island]
|
40
|
+
})
|
41
|
+
|
42
|
+
relationships = instance.send(:has_one_relationships)
|
43
|
+
expect(relationships).to have_key(:island)
|
44
|
+
relation_data = relationships[:island]
|
45
|
+
expect(relation_data[:attr_or_block]).to be_a(Proc)
|
46
|
+
model = relation_data[:attr_or_block].call
|
47
|
+
|
48
|
+
expect(model).to be_nil
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: forest_liana
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 9.
|
4
|
+
version: 9.15.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sandro Munda
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-07-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -254,6 +254,7 @@ files:
|
|
254
254
|
- app/services/forest_liana/ability/permission.rb
|
255
255
|
- app/services/forest_liana/ability/permission/request_permission.rb
|
256
256
|
- app/services/forest_liana/ability/permission/smart_action_checker.rb
|
257
|
+
- app/services/forest_liana/aggregation_helper.rb
|
257
258
|
- app/services/forest_liana/apimap_sorter.rb
|
258
259
|
- app/services/forest_liana/authentication.rb
|
259
260
|
- app/services/forest_liana/authorization_getter.rb
|
@@ -317,6 +318,7 @@ files:
|
|
317
318
|
- config/routes.rb
|
318
319
|
- config/routes/actions.rb
|
319
320
|
- lib/forest_liana.rb
|
321
|
+
- lib/forest_liana/active_record_override.rb
|
320
322
|
- lib/forest_liana/base64_string_io.rb
|
321
323
|
- lib/forest_liana/bootstrapper.rb
|
322
324
|
- lib/forest_liana/collection.rb
|
@@ -430,6 +432,7 @@ files:
|
|
430
432
|
- spec/services/forest_liana/resources_getter_spec.rb
|
431
433
|
- spec/services/forest_liana/schema_adapter_spec.rb
|
432
434
|
- spec/services/forest_liana/scope_manager_spec.rb
|
435
|
+
- spec/services/forest_liana/serializer_factory_spec.rb
|
433
436
|
- spec/services/forest_liana/smart_action_field_validator_spec.rb
|
434
437
|
- spec/services/forest_liana/smart_action_form_parser_spec.rb
|
435
438
|
- spec/services/forest_liana/utils/context_variables_injector_spec.rb
|
@@ -735,6 +738,7 @@ test_files:
|
|
735
738
|
- spec/services/forest_liana/resources_getter_spec.rb
|
736
739
|
- spec/services/forest_liana/schema_adapter_spec.rb
|
737
740
|
- spec/services/forest_liana/scope_manager_spec.rb
|
741
|
+
- spec/services/forest_liana/serializer_factory_spec.rb
|
738
742
|
- spec/services/forest_liana/smart_action_field_validator_spec.rb
|
739
743
|
- spec/services/forest_liana/smart_action_form_parser_spec.rb
|
740
744
|
- spec/services/forest_liana/utils/context_variables_injector_spec.rb
|