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