smart_collection 0.2.2 → 0.3

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
2
  SHA1:
3
- metadata.gz: 56a4ff13fb4cef1bbf08e862e2711100b2bbb05a
4
- data.tar.gz: 40968d63c36783863e4ed8801d4aaac52361e5a4
3
+ metadata.gz: 7106929dd8e41780fb988dd9aeb82d4b82e115cb
4
+ data.tar.gz: 689310ec89a9971a06ac74df1eaa8f27a7aef3a3
5
5
  SHA512:
6
- metadata.gz: f8041e8ae524fbd3fbcba902a6c89ea1fab57e52eef225550c4503bbb2d86a7c2c2bc747fd1b9d774ecc63b255213c0591ea07ac3606c59003b553d287f56cb7
7
- data.tar.gz: 44ae938e8924e69507bc49de2a91081304fc2b9da2ddebe77a9c1138946d424788f3dce2535825cb1f98ba39398d61e97e161161ebde51d9706fa425d447217d
6
+ metadata.gz: 560c1d306060f3ce682bb362d3c9131541c6e461c577f03d0a800448c6c4b7995204c19429317e26c80a0a3eaa28220e4a6a68622053e2154dac0a85d80d1d39
7
+ data.tar.gz: cb777ac82a0aad678235a4f67dda300d92e44d25ec80c0c370034760d57b7a11e700ab627e6615c888bdf5141d9446f4c8e2af69c8c9ce1bae9e283ce637ff17
@@ -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,27 +15,31 @@ 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,
@@ -61,47 +47,55 @@ module SmartCollection
61
47
  if owner.new_record?
62
48
  uncached_scope(owner)
63
49
  else
64
- if cache_manager = config.cache_manager
65
- unless cache_manager.cache_exists? owner
66
- owner.update_cache
67
- end
68
- cached_scope(owner)
69
- else
70
- uncached_scope(owner)
50
+ cache_manager = config.cache_manager
51
+ unless cache_manager.cache_exists? owner
52
+ owner.update_cache
71
53
  end
54
+ cached_scope(owner)
72
55
  end
73
56
  },
74
57
  type: :collection
75
58
  }
76
59
 
77
- case
78
- when cache_class == SmartCollection::CacheManager::CacheStore
79
- mixin_options[:preloader] = -> owners {
80
- owners.reject(&:cache_exists?).each(&:update_cache)
81
- loaded = config.cache_manager.read_multi(owners)
82
- records = config.item_class.where(id: loaded.values.flatten.uniq).map{|x| [x.id, x]}.to_h
83
- loaded.map do |owner, ids|
84
- [owner, ids.map{|x| records[x]}]
85
- end.to_h
86
- }
87
- when cache_class == SmartCollection::CacheManager::Table
88
- cached_name = "cached_#{config.items_name}".to_sym
89
- mixin_options[:preloader] = -> owners {
90
- owners.reject(&:cache_exists?).each(&:update_cache)
91
- Associationist.preload(owners, cached_items: config.item_name)
92
- owners.map do |owner|
93
- [owner, owner.cached_items.map{|item| item.send(config.item_name)}]
94
- end.to_h
95
- }
96
- else
97
- mixin_options[:preloader] = -> _ {
98
- raise RuntimeError, "Turn on cache to enable preloading."
99
- }
100
- 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
+
101
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
78
+
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
102
90
 
103
- base.validates_with SmartCollection::Validator
91
+ def included base
92
+ @config = config = SmartCollection::Config.new(@raw_config)
93
+
94
+ base.include(InstanceMethods)
95
+ base.extend(ClassMethods)
104
96
 
97
+ define_association base
98
+ define_inverse_association base
105
99
  end
106
100
  end
107
101
  end
@@ -1,3 +1,3 @@
1
1
  module SmartCollection
2
- VERSION = '0.2.2'
2
+ VERSION = '0.3'
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.2
4
+ version: '0.3'
5
5
  platform: ruby
6
6
  authors:
7
7
  - CicholGricenchos
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-06-11 00:00:00.000000000 Z
11
+ date: 2019-07-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: associationist
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.1.1
19
+ version: 0.1.3
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.1.1
26
+ version: 0.1.3
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: pry
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -89,12 +89,9 @@ extra_rdoc_files: []
89
89
  files:
90
90
  - lib/smart_collection.rb
91
91
  - lib/smart_collection/cache_manager.rb
92
- - lib/smart_collection/cache_manager/cache_store.rb
93
- - lib/smart_collection/cache_manager/table.rb
94
92
  - lib/smart_collection/config.rb
93
+ - lib/smart_collection/instance_methods.rb
95
94
  - lib/smart_collection/mixin.rb
96
- - lib/smart_collection/scope_builder.rb
97
- - lib/smart_collection/validator.rb
98
95
  - lib/smart_collection/version.rb
99
96
  homepage: https://github.com/CicholGricenchos/smart_collection
100
97
  licenses:
@@ -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