smart_collection 0.2 → 0.3.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 +5 -5
- data/lib/smart_collection/cache_manager.rb +28 -16
- data/lib/smart_collection/config.rb +47 -2
- data/lib/smart_collection/instance_methods.rb +19 -0
- data/lib/smart_collection/mixin.rb +59 -61
- data/lib/smart_collection/version.rb +1 -1
- metadata +12 -10
- data/lib/smart_collection/cache_manager/cache_store.rb +0 -42
- data/lib/smart_collection/cache_manager/table.rb +0 -44
- data/lib/smart_collection/scope_builder.rb +0 -46
- data/lib/smart_collection/validator.rb +0 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ab885e83fde2cc95d9864c47930a00c074228be053f02a4c115188fa815be2c0
|
4
|
+
data.tar.gz: 83f2cec29972fa10282e8aa35a41577afded2dff176f85c4db627c820996069e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 05a2b4e9d08c201646a72db23941074e7d6a41aff1dee36bad1013349759e5ba7b5a38ee4024650e1b14b720ddc3d14fe646e5b8d1f31518733795274f1a56a9
|
7
|
+
data.tar.gz: 46afdfe614697e6ae5efebe1f46e7fc8179c0dad073ffc63cc65b7be570ab6a1a7c02e2ce05b4d719881887c6f92ff2a842ba383009770c84bd74ebd42f8dfab
|
@@ -1,34 +1,46 @@
|
|
1
1
|
module SmartCollection
|
2
2
|
class CacheManager
|
3
|
-
|
4
|
-
def self.determine_class config
|
5
|
-
case
|
6
|
-
when config.dig(:cached_by, :table)
|
7
|
-
SmartCollection::CacheManager::Table
|
8
|
-
when config.dig(:cached_by, :cache_store)
|
9
|
-
SmartCollection::CacheManager::CacheStore
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
3
|
def initialize model:, config:
|
14
4
|
@model = model
|
15
5
|
@config = config
|
6
|
+
define_cache_association_for model
|
7
|
+
end
|
8
|
+
|
9
|
+
def define_cache_association_for model
|
10
|
+
config = @config
|
11
|
+
cached_item_model = nil
|
12
|
+
model.class_eval do
|
13
|
+
cached_item_model = Class.new ActiveRecord::Base do
|
14
|
+
self.table_name = config.cache_table_name
|
15
|
+
belongs_to config.item_name, class_name: config.item_class_name, foreign_key: :item_id
|
16
|
+
end
|
17
|
+
const_set("CachedItem", cached_item_model)
|
18
|
+
|
19
|
+
has_many :cached_items, class_name: cached_item_model.name, foreign_key: :collection_id
|
20
|
+
has_many "cached_#{config.items_name}".to_sym, class_name: config.item_class_name, through: :cached_items, source: config.item_name
|
21
|
+
end
|
22
|
+
@cache_model = cached_item_model
|
16
23
|
end
|
17
24
|
|
18
25
|
def update owner
|
19
|
-
|
26
|
+
scopes = @config.scopes_proc.(owner)
|
27
|
+
@cache_model.where(collection_id: owner.id).delete_all
|
28
|
+
#scopes.each do |scope|
|
29
|
+
# sql = scope.select(owner.id, :id).to_sql
|
30
|
+
# @cache_model.connection.execute "INSERT INTO #{@cache_model.table_name} (collection_id, item_id) #{sql}"
|
31
|
+
#end
|
32
|
+
@cache_model.connection.execute "INSERT INTO #{@cache_model.table_name} (collection_id, item_id) #{owner.smart_collection_mixin.uncached_scope(owner).select(owner.id, :id).to_sql}"
|
33
|
+
owner.update_column(:cache_expires_at, Time.now + @config.cache_expires_in_proc.(owner))
|
20
34
|
end
|
21
35
|
|
22
36
|
def read_scope owner
|
23
|
-
|
37
|
+
cache_association = owner.association("cached_#{@config.items_name}")
|
38
|
+
cache_association.scope
|
24
39
|
end
|
25
40
|
|
26
41
|
def cache_exists? owner
|
27
|
-
|
42
|
+
owner.cache_expires_at && owner.cache_expires_at > Time.now
|
28
43
|
end
|
29
44
|
|
30
|
-
def expires_in
|
31
|
-
@config.cache_config[:expires_in] || 1.hour
|
32
|
-
end
|
33
45
|
end
|
34
46
|
end
|
@@ -5,10 +5,11 @@ module SmartCollection
|
|
5
5
|
|
6
6
|
def initialize raw_config
|
7
7
|
@raw_config = raw_config
|
8
|
+
check_config
|
8
9
|
end
|
9
10
|
|
10
11
|
def items_name
|
11
|
-
@raw_config[:
|
12
|
+
@raw_config[:item_association]
|
12
13
|
end
|
13
14
|
|
14
15
|
def item_name
|
@@ -16,7 +17,7 @@ module SmartCollection
|
|
16
17
|
end
|
17
18
|
|
18
19
|
def item_class_name
|
19
|
-
@raw_config[:
|
20
|
+
@raw_config[:item_class_name]
|
20
21
|
end
|
21
22
|
|
22
23
|
def item_class
|
@@ -26,5 +27,49 @@ module SmartCollection
|
|
26
27
|
def cache_config
|
27
28
|
@raw_config[:cached_by]
|
28
29
|
end
|
30
|
+
|
31
|
+
def scopes_proc
|
32
|
+
case @raw_config[:scopes]
|
33
|
+
when Proc
|
34
|
+
@raw_config[:scopes]
|
35
|
+
when Symbol
|
36
|
+
-> (owner) { owner.send(@raw_config[:scopes]) }
|
37
|
+
else
|
38
|
+
raise "scopes option only accepts a Proc / Symbol"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def inverse_association
|
43
|
+
@raw_config[:inverse_association]
|
44
|
+
end
|
45
|
+
|
46
|
+
def cache_table_name
|
47
|
+
if @raw_config[:cache_table] == :default
|
48
|
+
:smart_collection_cached_items
|
49
|
+
else
|
50
|
+
@raw_config[:cache_table]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def cache_expires_in_proc
|
55
|
+
case @raw_config[:cache_expires_in]
|
56
|
+
when Proc
|
57
|
+
@raw_config[:cache_expires_in]
|
58
|
+
when ActiveSupport::Duration
|
59
|
+
-> (owner) { @raw_config[:cache_expires_in] }
|
60
|
+
when Symbol
|
61
|
+
-> (owner) { owner.send(@raw_config[:cache_expires_in]) }
|
62
|
+
else
|
63
|
+
raise "cache_expires_in option only accepts a Proc / ActiveSupport::Duration / Symbol"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def check_config
|
68
|
+
raise "items option must be provided" if items_name.nil?
|
69
|
+
raise "item_class option must be provided" if item_class_name.nil?
|
70
|
+
raise "scopes option must be provided" if @raw_config[:scopes].nil?
|
71
|
+
raise "cache_table option must be provided" if @raw_config[:cache_table].nil?
|
72
|
+
raise "cache_expires_in option must be provided" if @raw_config[:cache_expires_in].nil?
|
73
|
+
end
|
29
74
|
end
|
30
75
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module SmartCollection
|
2
|
+
module InstanceMethods
|
3
|
+
def update_cache
|
4
|
+
smart_collection_mixin.config.cache_manager.update self
|
5
|
+
end
|
6
|
+
|
7
|
+
def expire_cache
|
8
|
+
update_column(:cache_expires_at, Time.now - 1)
|
9
|
+
end
|
10
|
+
|
11
|
+
def cache_exists?
|
12
|
+
smart_collection_mixin.config.cache_manager.cache_exists? self
|
13
|
+
end
|
14
|
+
|
15
|
+
def smart_collection_mixin
|
16
|
+
self.class.smart_collection_mixin
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -1,23 +1,5 @@
|
|
1
1
|
module SmartCollection
|
2
2
|
class Mixin < Module
|
3
|
-
module InstanceMethods
|
4
|
-
def update_cache
|
5
|
-
smart_collection_mixin.config.cache_manager.update self
|
6
|
-
end
|
7
|
-
|
8
|
-
def expire_cache
|
9
|
-
update_column(:cache_expires_at, nil)
|
10
|
-
end
|
11
|
-
|
12
|
-
def cache_exists?
|
13
|
-
smart_collection_mixin.config.cache_manager.cache_exists? self
|
14
|
-
end
|
15
|
-
|
16
|
-
def smart_collection_mixin
|
17
|
-
self.class.smart_collection_mixin
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
3
|
module ClassMethods
|
22
4
|
def smart_collection_mixin
|
23
5
|
@__smart_collection_mixin ||= ancestors.find do |x|
|
@@ -33,71 +15,87 @@ module SmartCollection
|
|
33
15
|
end
|
34
16
|
|
35
17
|
def uncached_scope owner
|
36
|
-
|
18
|
+
scopes = @config.scopes_proc.(owner)
|
19
|
+
raise unless scopes.all?{|x| x.klass == @config.item_class}
|
20
|
+
if scopes.empty?
|
21
|
+
@config.item_class.where('1 = 0')
|
22
|
+
else
|
23
|
+
scopes = scopes.map do |scope|
|
24
|
+
if !scope.select_values.empty?
|
25
|
+
new_scope = scope.dup
|
26
|
+
new_scope.select_values = []
|
27
|
+
new_scope
|
28
|
+
else
|
29
|
+
scope
|
30
|
+
end
|
31
|
+
end
|
32
|
+
@config.item_class.from("(#{scopes.map{|x| "#{x.to_sql}"}.join(' UNION ')}) as #{@config.item_class.table_name}")
|
33
|
+
end
|
37
34
|
end
|
38
35
|
|
39
36
|
def cached_scope owner
|
40
37
|
@config.cache_manager.read_scope owner
|
41
38
|
end
|
42
39
|
|
43
|
-
def
|
44
|
-
|
45
|
-
|
46
|
-
reflection_options = {smart_collection: config}
|
47
|
-
if config.item_class_name
|
48
|
-
reflection_options[:class_name] = config.item_class_name
|
49
|
-
end
|
50
|
-
|
51
|
-
base.include(InstanceMethods)
|
52
|
-
base.extend(ClassMethods)
|
53
|
-
|
54
|
-
if cache_class = CacheManager.determine_class(@raw_config)
|
55
|
-
config.cache_manager = cache_class.new(model: base, config: config)
|
56
|
-
end
|
40
|
+
def define_association base
|
41
|
+
config = @config
|
42
|
+
config.cache_manager = CacheManager.new(model: base, config: config)
|
57
43
|
|
58
44
|
mixin_options = {
|
59
45
|
name: config.items_name,
|
60
46
|
scope: -> owner {
|
61
|
-
if
|
47
|
+
if owner.new_record?
|
48
|
+
uncached_scope(owner)
|
49
|
+
else
|
50
|
+
cache_manager = config.cache_manager
|
62
51
|
unless cache_manager.cache_exists? owner
|
63
52
|
owner.update_cache
|
64
53
|
end
|
65
54
|
cached_scope(owner)
|
66
|
-
else
|
67
|
-
uncached_scope(owner)
|
68
55
|
end
|
69
56
|
},
|
70
57
|
type: :collection
|
71
58
|
}
|
72
59
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
}
|
83
|
-
when cache_class == SmartCollection::CacheManager::Table
|
84
|
-
cached_name = "cached_#{config.items_name}".to_sym
|
85
|
-
mixin_options[:preloader] = -> owners {
|
86
|
-
owners.reject(&:cache_exists?).each(&:update_cache)
|
87
|
-
Associationist.preload(owners, cached_items: config.item_name)
|
88
|
-
owners.map do |owner|
|
89
|
-
[owner, owner.cached_items.map{|item| item.send(config.item_name)}]
|
90
|
-
end.to_h
|
91
|
-
}
|
92
|
-
else
|
93
|
-
mixin_options[:preloader] = -> _ {
|
94
|
-
raise RuntimeError, "Turn on cache to enable preloading."
|
95
|
-
}
|
96
|
-
end
|
60
|
+
cached_name = "cached_#{config.items_name}".to_sym
|
61
|
+
mixin_options[:preloader] = -> owners {
|
62
|
+
owners.reject(&:cache_exists?).each(&:update_cache)
|
63
|
+
Associationist.preload(owners, cached_items: config.item_name)
|
64
|
+
owners.map do |owner|
|
65
|
+
[owner, owner.cached_items.map{|item| item.send(config.item_name)}]
|
66
|
+
end.to_h
|
67
|
+
}
|
68
|
+
|
97
69
|
base.include Associationist::Mixin.new(mixin_options)
|
70
|
+
end
|
71
|
+
|
72
|
+
def expired_scope base
|
73
|
+
base.where(base.arel_table[:cache_expires_at].lt(Time.now))
|
74
|
+
end
|
75
|
+
|
76
|
+
def define_inverse_association base
|
77
|
+
return unless @config.inverse_association
|
98
78
|
|
99
|
-
|
79
|
+
mixin_options = {
|
80
|
+
name: @config.inverse_association,
|
81
|
+
scope: -> owner {
|
82
|
+
expired = base.joins(:cached_items).where(@config.cache_table_name => {item_id: owner.id}).merge(expired_scope base)
|
83
|
+
expired.each(&:update_cache)
|
84
|
+
base.joins(:cached_items).where(@config.cache_table_name => {item_id: owner.id})
|
85
|
+
},
|
86
|
+
type: :collection
|
87
|
+
}
|
88
|
+
@config.item_class.include Associationist::Mixin.new(mixin_options)
|
89
|
+
end
|
90
|
+
|
91
|
+
def included base
|
92
|
+
@config = config = SmartCollection::Config.new(@raw_config)
|
93
|
+
|
94
|
+
base.include(InstanceMethods)
|
95
|
+
base.extend(ClassMethods)
|
100
96
|
|
97
|
+
define_association base
|
98
|
+
define_inverse_association base
|
101
99
|
end
|
102
100
|
end
|
103
101
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: smart_collection
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- CicholGricenchos
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-07-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: associationist
|
@@ -16,14 +16,20 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.1
|
19
|
+
version: '0.1'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 0.1.8
|
20
23
|
type: :runtime
|
21
24
|
prerelease: false
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
23
26
|
requirements:
|
24
27
|
- - "~>"
|
25
28
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.1
|
29
|
+
version: '0.1'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.1.8
|
27
33
|
- !ruby/object:Gem::Dependency
|
28
34
|
name: pry
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
@@ -89,12 +95,9 @@ extra_rdoc_files: []
|
|
89
95
|
files:
|
90
96
|
- lib/smart_collection.rb
|
91
97
|
- lib/smart_collection/cache_manager.rb
|
92
|
-
- lib/smart_collection/cache_manager/cache_store.rb
|
93
|
-
- lib/smart_collection/cache_manager/table.rb
|
94
98
|
- lib/smart_collection/config.rb
|
99
|
+
- lib/smart_collection/instance_methods.rb
|
95
100
|
- lib/smart_collection/mixin.rb
|
96
|
-
- lib/smart_collection/scope_builder.rb
|
97
|
-
- lib/smart_collection/validator.rb
|
98
101
|
- lib/smart_collection/version.rb
|
99
102
|
homepage: https://github.com/CicholGricenchos/smart_collection
|
100
103
|
licenses:
|
@@ -115,8 +118,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
115
118
|
- !ruby/object:Gem::Version
|
116
119
|
version: '0'
|
117
120
|
requirements: []
|
118
|
-
|
119
|
-
rubygems_version: 2.5.2.3
|
121
|
+
rubygems_version: 3.2.15
|
120
122
|
signing_key:
|
121
123
|
specification_version: 4
|
122
124
|
summary: collections by rules
|
@@ -1,42 +0,0 @@
|
|
1
|
-
module SmartCollection
|
2
|
-
class CacheManager
|
3
|
-
class CacheStore < CacheManager
|
4
|
-
|
5
|
-
def initialize model:, config:
|
6
|
-
super
|
7
|
-
|
8
|
-
@cache_key_proc = @config.cache_config[:cache_key_proc] || -> (owner) { "_smart_collection_#{owner.class.name}_#{owner.id}" }
|
9
|
-
end
|
10
|
-
|
11
|
-
def cache_store
|
12
|
-
@config.cache_config[:cache_store]
|
13
|
-
end
|
14
|
-
|
15
|
-
def cache_key owner
|
16
|
-
@cache_key_proc.(owner)
|
17
|
-
end
|
18
|
-
|
19
|
-
def update owner
|
20
|
-
cache_store.write(cache_key(owner), Marshal.dump(owner.smart_collection_mixin.uncached_scope(owner).pluck(:id)))
|
21
|
-
owner.update(cache_expires_at: Time.now + expires_in)
|
22
|
-
end
|
23
|
-
|
24
|
-
def read_scope owner
|
25
|
-
@config.item_class.where(id: Marshal.load(cache_store.read(cache_key owner)))
|
26
|
-
end
|
27
|
-
|
28
|
-
def read_multi owners
|
29
|
-
cache_keys = owners.map{|owner| cache_key owner}
|
30
|
-
loaded = cache_store.read_multi(*cache_keys)
|
31
|
-
owners.map.with_index do |owner, index|
|
32
|
-
[owner, Marshal.load(loaded[cache_keys[index]])]
|
33
|
-
end.to_h
|
34
|
-
end
|
35
|
-
|
36
|
-
def cache_exists? owner
|
37
|
-
owner.cache_expires_at && owner.cache_expires_at > Time.now && cache_store.exist?(cache_key owner)
|
38
|
-
end
|
39
|
-
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
@@ -1,44 +0,0 @@
|
|
1
|
-
module SmartCollection
|
2
|
-
class CacheManager
|
3
|
-
class Table < CacheManager
|
4
|
-
|
5
|
-
def initialize model:, config:
|
6
|
-
super
|
7
|
-
|
8
|
-
define_cache_association_for model
|
9
|
-
end
|
10
|
-
|
11
|
-
def define_cache_association_for model
|
12
|
-
config = @config
|
13
|
-
cached_item_model = nil
|
14
|
-
model.class_eval do
|
15
|
-
cached_item_model = Class.new ActiveRecord::Base do
|
16
|
-
self.table_name = 'smart_collection_cached_items'
|
17
|
-
belongs_to config.item_name, class_name: config.item_class_name, foreign_key: :item_id
|
18
|
-
end
|
19
|
-
const_set("CachedItem", cached_item_model)
|
20
|
-
|
21
|
-
has_many :cached_items, class_name: cached_item_model.name, foreign_key: :collection_id
|
22
|
-
has_many "cached_#{config.items_name}".to_sym, class_name: config.item_class_name, through: :cached_items, source: config.item_name
|
23
|
-
end
|
24
|
-
@cache_model = cached_item_model
|
25
|
-
end
|
26
|
-
|
27
|
-
def update owner
|
28
|
-
@cache_model.where(collection_id: owner.id).delete_all
|
29
|
-
@cache_model.connection.execute "INSERT INTO #{@cache_model.table_name} (collection_id, item_id) #{owner.smart_collection_mixin.uncached_scope(owner).select(owner.id, :id).to_sql}"
|
30
|
-
owner.update_column(:cache_expires_at, Time.now + expires_in)
|
31
|
-
end
|
32
|
-
|
33
|
-
def read_scope owner
|
34
|
-
cache_association = owner.association("cached_#{@config.items_name}")
|
35
|
-
cache_association.scope
|
36
|
-
end
|
37
|
-
|
38
|
-
def cache_exists? owner
|
39
|
-
owner.cache_expires_at && owner.cache_expires_at > Time.now
|
40
|
-
end
|
41
|
-
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
@@ -1,46 +0,0 @@
|
|
1
|
-
module SmartCollection
|
2
|
-
class ScopeBuilder
|
3
|
-
def initialize rule, klass
|
4
|
-
@rule = rule
|
5
|
-
@klass = klass
|
6
|
-
@klass_hash = {}
|
7
|
-
end
|
8
|
-
|
9
|
-
def build
|
10
|
-
rule_to_bulk_queries @rule
|
11
|
-
bulk_load
|
12
|
-
rule_to_scope @rule
|
13
|
-
end
|
14
|
-
|
15
|
-
def bulk_load
|
16
|
-
@klass_hash = @klass_hash.map do |klass_name, ids|
|
17
|
-
[klass_name, Object.const_get(klass_name).where(id: ids).map{|x| [x.id, x]}.to_h]
|
18
|
-
end.to_h
|
19
|
-
end
|
20
|
-
|
21
|
-
def rule_to_bulk_queries rule
|
22
|
-
case
|
23
|
-
when arr = (rule['or'] || rule['and'])
|
24
|
-
arr.each{|x| rule_to_bulk_queries x}
|
25
|
-
when assoc = rule['association']
|
26
|
-
ids = @klass_hash[assoc['class_name']] ||= []
|
27
|
-
ids << assoc['id']
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def rule_to_scope rule
|
32
|
-
case
|
33
|
-
when ors = rule['or']
|
34
|
-
ors.map{|x| rule_to_scope x}.inject(:or)
|
35
|
-
when ands = rule['and']
|
36
|
-
ands.map{|x| rule_to_scope x}.inject(:merge)
|
37
|
-
when assoc = rule['association']
|
38
|
-
@klass_hash[assoc['class_name']][assoc['id']].association(assoc['source']).scope
|
39
|
-
when cond = rule['condition']
|
40
|
-
scope = @klass
|
41
|
-
scope = scope.joins(cond['joins'].to_sym) if cond['joins']
|
42
|
-
scope.where(cond['where'])
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
@@ -1,10 +0,0 @@
|
|
1
|
-
module SmartCollection
|
2
|
-
class Validator < ActiveModel::Validator
|
3
|
-
def validate record
|
4
|
-
# try to build scope
|
5
|
-
record.association(record.smart_collection_mixin.config.items_name).scope
|
6
|
-
rescue Exception => e
|
7
|
-
record.errors.add(:failed_to_build_scope, e)
|
8
|
-
end
|
9
|
-
end
|
10
|
-
end
|