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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: e48926b0db15bfc7e6c786278522b7f07df46900
4
- data.tar.gz: 6a1daefd7675a7bd97068d4e1298e5737f0273a0
2
+ SHA256:
3
+ metadata.gz: ab885e83fde2cc95d9864c47930a00c074228be053f02a4c115188fa815be2c0
4
+ data.tar.gz: 83f2cec29972fa10282e8aa35a41577afded2dff176f85c4db627c820996069e
5
5
  SHA512:
6
- metadata.gz: 6d9a7c2649df55b559a0f54517ffbca6a237cad2da28da6a254980fc41fd0eb6bb27db13b5eb7c59251fddfea404f9ad24e06595a364f962264fe147ec315528
7
- data.tar.gz: 782412ba48ce5aa99508992857e94490a19602a8b28d546217725cb77b8232875e6b733b191ff09195f889d32919181f3ce3587c167f0b637eefcb770efdc752
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
- raise NotImplementedError
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
- raise NotImplementedError
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
- raise NotImplementedError
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[:items]
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[:class_name]
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
- SmartCollection::ScopeBuilder.new(owner.rule, @config.item_class).build
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 included base
44
- @config = config = SmartCollection::Config.new(@raw_config)
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 cache_manager = config.cache_manager
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
- case
74
- when cache_class == SmartCollection::CacheManager::CacheStore
75
- mixin_options[:preloader] = -> owners {
76
- owners.reject(&:cache_exists?).each(&:update_cache)
77
- loaded = config.cache_manager.read_multi(owners)
78
- records = config.item_class.where(id: loaded.values.flatten.uniq).map{|x| [x.id, x]}.to_h
79
- loaded.map do |owner, ids|
80
- [owner, ids.map{|x| records[x]}]
81
- end.to_h
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
- base.validates_with SmartCollection::Validator
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
@@ -1,3 +1,3 @@
1
1
  module SmartCollection
2
- VERSION = '0.2'
2
+ VERSION = '0.3.1'
3
3
  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: '0.2'
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: 2019-05-26 00:00:00.000000000 Z
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.0
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.0
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
- rubyforge_project:
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