smart_collection 0.2.2 → 0.3

Sign up to get free protection for your applications and to get access to all the features.
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