fume-aloader 0.1.0 → 0.1.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
  SHA256:
3
- metadata.gz: e76d23af085408f974ed12200e21710eb7e41ed838f1e1f4906b28cda813967e
4
- data.tar.gz: f8ca566f229ef016c0f25a47d4018ec2b82fbb2e9c3c04c1a760d297fcf22edc
3
+ metadata.gz: 3d205f2e1934bebb8d922c2d17a5126b4a46d1f25897edbc82be331b3010e255
4
+ data.tar.gz: 37a54264eb5998a64bff6f9e400be6d1d2bcade3b5cc281add1687cbf88f9f0e
5
5
  SHA512:
6
- metadata.gz: 3cced0b48ce3022d0467db55bbc4f6b474546bf3990991d531a479d99914afaa568de687d608dc0f8deccc3cbea338eb0218d65656207b85e935e18f8dcc6f7a
7
- data.tar.gz: d2897f943c2cbceb053a2580190faa83e42fab3700d07999bd1aa18f675238fd0c864ee97168a8686b7b48a3962ad9ba80865048be730fc5bf98af91eb12ba05
6
+ metadata.gz: 1b7192010a01bcc3cdc5ed17b91523d9f3b8135db14f6c7ef2a588bebf928eb83871115d89c548274dd999609f9cf59bd4c027250719c401b1b48ca82a2cdcfd
7
+ data.tar.gz: f4676daf7024f3b81eaa85599f1a2923de222027aefedac1cb9e499850eb87a09d6d7dd41afc0138577a8d2d054d19ae169e7bae909119792fecc851eb14a17b
@@ -1,3 +1,5 @@
1
+ require_relative "relationship"
2
+
1
3
  module Fume::Aloader
2
4
  class AssociationLoader
3
5
  attr_accessor :klass
@@ -20,29 +22,32 @@ module Fume::Aloader
20
22
  end
21
23
 
22
24
  def find_cached_value(record, name)
23
- association = record.association(name)
24
- reflection = association.reflection
25
-
26
- key = reflection.collection? ? record.send(:id) : record.send(reflection.join_foreign_key)
27
-
28
- unless self.cached_values.key?(name)
29
- init_records_value(name)
30
- end
31
-
32
- self.cached_values[name][key]
25
+ relationship = Relationship.build(klass, name)
26
+ cache_key = relationship.get_cache_key(record)
27
+ self.cached_values[name][cache_key]
33
28
  end
34
29
 
35
30
  def load(record, name)
36
31
  association = record.association(name)
37
- if association.loaded? && !self.cached_values.key?(name)
38
- init_records_value(name, ->(scope) {
39
- values = self.records.flat_map(&name.to_sym).compact
40
- scope.send(:load_records, values)
41
- scope.al_init_records
42
- })
32
+ if self.cached_values.key?(name)
33
+ # prefer use cached value
34
+ association.target = find_cached_value(record, name)
35
+ elsif association.loaded?
36
+ ensure_attribute_aloader_inited(record, name)
43
37
  else
44
- value = find_cached_value(record, name)
45
- association.target = value
38
+ init_records_value(name)
39
+ association.target = find_cached_value(record, name)
40
+ end
41
+ end
42
+
43
+ def ensure_attribute_aloader_inited(record, name)
44
+ relationship = Relationship.build(klass, name)
45
+ return if relationship.loader_is_inited?(record)
46
+
47
+ options = active_preset.dig(:attributes, name) || {}
48
+ loaders = relationship.loaders_init(self.records, options[:preset])
49
+ (self.preload_values[name] || []).each do |args|
50
+ loaders.each { |it| it.preload_all(*args) }
46
51
  end
47
52
  end
48
53
 
@@ -51,59 +56,43 @@ module Fume::Aloader
51
56
  name = path.shift
52
57
 
53
58
  if path.size.zero?
54
- fill_records_value(name, values)
59
+ cache_association_values(name, values)
55
60
  else
56
61
  self.preload_values[name] ||= []
57
62
  self.preload_values[name] << [ path, values ]
58
63
  end
59
64
  end
60
65
 
61
- def init_records_value(name, callback = nil)
62
- association = klass.new.association(name)
63
- values = build_association_values_scope(name, association)
64
- callback&.(values)
65
-
66
- if self.preload_values.key?(name)
67
- preload_values[name].each do |args|
68
- values.al_preload_all(*args)
66
+ def init_records_value(name)
67
+ values_list = build_association_values_scopes(name)
68
+ values_list.each do |values|
69
+ if self.preload_values.key?(name)
70
+ preload_values[name].each do |args|
71
+ values.al_preload_all(*args)
72
+ end
69
73
  end
70
- end
71
74
 
72
- fill_records_value(name, values)
75
+ cache_association_values(name, values)
76
+ end
73
77
  end
74
78
 
75
- def fill_records_value(name, values)
76
- association = klass.new.association(name)
77
- reflection = association.reflection
79
+ def cache_association_values(name, values)
80
+ relationship = Relationship.build(klass, name)
78
81
 
79
- if reflection.collection?
80
- self.cached_values[name] = values.each_with_object(Hash.new { [] }) do |it, result|
81
- key = it.send(reflection.join_primary_key)
82
- result[key] += [ it ]
83
- end
84
- elsif reflection.belongs_to?
85
- self.cached_values[name] = values.index_by(&:id)
82
+ if self.cached_values.key?(name)
83
+ self.cached_values[name].update(relationship.build_cached_value(values))
86
84
  else
87
- self.cached_values[name] = values.index_by { |it| it.send(reflection.join_primary_key) }
85
+ self.cached_values[name] = relationship.build_cached_value(values)
88
86
  end
89
87
  end
90
88
 
91
- def build_association_values_scope(name, association)
92
- reflection = association.reflection
93
-
94
- # HACK: 重写第一次取值,升级后可能会报错
95
- # 不能使用子查询 select, 可能内存占用过多
96
- hack_values = [ records.map { |item| item.read_attribute(reflection.join_foreign_key) }.uniq ]
89
+ def build_association_values_scopes(name)
90
+ relationship = Relationship.build(klass, name)
91
+ values_scopes = relationship.build_values_scopes(records)
97
92
 
98
- value_transformation = ->(val) {
99
- hack_values.shift || val
100
- }
101
-
102
- association_scope = ActiveRecord::Associations::AssociationScope.new(value_transformation)
103
- values_scope = association.send(:target_scope).merge(association_scope.scope(association))
104
- values_scope = apply_profile_attribute_includes(values_scope, name)
105
- values_scope = values_scope.limit(nil).offset(0)
106
- values_scope
93
+ values_scopes.map do |values_scope|
94
+ apply_profile_attribute_includes(values_scope, name)
95
+ end
107
96
  end
108
97
 
109
98
  def apply_profile_attribute_includes(base, name)
@@ -114,10 +103,9 @@ module Fume::Aloader
114
103
  return base.al_to_scope(attr_preset_name)
115
104
  end
116
105
 
117
- includes = find_attribute_includes(preset, name) || []
106
+ includes = find_attribute_includes(preset, name, base.klass) || []
118
107
  return base if includes.empty?
119
108
 
120
-
121
109
  except = (self.preload_values[name] || []).map(&:first)
122
110
  columns = simplify_includes_hash(convert_to_includes_hash(includes, [], except))
123
111
 
@@ -145,7 +133,7 @@ module Fume::Aloader
145
133
  result.update convert_to_includes_hash({ name => value }, [], except)
146
134
  end
147
135
  else
148
- includes = find_attribute_includes(preset, root) || {}
136
+ includes = find_attribute_includes(preset, root, nil) || {}
149
137
  result.update convert_to_includes_hash({ root => includes }, [], except)
150
138
  end
151
139
  end
@@ -201,24 +189,18 @@ module Fume::Aloader
201
189
  end
202
190
  end
203
191
 
204
- def find_attribute_includes(preset, name)
192
+ def find_attribute_includes(preset, name, association_klass)
205
193
  attribute = preset.dig(:attributes, name) || {}
206
194
 
207
195
  attr_preset_name = attribute[:preset]
208
196
  return attribute[:scope_includes] || [] if attr_preset_name.nil?
209
197
 
210
- loader = build_attribute_aloader(name, attr_preset_name)
198
+ association_klass ||= klass.new.association(name).reflection.klass
199
+ loader = association_klass.al_build([])
200
+ loader.active(attr_preset_name)
211
201
  loader.build_profile_scope_includes
212
202
  end
213
203
 
214
- def build_attribute_aloader(name, profile)
215
- association = klass.new.association(name)
216
- reflection = association.reflection
217
- loader = reflection.klass.al_build([])
218
- loader.active(profile)
219
- loader
220
- end
221
-
222
204
  def active(name)
223
205
  self.profile = name
224
206
  end
@@ -9,6 +9,11 @@ module Fume::Aloader
9
9
  initializer 'fume-aloader.configure_rails_initialization' do |app|
10
10
  ::ActiveRecord::Base.include(RecordAddons)
11
11
  ::ActiveRecord::Relation.prepend(RelationAddons)
12
+
13
+ # CollectionProxy is inhert from Relation, proxy back to Relation
14
+ ::ActiveRecord::Associations::CollectionProxy.class_eval do
15
+ delegate :al_to_scope, :aloader, to: :scope
16
+ end
12
17
  end
13
18
  end
14
19
  end
@@ -1,6 +1,5 @@
1
1
  module Fume::Aloader
2
2
  module RelationAddons
3
-
4
3
  attr_accessor :aloader
5
4
 
6
5
  def load(*args, &block)
@@ -11,7 +10,7 @@ module Fume::Aloader
11
10
  end
12
11
 
13
12
  def spawn(*args)
14
- result = super
13
+ result = already_in_scope? ? klass.all : clone
15
14
  result.aloader = nil
16
15
  result.al_init_loader
17
16
  result.aloader&.spawn_from(self.aloader) if self.aloader
@@ -47,9 +46,15 @@ module Fume::Aloader
47
46
  end
48
47
 
49
48
  def al_to_scope(preset = :default)
49
+ if self.is_a?(ActiveRecord::Associations::CollectionProxy)
50
+ scope.al_to_scope(preset)
51
+ return
52
+ end
53
+
50
54
  al_init_loader
51
55
  self.aloader.active(preset)
52
- self.aloader.apply_profile_scope_includes(self)
56
+ result = self.aloader.apply_profile_scope_includes(self)
57
+ result
53
58
  end
54
59
  end
55
60
  end
@@ -0,0 +1,30 @@
1
+ module Fume::Aloader
2
+ module Relationship
3
+ class Base
4
+ attr_accessor :klass
5
+ attr_accessor :association
6
+ attr_accessor :reflection
7
+
8
+ def initialize(klass, association, reflection)
9
+ self.klass = klass
10
+ self.association = association
11
+ self.reflection = reflection
12
+ end
13
+
14
+ def build_values_scopes(records)
15
+ # HACK: 重写第一次取值,升级后可能会报错
16
+ # 不能使用子查询 select, 可能内存占用过多
17
+ hack_values = [ records.map { |item| item.read_attribute(reflection.join_foreign_key) }.uniq ]
18
+
19
+ value_transformation = ->(val) {
20
+ hack_values.shift || val
21
+ }
22
+
23
+ association_scope = ActiveRecord::Associations::AssociationScope.new(value_transformation)
24
+ values_scope = association.send(:target_scope).merge(association_scope.scope(association))
25
+ values_scope = values_scope.limit(nil).offset(0)
26
+ [ values_scope ]
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,25 @@
1
+ require_relative "base"
2
+
3
+ module Fume::Aloader
4
+ module Relationship
5
+ class BaseSingle < Base
6
+ def loader_is_inited?(parent)
7
+ value = parent.send(reflection.name)
8
+ value.nil? || !!value.aloader
9
+ end
10
+
11
+ def loaders_init(parents, preset)
12
+ values = parents.map { |it| it.send(reflection.name) }.compact
13
+ return [] if !reflection.klass.respond_to?(:al_build)
14
+ loader = reflection.klass.al_build(values)
15
+
16
+ values.each do |value|
17
+ value.aloader = loader
18
+ end
19
+
20
+ loader.active(preset) if preset
21
+ [ loader ]
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,15 @@
1
+ require_relative "base_single"
2
+
3
+ module Fume::Aloader
4
+ module Relationship
5
+ class BelongsTo < BaseSingle
6
+ def get_cache_key(record)
7
+ record.send(reflection.join_foreign_key)
8
+ end
9
+
10
+ def build_cached_value(values)
11
+ values.index_by(&:id)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,74 @@
1
+ require_relative "base_single"
2
+
3
+ module Fume::Aloader
4
+ module Relationship
5
+ class BelongsToPolymorphic < BaseSingle
6
+ def get_cache_key(record)
7
+ [ record.send(reflection.join_foreign_type), record.send(reflection.join_foreign_key) ]
8
+ end
9
+
10
+ def build_cached_value(values)
11
+ values.index_by { |it| [ it.class.to_s, it.id ] }
12
+ end
13
+
14
+ def build_values_scopes(records)
15
+ values_mapping = records.each_with_object({}).each do |record, hash|
16
+ type = record.read_attribute(reflection.join_foreign_type)
17
+ next if type.nil?
18
+
19
+ value = record.read_attribute(reflection.join_foreign_key)
20
+ if value
21
+ hash[type] ||= Set.new
22
+ hash[type] << value
23
+ end
24
+ end.compact
25
+
26
+ values_mapping.map do |type, values|
27
+ values = [ values.to_a ]
28
+
29
+ # HACK: 重写第一次取值,升级后可能会报错
30
+ # 不能使用子查询 select, 可能内存占用过多
31
+ value_transformation = ->(val) {
32
+ values.shift || val
33
+ }
34
+
35
+ association_scope = ActiveRecord::Associations::AssociationScope.new(value_transformation)
36
+
37
+ association = klass.new(reflection.join_foreign_type => type, reflection.join_foreign_key => -1).association(reflection.name)
38
+ values_scope = association.send(:target_scope).merge(association_scope.scope(association))
39
+ values_scope = values_scope.limit(nil).offset(0)
40
+ values_scope
41
+ end
42
+ end
43
+
44
+ def build_values_mapping(records)
45
+ end
46
+
47
+ def loaders_init(parents, preset)
48
+ values_mapping = parents.each_with_object({}).each do |parent, hash|
49
+ type = parent.read_attribute(reflection.join_foreign_type)
50
+ next if type.nil?
51
+
52
+ value = parent.send(reflection.name)
53
+ if value
54
+ hash[type] ||= Set.new
55
+ hash[type] << value
56
+ end
57
+ end.compact
58
+
59
+ values_mapping.each do |type, values|
60
+ loader_klass = type.constantize
61
+ next if !loader_klass.respond_to?(:al_build)
62
+ loader = loader_klass.al_build(values)
63
+
64
+ values.each do |value|
65
+ value.aloader = loader
66
+ end
67
+
68
+ loader.active(preset) if preset
69
+ loader
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,8 @@
1
+ # require_relative "base"
2
+
3
+ # module Fume::Aloader
4
+ # module Relationship
5
+ # class HasAndBelongsToMany < Base
6
+ # end
7
+ # end
8
+ # end
@@ -0,0 +1,36 @@
1
+ require_relative "base"
2
+
3
+ module Fume::Aloader
4
+ module Relationship
5
+ class HasMany < Base
6
+ def get_cache_key(record)
7
+ record.send(reflection.join_foreign_key)
8
+ end
9
+
10
+ def build_cached_value(values)
11
+ values.each_with_object(Hash.new { [] }) do |it, result|
12
+ key = it.send(reflection.join_primary_key)
13
+ result[key] += [ it ]
14
+ end
15
+ end
16
+
17
+ def loader_is_inited?(parent)
18
+ values = parent.send(reflection.name)
19
+ values.nil? || values.empty? || !!values.first.aloader
20
+ end
21
+
22
+ def loaders_init(parents, preset)
23
+ values = parents.flat_map { |it| it.send(reflection.name) }.compact
24
+ return [] if !reflection.klass.respond_to?(:al_build)
25
+ loader = reflection.klass.al_build(values)
26
+
27
+ values.each do |value|
28
+ value.aloader = loader
29
+ end
30
+
31
+ loader.active(preset) if preset
32
+ [ loader ]
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,15 @@
1
+ require_relative "base_single"
2
+
3
+ module Fume::Aloader
4
+ module Relationship
5
+ class HasOne < BaseSingle
6
+ def get_cache_key(record)
7
+ record.send(reflection.join_foreign_key)
8
+ end
9
+
10
+ def build_cached_value(values)
11
+ values.index_by { |it| it.send(reflection.join_primary_key) }
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,29 @@
1
+ require_relative "relationship/has_many"
2
+ require_relative "relationship/belongs_to"
3
+ require_relative "relationship/belongs_to_polymorphic"
4
+ # require_relative "relationship/has_and_belongs_to_many"
5
+ require_relative "relationship/has_one"
6
+
7
+ module Fume::Aloader
8
+ module Relationship
9
+ def self.build(klass, name)
10
+ association = klass.new.association(name)
11
+ reflection = association.reflection
12
+
13
+ reflection_class = nil
14
+ if reflection.through_reflection?
15
+ raise "Not supported through reflection yet"
16
+ elsif reflection.collection?
17
+ reflection_class = HasMany
18
+ elsif reflection.has_one?
19
+ reflection_class = HasOne
20
+ elsif reflection.polymorphic?
21
+ reflection_class = BelongsToPolymorphic
22
+ else
23
+ reflection_class = BelongsTo
24
+ end
25
+
26
+ reflection_class.new(klass, association, reflection)
27
+ end
28
+ end
29
+ end
@@ -1,5 +1,5 @@
1
1
  module Fume
2
2
  module Aloader
3
- VERSION = "0.1.0"
3
+ VERSION = "0.1.3"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fume-aloader
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - sunteya
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-04-29 00:00:00.000000000 Z
11
+ date: 2022-05-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec-rails
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 5.1.2
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 5.1.2
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: simplecov
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -130,10 +144,7 @@ extensions: []
130
144
  extra_rdoc_files: []
131
145
  files:
132
146
  - ".rspec"
133
- - ".rubocop.yml"
134
- - CHANGELOG.md
135
147
  - Gemfile
136
- - LICENSE
137
148
  - LICENSE.txt
138
149
  - README.md
139
150
  - config.ru
@@ -143,6 +154,14 @@ files:
143
154
  - lib/fume/aloader/railtie.rb
144
155
  - lib/fume/aloader/record_addons.rb
145
156
  - lib/fume/aloader/relation_addons.rb
157
+ - lib/fume/aloader/relationship.rb
158
+ - lib/fume/aloader/relationship/base.rb
159
+ - lib/fume/aloader/relationship/base_single.rb
160
+ - lib/fume/aloader/relationship/belongs_to.rb
161
+ - lib/fume/aloader/relationship/belongs_to_polymorphic.rb
162
+ - lib/fume/aloader/relationship/has_and_belongs_to_many.rb
163
+ - lib/fume/aloader/relationship/has_many.rb
164
+ - lib/fume/aloader/relationship/has_one.rb
146
165
  - lib/fume/aloader/version.rb
147
166
  homepage: https://github.com/sunteya/fume-aloader
148
167
  licenses:
data/.rubocop.yml DELETED
@@ -1,13 +0,0 @@
1
- AllCops:
2
- TargetRubyVersion: 2.6
3
-
4
- Style/StringLiterals:
5
- Enabled: true
6
- EnforcedStyle: double_quotes
7
-
8
- Style/StringLiteralsInInterpolation:
9
- Enabled: true
10
- EnforcedStyle: double_quotes
11
-
12
- Layout/LineLength:
13
- Max: 120
data/CHANGELOG.md DELETED
@@ -1,5 +0,0 @@
1
- ## [Unreleased]
2
-
3
- ## [0.1.0] - 2022-04-27
4
-
5
- - Initial release
data/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2022 sunteya
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.