smart_collection 0.1.1 → 0.2

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: 187bb48a7b917cc0b0f8e068fb48f32a559d9ffb
4
- data.tar.gz: fb246fd8dd0ca7c3cd1a8ed01d57a699dfa9433b
3
+ metadata.gz: e48926b0db15bfc7e6c786278522b7f07df46900
4
+ data.tar.gz: 6a1daefd7675a7bd97068d4e1298e5737f0273a0
5
5
  SHA512:
6
- metadata.gz: de0fdf1c0a01d34314a4e6d6665ef945dbb195ef728519ef8a874f7b4961c9c4547679245ce7eae2b0ca999bccf939ee715404c9c05a8b385ec9b74944a4b3f7
7
- data.tar.gz: 85aa4af37c448c96684b30a9764b677fd62f9de0de6d152cc81501ce40cf2aa7920695e7e29e5407236e2a5e87ad72ff5e02c88293c649b506293124433e07d1
6
+ metadata.gz: 6d9a7c2649df55b559a0f54517ffbca6a237cad2da28da6a254980fc41fd0eb6bb27db13b5eb7c59251fddfea404f9ad24e06595a364f962264fe147ec315528
7
+ data.tar.gz: 782412ba48ce5aa99508992857e94490a19602a8b28d546217725cb77b8232875e6b733b191ff09195f889d32919181f3ce3587c167f0b637eefcb770efdc752
@@ -17,9 +17,7 @@ module SmartCollection
17
17
  end
18
18
 
19
19
  def update owner
20
- association = owner.association(@config.items_name)
21
-
22
- cache_store.write(cache_key(owner), Marshal.dump(association.uncached_scope.pluck(:id)))
20
+ cache_store.write(cache_key(owner), Marshal.dump(owner.smart_collection_mixin.uncached_scope(owner).pluck(:id)))
23
21
  owner.update(cache_expires_at: Time.now + expires_in)
24
22
  end
25
23
 
@@ -25,10 +25,8 @@ module SmartCollection
25
25
  end
26
26
 
27
27
  def update owner
28
- association = owner.association(@config.items_name)
29
-
30
28
  @cache_model.where(collection_id: owner.id).delete_all
31
- @cache_model.connection.execute "INSERT INTO #{@cache_model.table_name} (collection_id, item_id) #{association.uncached_scope.select(owner.id, :id).to_sql}"
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}"
32
30
  owner.update_column(:cache_expires_at, Time.now + expires_in)
33
31
  end
34
32
 
@@ -32,6 +32,14 @@ module SmartCollection
32
32
  @raw_config = raw_config
33
33
  end
34
34
 
35
+ def uncached_scope owner
36
+ SmartCollection::ScopeBuilder.new(owner.rule, @config.item_class).build
37
+ end
38
+
39
+ def cached_scope owner
40
+ @config.cache_manager.read_scope owner
41
+ end
42
+
35
43
  def included base
36
44
  @config = config = SmartCollection::Config.new(@raw_config)
37
45
 
@@ -40,9 +48,6 @@ module SmartCollection
40
48
  reflection_options[:class_name] = config.item_class_name
41
49
  end
42
50
 
43
- reflection = Builder::SmartCollectionAssociation.build(base, config.items_name, nil, reflection_options)
44
- ::ActiveRecord::Reflection.add_reflection base, config.items_name, reflection
45
-
46
51
  base.include(InstanceMethods)
47
52
  base.extend(ClassMethods)
48
53
 
@@ -50,6 +55,47 @@ module SmartCollection
50
55
  config.cache_manager = cache_class.new(model: base, config: config)
51
56
  end
52
57
 
58
+ mixin_options = {
59
+ name: config.items_name,
60
+ scope: -> owner {
61
+ if cache_manager = config.cache_manager
62
+ unless cache_manager.cache_exists? owner
63
+ owner.update_cache
64
+ end
65
+ cached_scope(owner)
66
+ else
67
+ uncached_scope(owner)
68
+ end
69
+ },
70
+ type: :collection
71
+ }
72
+
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
97
+ base.include Associationist::Mixin.new(mixin_options)
98
+
53
99
  base.validates_with SmartCollection::Validator
54
100
 
55
101
  end
@@ -0,0 +1,46 @@
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,3 +1,3 @@
1
1
  module SmartCollection
2
- VERSION = '0.1.1'
2
+ VERSION = '0.2'
3
3
  end
@@ -1,4 +1,4 @@
1
- require 'active_record'
1
+ require 'associationist'
2
2
 
3
3
  Dir.glob("#{File.dirname(__FILE__)}/**/*.rb").each do |file|
4
4
  require file
metadata CHANGED
@@ -1,35 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smart_collection
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: '0.2'
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-23 00:00:00.000000000 Z
11
+ date: 2019-05-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: activerecord
14
+ name: associationist
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '5.0'
20
- - - "<"
17
+ - - "~>"
21
18
  - !ruby/object:Gem::Version
22
- version: '6.0'
19
+ version: 0.1.0
23
20
  type: :runtime
24
21
  prerelease: false
25
22
  version_requirements: !ruby/object:Gem::Requirement
26
23
  requirements:
27
- - - ">="
28
- - !ruby/object:Gem::Version
29
- version: '5.0'
30
- - - "<"
24
+ - - "~>"
31
25
  - !ruby/object:Gem::Version
32
- version: '6.0'
26
+ version: 0.1.0
33
27
  - !ruby/object:Gem::Dependency
34
28
  name: pry
35
29
  requirement: !ruby/object:Gem::Requirement
@@ -94,15 +88,12 @@ extensions: []
94
88
  extra_rdoc_files: []
95
89
  files:
96
90
  - lib/smart_collection.rb
97
- - lib/smart_collection/associations/preloader/smart_collection.rb
98
- - lib/smart_collection/associations/smart_collection_association.rb
99
- - lib/smart_collection/builder/smart_collection_association.rb
100
91
  - lib/smart_collection/cache_manager.rb
101
92
  - lib/smart_collection/cache_manager/cache_store.rb
102
93
  - lib/smart_collection/cache_manager/table.rb
103
94
  - lib/smart_collection/config.rb
104
95
  - lib/smart_collection/mixin.rb
105
- - lib/smart_collection/reflection/smart_collection_reflection.rb
96
+ - lib/smart_collection/scope_builder.rb
106
97
  - lib/smart_collection/validator.rb
107
98
  - lib/smart_collection/version.rb
108
99
  homepage: https://github.com/CicholGricenchos/smart_collection
@@ -1,54 +0,0 @@
1
- module SmartCollection
2
- module Associations
3
- module Preloader
4
- class SmartCollectionCachedByTable < ActiveRecord::Associations::Preloader::HasManyThrough
5
- def through_reflection
6
- reflection.active_record.reflect_on_association(:cached_items)
7
- end
8
-
9
- def source_reflection
10
- association_name = reflection.options[:smart_collection].item_name
11
- through_reflection.klass.reflect_on_association(association_name)
12
- end
13
-
14
- def associated_records_by_owner preloader
15
- owners.reject(&:cache_exists?).each(&:update_cache)
16
- super
17
- end
18
- end
19
-
20
- class SmartCollectionCachedByCacheStore < ActiveRecord::Associations::Preloader::CollectionAssociation
21
- def associated_records_by_owner preloader
22
- owners.reject(&:cache_exists?).each(&:update_cache)
23
- config = reflection.options[:smart_collection]
24
- loaded = config.cache_manager.read_multi(owners)
25
- records = config.item_class.where(id: loaded.values.flatten.uniq).map{|x| [x.id, x]}.to_h
26
- loaded.map do |owner, ids|
27
- [owner, ids.map{|x| records[x]}]
28
- end
29
- end
30
- end
31
-
32
- end
33
- end
34
-
35
- module ActiveRecordPreloaderPatch
36
- def preloader_for(reflection, owners, rhs_klass)
37
- config = reflection.options[:smart_collection]
38
- if config
39
- case config.cache_manager
40
- when SmartCollection::CacheManager::CacheStore
41
- SmartCollection::Associations::Preloader::SmartCollectionCachedByCacheStore
42
- when SmartCollection::CacheManager::Table
43
- SmartCollection::Associations::Preloader::SmartCollectionCachedByTable
44
- else
45
- raise RuntimeError, "Turn on cache to enable preloading."
46
- end
47
- else
48
- super
49
- end
50
- end
51
- end
52
-
53
- ActiveRecord::Associations::Preloader.prepend ActiveRecordPreloaderPatch
54
- end
@@ -1,75 +0,0 @@
1
- module SmartCollection
2
- module Associations
3
- class SmartCollectionAssociation < ::ActiveRecord::Associations::HasManyAssociation
4
-
5
- class ScopeBuilder
6
- def initialize rule, klass
7
- @rule = rule
8
- @klass = klass
9
- @klass_hash = {}
10
- end
11
-
12
- def build
13
- rule_to_bulk_queries @rule
14
- bulk_load
15
- rule_to_scope @rule
16
- end
17
-
18
- def bulk_load
19
- @klass_hash = @klass_hash.map do |klass_name, ids|
20
- [klass_name, Object.const_get(klass_name).where(id: ids).map{|x| [x.id, x]}.to_h]
21
- end.to_h
22
- end
23
-
24
- def rule_to_bulk_queries rule
25
- case
26
- when arr = (rule['or'] || rule['and'])
27
- arr.each{|x| rule_to_bulk_queries x}
28
- when assoc = rule['association']
29
- ids = @klass_hash[assoc['class_name']] ||= []
30
- ids << assoc['id']
31
- end
32
- end
33
-
34
- def rule_to_scope rule
35
- case
36
- when ors = rule['or']
37
- ors.map{|x| rule_to_scope x}.inject(:or)
38
- when ands = rule['and']
39
- ands.map{|x| rule_to_scope x}.inject(:merge)
40
- when assoc = rule['association']
41
- @klass_hash[assoc['class_name']][assoc['id']].association(assoc['source']).scope
42
- when cond = rule['condition']
43
- scope = @klass
44
- scope = scope.joins(cond['joins'].to_sym) if cond['joins']
45
- scope.where(cond['where'])
46
- end
47
- end
48
- end
49
-
50
- def association_scope
51
- if cache_manager = reflection.options[:smart_collection].cache_manager
52
- unless cache_manager.cache_exists? owner
53
- owner.update_cache
54
- end
55
- cached_scope
56
- else
57
- uncached_scope
58
- end
59
- end
60
-
61
- def skip_statement_cache?
62
- true
63
- end
64
-
65
- def uncached_scope
66
- ScopeBuilder.new(owner.rule, reflection.klass).build
67
- end
68
-
69
- def cached_scope
70
- reflection.options[:smart_collection].cache_manager.read_scope owner
71
- end
72
-
73
- end
74
- end
75
- end
@@ -1,26 +0,0 @@
1
- module SmartCollection
2
- module Builder
3
- class SmartCollectionAssociation < ::ActiveRecord::Associations::Builder::CollectionAssociation
4
- def self.macro
5
- :has_many
6
- end
7
-
8
- def self.valid_options(options)
9
- super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache, :join_table, :foreign_type, :index_errors, :smart_collection]
10
- end
11
-
12
- def self.valid_dependent_options
13
- [:destroy, :delete_all, :nullify, :restrict_with_error, :restrict_with_exception]
14
- end
15
-
16
- def self.create_reflection(model, name, scope, options, extension = nil)
17
- raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol)
18
-
19
- validate_options(options)
20
-
21
- scope = build_scope(scope, extension)
22
- Reflection::SmartCollectionReflection.new(name, scope, options, model)
23
- end
24
- end
25
- end
26
- end
@@ -1,20 +0,0 @@
1
- module SmartCollection
2
- module Reflection
3
- class SmartCollectionReflection < ::ActiveRecord::Reflection::HasManyReflection
4
- def association_class
5
- Associations::SmartCollectionAssociation
6
- end
7
-
8
- def check_eager_loadable!
9
- unless options[:smart_collection].cache_manager.instance_of? SmartCollection::CacheManager::Table
10
- raise RuntimeError, 'eager_load is only supported when using table for cache.'
11
- end
12
- end
13
-
14
- def chain
15
- items_name = options[:smart_collection].items_name
16
- active_record.reflect_on_association("cached_#{items_name}".to_sym).chain
17
- end
18
- end
19
- end
20
- end