forest_liana 9.22.9 → 9.22.10
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/services/forest_liana/schema_utils.rb +12 -4
- data/lib/forest_liana/lookup_cache.rb +37 -0
- data/lib/forest_liana/version.rb +1 -1
- data/lib/forest_liana.rb +7 -4
- data/spec/lib/forest_liana/lookup_cache_spec.rb +68 -0
- data/spec/rails_helper.rb +1 -0
- data/spec/services/forest_liana/ability/assert_can_read_query_fields_spec.rb +2 -4
- data/spec/services/forest_liana/ability/redact_fields_spec.rb +2 -4
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 23a1adeed07bf166b64953093122fc2e9c8c442d1ab52aec8227bec80f83f17d
|
|
4
|
+
data.tar.gz: 3cbea9923bf9350fbd8ad11c0a11479504c3a7b876b1c96e2c79a0cf602c88a2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d41d2034547eb686df156ac01a72cd9e85f3aa10fcaf272831013dcf0827f7bd1b216b35828b22cc699d88e6ed1541ddaae5769b76e16cf6caf6aace4a49edef
|
|
7
|
+
data.tar.gz: f26b8e3fb84c18d3ee198b0f474e2bf189254b0c47a32365ee775ffcb1652baee2c6eea641e22cdb753d3f552ef5cc360db7b6c76a3df3f5eef8357adec0ede0
|
|
@@ -31,6 +31,18 @@ module ForestLiana
|
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def self.find_model_from_collection_name(collection_name, logs = false)
|
|
34
|
+
model_found = LookupCache.fetch(:model_from_collection_name, collection_name) do
|
|
35
|
+
scan_models_for_collection_name(collection_name)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
if logs && model_found.nil?
|
|
39
|
+
FOREST_LOGGER.warn "No model found for collection #{collection_name}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
model_found
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.scan_models_for_collection_name(collection_name)
|
|
34
46
|
model_found = nil
|
|
35
47
|
ForestLiana.models.each do |model|
|
|
36
48
|
if model.abstract_class?
|
|
@@ -42,10 +54,6 @@ module ForestLiana
|
|
|
42
54
|
break if model_found
|
|
43
55
|
end
|
|
44
56
|
|
|
45
|
-
if logs && model_found.nil?
|
|
46
|
-
FOREST_LOGGER.warn "No model found for collection #{collection_name}"
|
|
47
|
-
end
|
|
48
|
-
|
|
49
57
|
model_found
|
|
50
58
|
end
|
|
51
59
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module ForestLiana
|
|
2
|
+
# Remembers lookups by name over ForestLiana.models, .apimap and .names_overriden: on a large
|
|
3
|
+
# schema each one is a linear scan, schema_for_resource a scan of scans, and every request runs
|
|
4
|
+
# several. Any of the three being replaced or growing (the bootstrapper, a Forest collection
|
|
5
|
+
# file, a code reload) drops everything remembered, so an answer never outlives what it read.
|
|
6
|
+
module LookupCache
|
|
7
|
+
# A miss is not remembered: the router resolves a request's collection before authenticating
|
|
8
|
+
# it, so caching unknown names would let any caller grow this cache without bound.
|
|
9
|
+
def self.fetch(namespace, key)
|
|
10
|
+
reset_if_stale
|
|
11
|
+
store = (@stores[namespace] ||= {})
|
|
12
|
+
return store[key] if store.key?(key)
|
|
13
|
+
|
|
14
|
+
value = yield
|
|
15
|
+
store[key] = value unless value.nil?
|
|
16
|
+
value
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.reset!
|
|
20
|
+
@stores = nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.reset_if_stale
|
|
24
|
+
models = ForestLiana.models
|
|
25
|
+
apimap = ForestLiana.apimap
|
|
26
|
+
names = ForestLiana.names_overriden
|
|
27
|
+
return if @stores && models.equal?(@models) && models.size == @models_size &&
|
|
28
|
+
apimap.equal?(@apimap) && apimap.size == @apimap_size &&
|
|
29
|
+
names.equal?(@names) && names.size == @names_size
|
|
30
|
+
|
|
31
|
+
@models, @models_size = models, models.size
|
|
32
|
+
@apimap, @apimap_size = apimap, apimap.size
|
|
33
|
+
@names, @names_size = names, names.size
|
|
34
|
+
@stores = {}
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
data/lib/forest_liana/version.rb
CHANGED
data/lib/forest_liana.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require 'forest_liana/engine'
|
|
2
|
+
require 'forest_liana/lookup_cache'
|
|
2
3
|
|
|
3
4
|
module Forest
|
|
4
5
|
end
|
|
@@ -69,14 +70,16 @@ module ForestLiana
|
|
|
69
70
|
end
|
|
70
71
|
|
|
71
72
|
def self.schema_for_resource resource
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
.
|
|
73
|
+
LookupCache.fetch(:schema_for_resource, resource.name) do
|
|
74
|
+
self.apimap.find do |collection|
|
|
75
|
+
SchemaUtils.find_model_from_collection_name(collection.name)
|
|
76
|
+
.try(:name) == resource.name
|
|
77
|
+
end
|
|
75
78
|
end
|
|
76
79
|
end
|
|
77
80
|
|
|
78
81
|
def self.name_for(model)
|
|
79
|
-
self.names_overriden[model] || model.try(:name).gsub('::', '__')
|
|
82
|
+
LookupCache.fetch(:name_for, model) { self.names_overriden[model] || model.try(:name).gsub('::', '__') }
|
|
80
83
|
end
|
|
81
84
|
|
|
82
85
|
# TODO: Remove once lianas prior to 2.0.0 are not supported anymore.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
require 'rails_helper'
|
|
2
|
+
|
|
3
|
+
describe 'Lookups by name over the schema' do
|
|
4
|
+
describe 'ForestLiana.name_for' do
|
|
5
|
+
after { ForestLiana.names_overriden.delete(Tree) }
|
|
6
|
+
|
|
7
|
+
it 'answers an override registered after a first lookup' do
|
|
8
|
+
expect(ForestLiana.name_for(Tree)).to eq 'Tree'
|
|
9
|
+
|
|
10
|
+
ForestLiana.names_overriden[Tree] = 'Orchard'
|
|
11
|
+
|
|
12
|
+
expect(ForestLiana.name_for(Tree)).to eq 'Orchard'
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
describe 'ForestLiana::SchemaUtils.find_model_from_collection_name' do
|
|
17
|
+
let(:ghost) { Class.new(ActiveRecord::Base) { self.table_name = 'trees' } }
|
|
18
|
+
|
|
19
|
+
after do
|
|
20
|
+
ForestLiana.models.delete(ghost)
|
|
21
|
+
ForestLiana.names_overriden.delete(ghost)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'scans the models once for repeated lookups of a name' do
|
|
25
|
+
expect(ForestLiana::SchemaUtils).to receive(:scan_models_for_collection_name).with('Tree').once.and_call_original
|
|
26
|
+
|
|
27
|
+
3.times { expect(ForestLiana::SchemaUtils.find_model_from_collection_name('Tree')).to eq Tree }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'does not remember an unknown name, which any caller can send before authenticating' do
|
|
31
|
+
expect(ForestLiana::SchemaUtils).to receive(:scan_models_for_collection_name).with('Ghost').twice.and_call_original
|
|
32
|
+
|
|
33
|
+
2.times { expect(ForestLiana::SchemaUtils.find_model_from_collection_name('Ghost')).to be_nil }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it 'still warns on each lookup of an unknown name' do
|
|
37
|
+
expect(FOREST_LOGGER).to receive(:warn).with('No model found for collection Ghost').twice
|
|
38
|
+
|
|
39
|
+
2.times { expect(ForestLiana::SchemaUtils.find_model_from_collection_name('Ghost', true)).to be_nil }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'finds a model added after a first lookup missed it' do
|
|
43
|
+
expect(ForestLiana::SchemaUtils.find_model_from_collection_name('Ghost')).to be_nil
|
|
44
|
+
|
|
45
|
+
ForestLiana.names_overriden[ghost] = 'Ghost'
|
|
46
|
+
ForestLiana.models << ghost
|
|
47
|
+
|
|
48
|
+
expect(ForestLiana::SchemaUtils.find_model_from_collection_name('Ghost')).to eq ghost
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
describe 'ForestLiana.schema_for_resource' do
|
|
53
|
+
it 'resolves the collection of a resource once for repeated lookups' do
|
|
54
|
+
expect(ForestLiana.schema_for_resource(Tree).name).to eq 'Tree'
|
|
55
|
+
expect(ForestLiana::SchemaUtils).not_to receive(:find_model_from_collection_name)
|
|
56
|
+
|
|
57
|
+
expect(ForestLiana.schema_for_resource(Tree).name).to eq 'Tree'
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it 'answers from the apimap in place when it is replaced' do
|
|
61
|
+
expect(ForestLiana.schema_for_resource(Tree).name).to eq 'Tree'
|
|
62
|
+
replacement = ForestLiana::Model::Collection.new(name: 'Tree', fields: [])
|
|
63
|
+
allow(ForestLiana).to receive(:apimap).and_return([replacement])
|
|
64
|
+
|
|
65
|
+
expect(ForestLiana.schema_for_resource(Tree)).to equal replacement
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
data/spec/rails_helper.rb
CHANGED
|
@@ -65,6 +65,7 @@ RSpec.configure do |config|
|
|
|
65
65
|
# own comment) — cleared before every example so one spec's warning can't silence another's,
|
|
66
66
|
# regardless of run order or which file exercises the valve.
|
|
67
67
|
config.before { ForestLiana::MissingAttributeValve::WARNED_ONCE.clear }
|
|
68
|
+
config.before { ForestLiana::LookupCache.reset! }
|
|
68
69
|
|
|
69
70
|
# Filter lines from Rails gems in backtraces.
|
|
70
71
|
config.filter_rails_from_backtrace!
|
|
@@ -67,10 +67,9 @@ module ForestLiana
|
|
|
67
67
|
expect { dummy_class.assert_can_read_query_fields(user, Tree) }.not_to raise_error
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
-
it '
|
|
70
|
+
it 'indexes the apimap once, however many paths are checked' do
|
|
71
71
|
write_permissions('Tree' => true, 'User' => true, 'Island' => true)
|
|
72
|
-
|
|
73
|
-
allow(ForestLiana).to receive(:apimap).and_wrap_original { |original| reads += 1; original.call }
|
|
72
|
+
expect(ForestLiana::Ability::ApimapIndex).to receive(:new).with(ForestLiana.apimap).once.and_call_original
|
|
74
73
|
|
|
75
74
|
expect do
|
|
76
75
|
dummy_class.assert_can_read_query_fields(
|
|
@@ -78,7 +77,6 @@ module ForestLiana
|
|
|
78
77
|
search_paths: %w[owner:title]
|
|
79
78
|
)
|
|
80
79
|
end.not_to raise_error
|
|
81
|
-
expect(reads).to eq 1
|
|
82
80
|
end
|
|
83
81
|
|
|
84
82
|
it 'serves a filter, a sort and a search on an unreadable collection once the option is on' do
|
|
@@ -169,10 +169,9 @@ module ForestLiana
|
|
|
169
169
|
|
|
170
170
|
# Resolving each field against the apimap rescans every collection: on a wide collection and
|
|
171
171
|
# a large schema, that cost outweighs the request it guards.
|
|
172
|
-
it '
|
|
172
|
+
it 'indexes the apimap once, however many fields are requested' do
|
|
173
173
|
write_permissions('Tree' => true, 'User' => true, 'Island' => true)
|
|
174
|
-
|
|
175
|
-
allow(ForestLiana).to receive(:apimap).and_wrap_original { |original| reads += 1; original.call }
|
|
174
|
+
expect(ForestLiana::Ability::ApimapIndex).to receive(:new).with(ForestLiana.apimap).once.and_call_original
|
|
176
175
|
|
|
177
176
|
redacted = dummy_class.redact_fields(
|
|
178
177
|
user, Tree, { 'Tree' => 'id,name,age,owner,cutter,island', 'owner' => 'name', 'island' => 'name' },
|
|
@@ -180,7 +179,6 @@ module ForestLiana
|
|
|
180
179
|
)
|
|
181
180
|
|
|
182
181
|
expect(redacted).to eq('Tree' => 'id,name,age,owner,cutter,island', 'owner' => 'name', 'island' => 'name')
|
|
183
|
-
expect(reads).to eq 1
|
|
184
182
|
end
|
|
185
183
|
|
|
186
184
|
it 'checks nothing at all when only root collection fields are requested' do
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: forest_liana
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 9.22.
|
|
4
|
+
version: 9.22.10
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sandro Munda
|
|
@@ -365,6 +365,7 @@ files:
|
|
|
365
365
|
- lib/forest_liana/collection.rb
|
|
366
366
|
- lib/forest_liana/engine.rb
|
|
367
367
|
- lib/forest_liana/json_printer.rb
|
|
368
|
+
- lib/forest_liana/lookup_cache.rb
|
|
368
369
|
- lib/forest_liana/mixpanel_event.rb
|
|
369
370
|
- lib/forest_liana/schema_file_updater.rb
|
|
370
371
|
- lib/forest_liana/version.rb
|
|
@@ -471,6 +472,7 @@ files:
|
|
|
471
472
|
- spec/lib/forest_liana/bootstrapper_spec.rb
|
|
472
473
|
- spec/lib/forest_liana/collection_spec.rb
|
|
473
474
|
- spec/lib/forest_liana/engine_spec.rb
|
|
475
|
+
- spec/lib/forest_liana/lookup_cache_spec.rb
|
|
474
476
|
- spec/lib/forest_liana/schema_file_updater_spec.rb
|
|
475
477
|
- spec/models/forest_liana/model/action_spec.rb
|
|
476
478
|
- spec/models/forest_liana/model/collection_spec.rb
|
|
@@ -829,6 +831,7 @@ test_files:
|
|
|
829
831
|
- spec/lib/forest_liana/bootstrapper_spec.rb
|
|
830
832
|
- spec/lib/forest_liana/collection_spec.rb
|
|
831
833
|
- spec/lib/forest_liana/engine_spec.rb
|
|
834
|
+
- spec/lib/forest_liana/lookup_cache_spec.rb
|
|
832
835
|
- spec/lib/forest_liana/schema_file_updater_spec.rb
|
|
833
836
|
- spec/models/forest_liana/model/action_spec.rb
|
|
834
837
|
- spec/models/forest_liana/model/collection_spec.rb
|