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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1ec3f81fd1d54428b4881ba8ec0631dbf2587d0b136f9fe9baecd29b9ac9d627
4
- data.tar.gz: 9e7f106d42380915cfa2c0a475be0403951a8c632675d3cd5b4d8b3b0e25ec37
3
+ metadata.gz: d299c4294fa21bc85d59919b153f88de9f78a4ed9a2f2d7f87cf98cdc8093706
4
+ data.tar.gz: 9a66c679744b7e8128ce492209f3743e397cb0f3d1eec93097667c7c0e142863
5
5
  SHA512:
6
- metadata.gz: ae977d3dcbde64175dd581ac341fa7db034dd7347ebd486d5c0b09808215cf7742ad8126cc6ca6c946406453d44a32125805488c6131ef981b777e40bdac0e18
7
- data.tar.gz: 4a772034ef0eaa1aaf8d50fde4452e1a5e0ded1ddf00571838c159950e6a23e3185e5ff5c5c427def22909744e83928ebf6a204f52973c34047a025442892d6f
6
+ metadata.gz: 2683f61c4107464423359aecb53193f6e5d9de727812916282e7eb6c3848541fc3212d4be773d288044582ef29ba7832d2558fd80e60fed0af2ef833dc2bb858
7
+ data.tar.gz: c9c777b5d1d56ad21289db010206cc20aa61225eacd9041a29f68745942e72696bee19acfef86eca4d08dba34152ff3e6ba3a1d0833296a0759c91e6203583f2
@@ -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
 
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ams_lazy_relationships (0.1.5)
4
+ ams_lazy_relationships (0.2.0)
5
5
  active_model_serializers
6
6
  batch-loader (~> 1)
7
7
 
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(:load_all_lazy_relationships, object)
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 load_all_lazy_relationships(object, level = NESTING_START_LEVEL)
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
- load_lazy_relationship(lrm, object, level)
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 load_lazy_relationship(lrm, object, level = NESTING_START_LEVEL)
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
- deep_load_for_yielded_records(
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 deep_load_for_yielded_records(batch_records, lrm, level)
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
- deep_load_for_yielded_record(r, lrm, level)
73
+ deep_init_for_yielded_record(r, lrm, level)
53
74
  end
54
75
  end
55
76
 
56
- def deep_load_for_yielded_record(batch_record, lrm, level)
57
- serializer = serializer_for(batch_record, lrm.reflection.options)
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(:load_all_lazy_relationships, batch_record, level + 1)
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
- # We need to evaluate the promise right before serializer tries
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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AmsLazyRelationships
4
- VERSION = "0.1.5"
4
+ VERSION = "0.2.0"
5
5
  end
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.1.5
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-08 00:00:00.000000000 Z
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