serega 0.10.0 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +319 -141
  3. data/VERSION +1 -1
  4. data/lib/serega/attribute.rb +37 -105
  5. data/lib/serega/attribute_normalizer.rb +176 -0
  6. data/lib/serega/config.rb +26 -9
  7. data/lib/serega/object_serializer.rb +11 -10
  8. data/lib/serega/plan.rb +128 -0
  9. data/lib/serega/plan_point.rb +94 -0
  10. data/lib/serega/plugins/batch/batch.rb +187 -41
  11. data/lib/serega/plugins/batch/lib/loader.rb +4 -4
  12. data/lib/serega/plugins/batch/lib/loaders.rb +3 -3
  13. data/lib/serega/plugins/batch/lib/plugins_extensions/activerecord_preloads.rb +2 -2
  14. data/lib/serega/plugins/batch/lib/plugins_extensions/formatters.rb +19 -1
  15. data/lib/serega/plugins/batch/lib/plugins_extensions/preloads.rb +6 -4
  16. data/lib/serega/plugins/batch/lib/validations/check_opt_batch.rb +9 -5
  17. data/lib/serega/plugins/formatters/formatters.rb +28 -31
  18. data/lib/serega/plugins/if/if.rb +24 -6
  19. data/lib/serega/plugins/preloads/lib/format_user_preloads.rb +11 -13
  20. data/lib/serega/plugins/preloads/lib/main_preload_path.rb +2 -2
  21. data/lib/serega/plugins/preloads/lib/preloads_constructor.rb +21 -15
  22. data/lib/serega/plugins/preloads/preloads.rb +72 -40
  23. data/lib/serega/plugins/presenter/presenter.rb +0 -9
  24. data/lib/serega/plugins/string_modifiers/parse_string_modifiers.rb +11 -1
  25. data/lib/serega/plugins.rb +3 -3
  26. data/lib/serega/utils/enum_deep_dup.rb +18 -18
  27. data/lib/serega/utils/enum_deep_freeze.rb +35 -0
  28. data/lib/serega/utils/to_hash.rb +17 -9
  29. data/lib/serega.rb +22 -15
  30. metadata +6 -6
  31. data/lib/serega/map.rb +0 -91
  32. data/lib/serega/map_point.rb +0 -66
  33. data/lib/serega/plugins/batch/lib/batch_option_model.rb +0 -73
  34. data/lib/serega/plugins/preloads/lib/enum_deep_freeze.rb +0 -26
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.10.0
1
+ 0.11.0
@@ -9,98 +9,62 @@ class Serega
9
9
  # Attribute instance methods
10
10
  #
11
11
  module AttributeInstanceMethods
12
- # Returns attribute name
12
+ # Attribute initial params
13
+ # @return [Hash] Attribute initial params
14
+ attr_reader :initials
15
+
16
+ # Attribute name
13
17
  # @return [Symbol] Attribute name
14
18
  attr_reader :name
15
19
 
16
- # Returns attribute options
17
- # @return [Hash] Attribute options
18
- attr_reader :opts
20
+ # Attribute :many option
21
+ # @return [Boolean, nil] Attribute :many option
22
+ attr_reader :many
19
23
 
20
- # Returns attribute block
21
- # @return [Proc] Attribute originally added block
22
- attr_reader :block
24
+ # Attribute :hide option
25
+ # @return [Boolean nil] Attribute :hide option
26
+ attr_reader :hide
23
27
 
24
28
  #
25
29
  # Initializes new attribute
26
30
  #
27
31
  # @param name [Symbol, String] Name of attribute
28
- #
29
32
  # @param opts [Hash] Attribute options
30
- # @option opts [Symbol] :key Object instance method name to get attribute value
31
- # @option opts [Boolean] :exposed Configures if we should serialize this attribute by default.
32
- # (by default is true for regular attributes and false for relationships)
33
+ # @option opts [Symbol] :key Object method name to fetch attribute value
34
+ # @option opts [Hash] :delegate Allows to fetch value from nested object
35
+ # @option opts [Boolean] :hide Specify `true` to not serialize this attribute by default
33
36
  # @option opts [Boolean] :many Specifies has_many relationship. By default is detected via object.is_a?(Enumerable)
34
- # @option opts [Serega, Proc] :serializer Relationship serializer class. Use `proc { MySerializer }` if serializers have cross references.
35
- #
36
- # @param block [Proc] Proc that receives object and context and finds attribute value
37
+ # @option opts [Proc, #call] :value Custom block or callable to find attribute value
38
+ # @option opts [Serega, Proc] :serializer Relationship serializer class. Use `proc { MySerializer }` if serializers have cross references
39
+ # @param block [Proc] Custom block to find attribute value
37
40
  #
38
41
  def initialize(name:, opts: {}, block: nil)
39
- self.class.serializer_class::CheckAttributeParams.new(name, opts, block).validate
40
-
41
- @name = name.to_sym
42
- @opts = SeregaUtils::EnumDeepDup.call(opts)
43
- @block = block
44
- end
42
+ serializer_class = self.class.serializer_class
43
+ serializer_class::CheckAttributeParams.new(name, opts, block).validate
45
44
 
46
- # Method name that will be used to get attribute value
47
- #
48
- # @return [Symbol] key
49
- def key
50
- @key ||= opts.key?(:key) ? opts[:key].to_sym : name
51
- end
52
-
53
- # Shows current opts[:hide] option
54
- #
55
- # Patched in:
56
- # - plugin :preloads (returns true by default if config option auto_hide_attribute_with_preloads is enabled)
57
- #
58
- # @return [Boolean, nil] Attribute :hide option value
59
- def hide
60
- opts[:hide]
61
- end
45
+ @initials = SeregaUtils::EnumDeepFreeze.call(
46
+ name: name,
47
+ opts: SeregaUtils::EnumDeepDup.call(opts),
48
+ block: block
49
+ )
62
50
 
63
- # Shows current opts[:many] option
64
- # @return [Boolean, nil] Attribute :many option value
65
- def many
66
- opts[:many]
51
+ normalizer = serializer_class::SeregaAttributeNormalizer.new(initials)
52
+ set_normalized_vars(normalizer)
67
53
  end
68
54
 
69
55
  # Shows whether attribute has specified serializer
70
56
  # @return [Boolean] Checks if attribute is relationship (if :serializer option exists)
71
57
  def relation?
72
- !opts[:serializer].nil?
58
+ !@serializer.nil?
73
59
  end
74
60
 
75
61
  # Shows specified serializer class
76
62
  # @return [Serega, nil] Attribute serializer if exists
77
63
  def serializer
78
- return @serializer if instance_variable_defined?(:@serializer)
79
-
80
- serializer = opts[:serializer]
81
- @serializer =
82
- case serializer
83
- when String then Object.const_get(serializer, false)
84
- when Proc then serializer.call
85
- else serializer
86
- end
87
- end
64
+ ser = @serializer
65
+ return ser if (ser.is_a?(Class) && (ser < Serega)) || !ser
88
66
 
89
- # Returns final block used to find attribute value
90
- #
91
- # Patched in:
92
- # - plugin :formatters (wraps resulted block in formatter block and formats :const values)
93
- #
94
- # @return [Proc] Proc to find attribute value
95
- def value_block
96
- return @value_block if instance_variable_defined?(:@value_block)
97
-
98
- @value_block =
99
- block ||
100
- opts[:value] ||
101
- const_block ||
102
- delegate_block ||
103
- keyword_block
67
+ @serializer = ser.is_a?(String) ? Object.const_get(ser, false) : ser.call
104
68
  end
105
69
 
106
70
  #
@@ -135,46 +99,14 @@ class Serega
135
99
 
136
100
  private
137
101
 
138
- def const_block
139
- return unless opts.key?(:const)
140
-
141
- const = opts[:const]
142
- proc { const }
143
- end
144
-
145
- def keyword_block
146
- key_method_name = key
147
- proc do |object|
148
- handle_no_method_error { object.public_send(key_method_name) }
149
- end
150
- end
151
-
152
- def delegate_block
153
- delegate = opts[:delegate]
154
- return unless delegate
155
-
156
- key_method_name = delegate[:key] || key
157
- delegate_to = delegate[:to]
158
-
159
- if delegate[:allow_nil]
160
- proc do |object|
161
- handle_no_method_error do
162
- object.public_send(delegate_to)&.public_send(key_method_name)
163
- end
164
- end
165
- else
166
- proc do |object|
167
- handle_no_method_error do
168
- object.public_send(delegate_to).public_send(key_method_name)
169
- end
170
- end
171
- end
172
- end
102
+ attr_reader :value_block
173
103
 
174
- def handle_no_method_error
175
- yield
176
- rescue NoMethodError => error
177
- raise error, "NoMethodError when serializing '#{name}' attribute in #{self.class.serializer_class}\n\n#{error.message}", error.backtrace
104
+ def set_normalized_vars(normalizer)
105
+ @name = normalizer.name
106
+ @value_block = normalizer.value_block
107
+ @hide = normalizer.hide
108
+ @many = normalizer.many
109
+ @serializer = normalizer.serializer
178
110
  end
179
111
  end
180
112
 
@@ -0,0 +1,176 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ #
5
+ # Prepares provided attribute options
6
+ #
7
+ class SeregaAttributeNormalizer
8
+ #
9
+ # AttributeNormalizer instance methods
10
+ #
11
+ module AttributeNormalizerInstanceMethods
12
+ # Attribute initial params
13
+ # @return [Hash] Attribute initial params
14
+ attr_reader :init_name, :init_opts, :init_block
15
+
16
+ #
17
+ # Instantiates attribute options normalizer
18
+ #
19
+ # @param initials [Hash] new attribute options
20
+ #
21
+ # @return [SeregaAttributeNormalizer] Instantiated attribute options normalizer
22
+ #
23
+ def initialize(initials)
24
+ @init_name = initials[:name]
25
+ @init_opts = initials[:opts]
26
+ @init_block = initials[:block]
27
+ end
28
+
29
+ #
30
+ # Symbolized initial attribute name
31
+ #
32
+ # @return [Symbol] Attribute normalized name
33
+ #
34
+ def name
35
+ @name ||= prepare_name
36
+ end
37
+
38
+ #
39
+ # Symbolized initial attribute key or attribute name if key is empty
40
+ #
41
+ # @return [Symbol] Attribute normalized name
42
+ #
43
+ def key
44
+ @key ||= prepare_key
45
+ end
46
+
47
+ #
48
+ # Combines all options to return single block that will be used to find
49
+ # attribute value during serialization
50
+ #
51
+ # @return [#call] Attribute normalized callable value block
52
+ #
53
+ def value_block
54
+ @value_block ||= prepare_value_block
55
+ end
56
+
57
+ #
58
+ # Shows if attribute is specified to be hidden
59
+ #
60
+ # @return [Boolean, nil] if attribute must be hidden by default
61
+ #
62
+ def hide
63
+ return @hide if instance_variable_defined?(:@hide)
64
+
65
+ @hide = prepare_hide
66
+ end
67
+
68
+ #
69
+ # Shows if attribute is specified to be a one-to-many relationship
70
+ #
71
+ # @return [Boolean, nil] if attribute is specified to be a one-to-many relationship
72
+ #
73
+ def many
74
+ return @many if instance_variable_defined?(:@many)
75
+
76
+ @many = prepare_many
77
+ end
78
+
79
+ #
80
+ # Shows specified attribute serializer
81
+ # @return [Serega, String, #callable, nil] specified serializer
82
+ #
83
+ def serializer
84
+ return @serializer if instance_variable_defined?(:@serializer)
85
+
86
+ @serializer = prepare_serializer
87
+ end
88
+
89
+ private
90
+
91
+ def prepare_name
92
+ init_name.to_sym
93
+ end
94
+
95
+ #
96
+ # Patched in:
97
+ # - plugin :formatters (wraps resulted block in formatter block and formats :const values)
98
+ #
99
+ def prepare_value_block
100
+ init_block ||
101
+ init_opts[:value] ||
102
+ prepare_const_block ||
103
+ prepare_delegate_block ||
104
+ prepare_keyword_block
105
+ end
106
+
107
+ #
108
+ # Patched in:
109
+ # - plugin :preloads (returns true by default if config option auto_hide_attribute_with_preloads is enabled)
110
+ #
111
+ def prepare_hide
112
+ init_opts[:hide]
113
+ end
114
+
115
+ def prepare_many
116
+ init_opts[:many]
117
+ end
118
+
119
+ def prepare_serializer
120
+ init_opts[:serializer]
121
+ end
122
+
123
+ def prepare_key
124
+ key = init_opts[:key]
125
+ key ? key.to_sym : name
126
+ end
127
+
128
+ def prepare_const_block
129
+ return unless init_opts.key?(:const)
130
+
131
+ const = init_opts[:const]
132
+ proc { const }
133
+ end
134
+
135
+ def prepare_keyword_block
136
+ key_method_name = key
137
+ proc do |object|
138
+ handle_no_method_error { object.public_send(key_method_name) }
139
+ end
140
+ end
141
+
142
+ def prepare_delegate_block
143
+ delegate = init_opts[:delegate]
144
+ return unless delegate
145
+
146
+ key_method_name = delegate[:key] || key
147
+ delegate_to = delegate[:to]
148
+
149
+ allow_nil = delegate.fetch(:allow_nil) { self.class.serializer_class.config.delegate_default_allow_nil }
150
+
151
+ if allow_nil
152
+ proc do |object|
153
+ handle_no_method_error do
154
+ object.public_send(delegate_to)&.public_send(key_method_name)
155
+ end
156
+ end
157
+ else
158
+ proc do |object|
159
+ handle_no_method_error do
160
+ object.public_send(delegate_to).public_send(key_method_name)
161
+ end
162
+ end
163
+ end
164
+ end
165
+
166
+ def handle_no_method_error
167
+ yield
168
+ rescue NoMethodError => error
169
+ raise error, "NoMethodError when serializing '#{name}' attribute in #{self.class.serializer_class}\n\n#{error.message}", error.backtrace
170
+ end
171
+ end
172
+
173
+ extend Serega::SeregaHelpers::SerializerClassHelper
174
+ include AttributeNormalizerInstanceMethods
175
+ end
176
+ end
data/lib/serega/config.rb CHANGED
@@ -19,7 +19,8 @@ class Serega
19
19
  serialize_keys: %i[context many].freeze,
20
20
  check_attribute_name: true,
21
21
  check_initiate_params: true,
22
- max_cached_map_per_serializer_count: 0,
22
+ delegate_default_allow_nil: false,
23
+ max_cached_plans_per_serializer_count: 0,
23
24
  to_json: (SeregaJSON.adapter == :oj) ? SeregaJSON::OjDump : SeregaJSON::JSONDump,
24
25
  from_json: (SeregaJSON.adapter == :oj) ? SeregaJSON::OjLoad : SeregaJSON::JSONLoad
25
26
  }.freeze
@@ -87,20 +88,36 @@ class Serega
87
88
  opts[:check_initiate_params] = value
88
89
  end
89
90
 
90
- # Returns :max_cached_map_per_serializer_count config option
91
- # @return [Boolean] Current :max_cached_map_per_serializer_count config option
92
- def max_cached_map_per_serializer_count
93
- opts.fetch(:max_cached_map_per_serializer_count)
91
+ # Returns :delegate_default_allow_nil config option
92
+ # @return [Boolean] Current :delegate_default_allow_nil config option
93
+ def delegate_default_allow_nil
94
+ opts.fetch(:delegate_default_allow_nil)
94
95
  end
95
96
 
96
- # Sets :max_cached_map_per_serializer_count config option
97
+ # Sets :delegate_default_allow_nil config option
98
+ #
99
+ # @param value [Boolean] Set :delegate_default_allow_nil config option
100
+ #
101
+ # @return [Boolean] :delegate_default_allow_nil config option
102
+ def delegate_default_allow_nil=(value)
103
+ raise SeregaError, "Must have boolean value, #{value.inspect} provided" if (value != true) && (value != false)
104
+ opts[:delegate_default_allow_nil] = value
105
+ end
106
+
107
+ # Returns :max_cached_plans_per_serializer_count config option
108
+ # @return [Boolean] Current :max_cached_plans_per_serializer_count config option
109
+ def max_cached_plans_per_serializer_count
110
+ opts.fetch(:max_cached_plans_per_serializer_count)
111
+ end
112
+
113
+ # Sets :max_cached_plans_per_serializer_count config option
97
114
  #
98
115
  # @param value [Boolean] Set :check_initiate_params config option
99
116
  #
100
- # @return [Boolean] New :max_cached_map_per_serializer_count config option
101
- def max_cached_map_per_serializer_count=(value)
117
+ # @return [Boolean] New :max_cached_plans_per_serializer_count config option
118
+ def max_cached_plans_per_serializer_count=(value)
102
119
  raise SeregaError, "Must have Integer value, #{value.inspect} provided" unless value.is_a?(Integer)
103
- opts[:max_cached_map_per_serializer_count] = value
120
+ opts[:max_cached_plans_per_serializer_count] = value
104
121
  end
105
122
 
106
123
  # Returns whether attributes names check is disabled
@@ -10,16 +10,17 @@ class Serega
10
10
  # SeregaObjectSerializer instance methods
11
11
  #
12
12
  module InstanceMethods
13
- attr_reader :context, :points, :many, :opts
13
+ attr_reader :context, :plan, :many, :opts
14
14
 
15
+ # @param plan [SeregaPlan] Serialization plan
15
16
  # @param context [Hash] Serialization context
16
17
  # @param many [TrueClass|FalseClass] is object is enumerable
17
- # @param points [Array<MapPoint>] Serialization points (attributes)
18
+ # @param opts [Hash] Any custom options
18
19
  #
19
20
  # @return [SeregaObjectSerializer] New SeregaObjectSerializer
20
- def initialize(context:, points:, many: nil, **opts)
21
+ def initialize(context:, plan:, many: nil, **opts)
21
22
  @context = context
22
- @points = points
23
+ @plan = plan
23
24
  @many = many
24
25
  @opts = opts
25
26
  end
@@ -44,7 +45,7 @@ class Serega
44
45
  # Patched in:
45
46
  # - plugin :presenter (makes presenter_object and serializes it)
46
47
  def serialize_object(object)
47
- points.each_with_object({}) do |point, container|
48
+ plan.points.each_with_object({}) do |point, container|
48
49
  serialize_point(object, point, container)
49
50
  end
50
51
  end
@@ -70,14 +71,14 @@ class Serega
70
71
  end
71
72
 
72
73
  def final_value(value, point)
73
- point.has_nested_points? ? relation_value(value, point) : value
74
+ point.child_plan ? relation_value(value, point) : value
74
75
  end
75
76
 
76
77
  def relation_value(value, point)
77
- nested_points = point.nested_points
78
- nested_serializer = point.nested_object_serializer
79
- nested_many = point.many
80
- serializer = nested_serializer.new(context: context, points: nested_points, many: nested_many, **opts)
78
+ child_plan = point.child_plan
79
+ child_serializer = point.child_object_serializer
80
+ child_many = point.many
81
+ serializer = child_serializer.new(context: context, plan: child_plan, many: child_many, **opts)
81
82
  serializer.serialize(value)
82
83
  end
83
84
 
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ #
5
+ # Constructs plan - list of serialized attributes.
6
+ # We will traverse this plan to construct serialized response.
7
+ #
8
+ class SeregaPlan
9
+ #
10
+ # SeregaPlan class methods
11
+ #
12
+ module ClassMethods
13
+ #
14
+ # Constructs plan of attributes that should be serialized.
15
+ #
16
+ # @param opts Serialization parameters
17
+ # @option opts [Hash] :only The only attributes to serialize
18
+ # @option opts [Hash] :except Attributes to hide
19
+ # @option opts [Hash] :with Attributes (usually marked `hide: true`) to serialize additionally
20
+ #
21
+ # @return [SeregaPlan] Serialization plan
22
+ #
23
+ def call(opts)
24
+ max_cache_size = serializer_class.config.max_cached_plans_per_serializer_count
25
+ return plan_for(opts) if max_cache_size.zero?
26
+
27
+ cached_plan_for(opts, max_cache_size)
28
+ end
29
+
30
+ private
31
+
32
+ def plan_for(opts)
33
+ new(**modifiers(opts))
34
+ end
35
+
36
+ def cached_plan_for(opts, max_cache_size)
37
+ @cache ||= {}
38
+ cache_key = construct_cache_key(opts)
39
+
40
+ plan = @cache[cache_key] ||= plan_for(opts)
41
+ @cache.shift if @cache.length > max_cache_size
42
+ plan
43
+ end
44
+
45
+ def modifiers(opts)
46
+ {
47
+ only: opts[:only] || FROZEN_EMPTY_HASH,
48
+ except: opts[:except] || FROZEN_EMPTY_HASH,
49
+ with: opts[:with] || FROZEN_EMPTY_HASH
50
+ }
51
+ end
52
+
53
+ def construct_cache_key(opts, cache_key = nil)
54
+ return nil if opts.empty?
55
+
56
+ cache_key ||= +""
57
+
58
+ opts.each do |key, nested_opts|
59
+ cache_key.insert(-1, SeregaUtils::SymbolName.call(key))
60
+ cache_key.insert(-1, "-")
61
+ construct_cache_key(nested_opts, cache_key)
62
+ end
63
+
64
+ cache_key
65
+ end
66
+ end
67
+
68
+ #
69
+ # SeregaPlan instance methods
70
+ #
71
+ module InstanceMethods
72
+ # Parent plan point, if exists
73
+ # @return [SeregaPlanPoint, nil]
74
+ attr_reader :parent_plan_point
75
+
76
+ # Serialization points
77
+ # @return [Array<SeregaPlanPoint>] points to serialize
78
+ attr_reader :points
79
+
80
+ #
81
+ # Instantiate new serialization plan.
82
+ #
83
+ # @param opts Serialization parameters
84
+ # @option opts [Hash] :only The only attributes to serialize
85
+ # @option opts [Hash] :except Attributes to hide
86
+ # @option opts [Hash] :with Attributes (usually marked hide: true`) to serialize additionally
87
+ # @option opts [Hash] :with Attributes (usually marked hide: true`) to serialize additionally
88
+ #
89
+ # @return [SeregaPlan] Serialization plan
90
+ #
91
+ def initialize(only:, except:, with:, parent_plan_point: nil)
92
+ @parent_plan_point = parent_plan_point
93
+ @points = attributes_points(only: only, except: except, with: with)
94
+ end
95
+
96
+ #
97
+ # Serializer class of current plan
98
+ #
99
+ def serializer_class
100
+ self.class.serializer_class
101
+ end
102
+
103
+ private
104
+
105
+ def attributes_points(only:, except:, with:)
106
+ points = []
107
+
108
+ serializer_class.attributes.each_value do |attribute|
109
+ next unless attribute.visible?(only: only, except: except, with: with)
110
+
111
+ child_fields =
112
+ if attribute.relation?
113
+ name = attribute.name
114
+ {only: only[name], with: with[name], except: except[name]}
115
+ end
116
+
117
+ points << serializer_class::SeregaPlanPoint.new(attribute, self, child_fields)
118
+ end
119
+
120
+ points.freeze
121
+ end
122
+ end
123
+
124
+ extend ClassMethods
125
+ include InstanceMethods
126
+ extend SeregaHelpers::SerializerClassHelper
127
+ end
128
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Serega
4
+ #
5
+ # Combines attribute and nested attributes
6
+ #
7
+ class SeregaPlanPoint
8
+ #
9
+ # SeregaPlanPoint instance methods
10
+ #
11
+ module InstanceMethods
12
+ extend Forwardable
13
+
14
+ # Link to current plan this point belongs to
15
+ # @return [SeregaAttribute] Current plan
16
+ attr_reader :plan
17
+
18
+ # Shows current attribute
19
+ # @return [SeregaAttribute] Current attribute
20
+ attr_reader :attribute
21
+
22
+ # Shows child plan if exists
23
+ # @return [SeregaPlan, nil] Attribute serialization plan
24
+ attr_reader :child_plan
25
+
26
+ # Child fields to serialize
27
+ # @return [SeregaPlan, nil] Attribute serialization plan
28
+ attr_reader :child_fields
29
+
30
+ # @!method name
31
+ # Attribute `name`
32
+ # @see SeregaAttribute::AttributeInstanceMethods#name
33
+ # @!method value
34
+ # Attribute `value` block
35
+ # @see SeregaAttribute::AttributeInstanceMethods#value
36
+ # @!method many
37
+ # Attribute `many` option
38
+ # @see SeregaAttribute::AttributeInstanceMethods#many
39
+ # @!method serializer
40
+ # Attribute `serializer` option
41
+ # @see SeregaAttribute::AttributeInstanceMethods#serializer
42
+ def_delegators :@attribute, :name, :value, :many, :serializer
43
+
44
+ #
45
+ # Initializes plan point
46
+ #
47
+ # @param plan [SeregaPlan] Plan where this point belongs to.
48
+ # @param attribute [SeregaAttribute] Attribute to construct plan point
49
+ # @param child_fields [Hash, nil] Child fields (:only, :with, :except)
50
+ #
51
+ # @return [SeregaPlanPoint] New plan point
52
+ #
53
+ def initialize(attribute, plan = nil, child_fields = nil)
54
+ @plan = plan
55
+ @attribute = attribute
56
+ @child_fields = child_fields
57
+ set_normalized_vars
58
+ freeze
59
+ end
60
+
61
+ #
62
+ # @return [SeregaObjectSerializer] object serializer for child plan
63
+ #
64
+ def child_object_serializer
65
+ serializer::SeregaObjectSerializer
66
+ end
67
+
68
+ private
69
+
70
+ # Patched in:
71
+ # - plugin :batch (prepares @batch)
72
+ # - plugin :preloads (prepares @preloads and @preloads_path)
73
+ def set_normalized_vars
74
+ @child_plan = prepare_child_plan
75
+ end
76
+
77
+ def prepare_child_plan
78
+ return unless serializer
79
+
80
+ fields = child_fields || FROZEN_EMPTY_HASH
81
+
82
+ serializer::SeregaPlan.new(
83
+ parent_plan_point: self,
84
+ only: fields[:only] || FROZEN_EMPTY_HASH,
85
+ with: fields[:with] || FROZEN_EMPTY_HASH,
86
+ except: fields[:except] || FROZEN_EMPTY_HASH
87
+ )
88
+ end
89
+ end
90
+
91
+ extend SeregaHelpers::SerializerClassHelper
92
+ include InstanceMethods
93
+ end
94
+ end