ams_lazy_relationships 0.1.5 → 0.2.0
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 +4 -4
- data/CHANGELOG.md +7 -0
- data/Gemfile.lock +1 -1
- data/README.md +18 -0
- data/lib/ams_lazy_relationships/core.rb +3 -1
- data/lib/ams_lazy_relationships/core/evaluation.rb +37 -9
- data/lib/ams_lazy_relationships/core/lazy_dig_method.rb +91 -0
- data/lib/ams_lazy_relationships/core/lazy_relationship_method.rb +1 -9
- data/lib/ams_lazy_relationships/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d299c4294fa21bc85d59919b153f88de9f78a4ed9a2f2d7f87cf98cdc8093706
|
4
|
+
data.tar.gz: 9a66c679744b7e8128ce492209f3743e397cb0f3d1eec93097667c7c0e142863
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2683f61c4107464423359aecb53193f6e5d9de727812916282e7eb6c3848541fc3212d4be773d288044582ef29ba7832d2558fd80e60fed0af2ef833dc2bb858
|
7
|
+
data.tar.gz: c9c777b5d1d56ad21289db010206cc20aa61225eacd9041a29f68745942e72696bee19acfef86eca4d08dba34152ff3e6ba3a1d0833296a0759c91e6203583f2
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
## [v0.2.0](https://github.com/Bajena/ams_lazy_relationships/tree/v0.2.0) (2020-01-11)
|
4
|
+
[Full Changelog](https://github.com/Bajena/ams_lazy_relationships/compare/v0.1.5...v0.2.0)
|
5
|
+
|
6
|
+
**Merged pull requests:**
|
7
|
+
|
8
|
+
- Lazy dig [\#44](https://github.com/Bajena/ams_lazy_relationships/pull/44) ([stokarenko](https://github.com/stokarenko))
|
9
|
+
|
3
10
|
## [v0.1.5](https://github.com/Bajena/ams_lazy_relationships/tree/v0.1.5) (2020-01-08)
|
4
11
|
[Full Changelog](https://github.com/Bajena/ams_lazy_relationships/compare/v0.1.4...v0.1.5)
|
5
12
|
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -163,6 +163,24 @@ class BlogPostSerializer < BaseSerializer
|
|
163
163
|
end
|
164
164
|
```
|
165
165
|
|
166
|
+
#### Example 6: Lazy dig through relationships
|
167
|
+
In additional to previous example you may want to make use of nested lazy relationship without rendering of any nested record.
|
168
|
+
There is an `lazy_dig` method to be used for that:
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
class AuthorSerializer < BaseSerializer
|
172
|
+
lazy_relationship :address
|
173
|
+
end
|
174
|
+
|
175
|
+
class BlogPostSerializer < BaseSerializer
|
176
|
+
lazy_relationship :author
|
177
|
+
|
178
|
+
attribute :author_address do
|
179
|
+
lazy_dig(:author, :address)&.full_address
|
180
|
+
end
|
181
|
+
end
|
182
|
+
```
|
183
|
+
|
166
184
|
## Performance comparison with vanilla AMS
|
167
185
|
|
168
186
|
In general the bigger and more complex your serialized records hierarchy is and the more latency you have in your DB the more you'll benefit from using this gem.
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "ams_lazy_relationships/core/lazy_relationship_method"
|
4
|
+
require "ams_lazy_relationships/core/lazy_dig_method"
|
4
5
|
require "ams_lazy_relationships/core/relationship_wrapper_methods"
|
5
6
|
require "ams_lazy_relationships/core/evaluation"
|
6
7
|
|
@@ -18,6 +19,7 @@ module AmsLazyRelationships::Core
|
|
18
19
|
|
19
20
|
def self.included(klass)
|
20
21
|
klass.send :extend, ClassMethods
|
22
|
+
klass.send :include, LazyDigMethod
|
21
23
|
klass.send :prepend, Initializer
|
22
24
|
|
23
25
|
klass.send(:define_relationship_wrapper_methods)
|
@@ -48,7 +50,7 @@ module AmsLazyRelationships::Core
|
|
48
50
|
def initialize(*)
|
49
51
|
super
|
50
52
|
|
51
|
-
self.class.send(:
|
53
|
+
self.class.send(:init_all_lazy_relationships, object)
|
52
54
|
end
|
53
55
|
end
|
54
56
|
end
|
@@ -8,26 +8,47 @@ module AmsLazyRelationships::Core
|
|
8
8
|
LAZY_NESTING_LEVELS = 3
|
9
9
|
NESTING_START_LEVEL = 1
|
10
10
|
|
11
|
+
# Loads the lazy relationship
|
12
|
+
#
|
13
|
+
# @param relation_name [Symbol] relation name to be loaded
|
14
|
+
# @param object [Object] Lazy relationships will be loaded for this record.
|
15
|
+
def load_lazy_relationship(relation_name, object)
|
16
|
+
lrm = lazy_relationships[relation_name]
|
17
|
+
unless lrm
|
18
|
+
raise ArgumentError, "Undefined lazy '#{relation_name}' relationship for '#{name}' serializer"
|
19
|
+
end
|
20
|
+
|
21
|
+
# We need to evaluate the promise right before serializer tries
|
22
|
+
# to touch it. Otherwise the various side effects can happen:
|
23
|
+
# 1. AMS will attempt to serialize nil values with a specific V1 serializer
|
24
|
+
# 2. `lazy_association ? 'exists' : 'missing'` expression will always
|
25
|
+
# equal to 'exists'
|
26
|
+
# 3. `lazy_association&.id` expression can raise NullPointer exception
|
27
|
+
#
|
28
|
+
# Calling `__sync` will evaluate the promise.
|
29
|
+
init_lazy_relationship(lrm, object).__sync
|
30
|
+
end
|
31
|
+
|
11
32
|
# Recursively loads the tree of lazy relationships
|
12
33
|
# The nesting is limited to 3 levels.
|
13
34
|
#
|
14
35
|
# @param object [Object] Lazy relationships will be loaded for this record.
|
15
36
|
# @param level [Integer] Current nesting level
|
16
|
-
def
|
37
|
+
def init_all_lazy_relationships(object, level = NESTING_START_LEVEL)
|
17
38
|
return if level >= LAZY_NESTING_LEVELS
|
18
39
|
return unless object
|
19
40
|
|
20
41
|
return unless lazy_relationships
|
21
42
|
|
22
43
|
lazy_relationships.each_value do |lrm|
|
23
|
-
|
44
|
+
init_lazy_relationship(lrm, object, level)
|
24
45
|
end
|
25
46
|
end
|
26
47
|
|
27
48
|
# @param lrm [LazyRelationshipMeta] relationship data
|
28
49
|
# @param object [Object] Object to load the relationship for
|
29
50
|
# @param level [Integer] Current nesting level
|
30
|
-
def
|
51
|
+
def init_lazy_relationship(lrm, object, level = NESTING_START_LEVEL)
|
31
52
|
load_for_object = if lrm.load_for.present?
|
32
53
|
object.public_send(lrm.load_for)
|
33
54
|
else
|
@@ -35,7 +56,7 @@ module AmsLazyRelationships::Core
|
|
35
56
|
end
|
36
57
|
|
37
58
|
lrm.loader.load(load_for_object) do |batch_records|
|
38
|
-
|
59
|
+
deep_init_for_yielded_records(
|
39
60
|
batch_records,
|
40
61
|
lrm,
|
41
62
|
level
|
@@ -43,21 +64,28 @@ module AmsLazyRelationships::Core
|
|
43
64
|
end
|
44
65
|
end
|
45
66
|
|
46
|
-
def
|
67
|
+
def deep_init_for_yielded_records(batch_records, lrm, level)
|
47
68
|
# There'll be no more nesting if there's no
|
48
69
|
# reflection for this relationship. We can skip deeper lazy loading.
|
49
70
|
return unless lrm.reflection
|
50
71
|
|
51
72
|
Array.wrap(batch_records).each do |r|
|
52
|
-
|
73
|
+
deep_init_for_yielded_record(r, lrm, level)
|
53
74
|
end
|
54
75
|
end
|
55
76
|
|
56
|
-
def
|
57
|
-
serializer =
|
77
|
+
def deep_init_for_yielded_record(batch_record, lrm, level)
|
78
|
+
serializer = lazy_serializer_for(batch_record, lrm: lrm)
|
58
79
|
return unless serializer
|
59
80
|
|
60
|
-
serializer.send(:
|
81
|
+
serializer.send(:init_all_lazy_relationships, batch_record, level + 1)
|
82
|
+
end
|
83
|
+
|
84
|
+
def lazy_serializer_for(object, lrm: nil, relation_name: nil)
|
85
|
+
lrm ||= lazy_relationships[relation_name]
|
86
|
+
return unless lrm&.reflection
|
87
|
+
|
88
|
+
serializer_for(object, lrm.reflection.options)
|
61
89
|
end
|
62
90
|
end
|
63
91
|
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AmsLazyRelationships::Core
|
4
|
+
# Provides `lazy_dig` as an instance method for serializers, in order to make
|
5
|
+
# possible to dig relationships in depth just like `Hash#dig` do, keeping the
|
6
|
+
# laziness and N+1-free evaluation.
|
7
|
+
module LazyDigMethod
|
8
|
+
# @param relation_names [Array<Symbol>] the sequence of relation names
|
9
|
+
# to dig through.
|
10
|
+
# @return [ActiveRecord::Base, Array<ActiveRecord::Base>, nil] ActiveRecord
|
11
|
+
# objects found by digging through the sequence of nested relationships.
|
12
|
+
# Singular or plural nature of returned value depends from the
|
13
|
+
# singular/plural nature of the chain of relation_names.
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# class AuthorSerializer < BaseSerializer
|
17
|
+
# lazy_belongs_to :address
|
18
|
+
# lazy_has_many :rewards
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# class BlogPostSerializer < BaseSerializer
|
22
|
+
# lazy_belongs_to :author
|
23
|
+
#
|
24
|
+
# attribute :author_address do
|
25
|
+
# # returns single AR object or nil
|
26
|
+
# lazy_dig(:author, :address)&.full_address
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# attribute :author_rewards do
|
30
|
+
# # returns an array of AR objects
|
31
|
+
# lazy_dig(:author, :rewards).map(&:description)
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
def lazy_dig(*relation_names)
|
35
|
+
relationships = {
|
36
|
+
multiple: false,
|
37
|
+
data: [{
|
38
|
+
serializer: self.class,
|
39
|
+
object: object
|
40
|
+
}]
|
41
|
+
}
|
42
|
+
|
43
|
+
relation_names.each do |relation_name|
|
44
|
+
lazy_dig_relationship!(relation_name, relationships)
|
45
|
+
end
|
46
|
+
|
47
|
+
objects = relationships[:data].map { |r| r[:object] }
|
48
|
+
|
49
|
+
relationships[:multiple] ? objects : objects.first
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def lazy_dig_relationship!(relation_name, relationships)
|
55
|
+
relationships[:data].map! do |serializer:, object:|
|
56
|
+
next_objects = lazy_dig_next_objects!(relation_name, serializer, object)
|
57
|
+
next unless next_objects
|
58
|
+
|
59
|
+
relationships[:multiple] ||= next_objects.respond_to?(:to_ary)
|
60
|
+
|
61
|
+
lazy_dig_next_relationships!(relation_name, serializer, next_objects)
|
62
|
+
end
|
63
|
+
|
64
|
+
relationships[:data].flatten!
|
65
|
+
relationships[:data].compact!
|
66
|
+
end
|
67
|
+
|
68
|
+
def lazy_dig_next_objects!(relation_name, serializer, object)
|
69
|
+
serializer&.send(
|
70
|
+
:load_lazy_relationship,
|
71
|
+
relation_name,
|
72
|
+
object
|
73
|
+
)
|
74
|
+
end
|
75
|
+
|
76
|
+
def lazy_dig_next_relationships!(relation_name, serializer, next_objects)
|
77
|
+
Array.wrap(next_objects).map do |next_object|
|
78
|
+
next_serializer = serializer.send(
|
79
|
+
:lazy_serializer_for,
|
80
|
+
next_object,
|
81
|
+
relation_name: relation_name
|
82
|
+
)
|
83
|
+
|
84
|
+
{
|
85
|
+
serializer: next_serializer,
|
86
|
+
object: next_object
|
87
|
+
}
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -39,15 +39,7 @@ module AmsLazyRelationships::Core
|
|
39
39
|
@lazy_relationships[name] = lrm
|
40
40
|
|
41
41
|
define_method :"lazy_#{name}" do
|
42
|
-
|
43
|
-
# to touch it. Otherwise the various side effects can happen:
|
44
|
-
# 1. AMS will attempt to serialize nil values with a specific V1 serializer
|
45
|
-
# 2. `lazy_association ? 'exists' : 'missing'` expression will always
|
46
|
-
# equal to 'exists'
|
47
|
-
# 3. `lazy_association&.id` expression can raise NullPointer exception
|
48
|
-
#
|
49
|
-
# Calling `__sync` will evaluate the promise.
|
50
|
-
self.class.send(:load_lazy_relationship, lrm, object).__sync
|
42
|
+
self.class.send(:load_lazy_relationship, name, object)
|
51
43
|
end
|
52
44
|
end
|
53
45
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ams_lazy_relationships
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jan Bajena
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-01-
|
11
|
+
date: 2020-01-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: active_model_serializers
|
@@ -306,6 +306,7 @@ files:
|
|
306
306
|
- lib/ams_lazy_relationships.rb
|
307
307
|
- lib/ams_lazy_relationships/core.rb
|
308
308
|
- lib/ams_lazy_relationships/core/evaluation.rb
|
309
|
+
- lib/ams_lazy_relationships/core/lazy_dig_method.rb
|
309
310
|
- lib/ams_lazy_relationships/core/lazy_relationship_meta.rb
|
310
311
|
- lib/ams_lazy_relationships/core/lazy_relationship_method.rb
|
311
312
|
- lib/ams_lazy_relationships/core/relationship_wrapper_methods.rb
|