store_model 4.6.1 → 4.6.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c20c6465ee84ba2d0ffce4610ea14b6f6fed01e5da08cda8b165392e1d2e2952
4
- data.tar.gz: 072ca921fd0b02814fc917b5d2c6b2df14f8c3bd99c536fda472b5df64ea7774
3
+ metadata.gz: cdc2533305687b1b68c6f89ac876f26db75da028d8678739f54f716fdf6d36b4
4
+ data.tar.gz: 7cc3699f31b7109983e03520caba608f3aacf5739d4b31440a58c0eb1e778b5b
5
5
  SHA512:
6
- metadata.gz: a2e4339119a3076c4d200a535267033045d0fe75b461c2dfbe9b8d95bae1b4ecff0634ea5cbc5786076029fa2aba8027ac8a70e4f9d465c750f5f4d3a404b237
7
- data.tar.gz: 1c8e8941a81e46a1f4d082a59252fc7867bcb95684a814a17701121e69ae82005fb0ca21da2dd3fea49467d3048c0c51ea0661c9d92b706573ce4a7e265c3eb5
6
+ metadata.gz: a62aea392fd0662f66b400423f18d6b64f63841e27d5692089073e11a3602271280973cd08ce66dc7af1c2d979cc06000434fb31fdb0ea784c2619960567d2fd
7
+ data.tar.gz: 1cb3f90063e27f74600c61abe8a120443a72044df9fd55da0239d33958e01444dc75ae945cd71c73fb7bb47b980c327219e8ec90a8560e61b67f752e7f81076d
@@ -8,25 +8,14 @@ module StoreModel
8
8
  include ParentAssignment
9
9
 
10
10
  def _read_attribute(*)
11
- value = super
12
- assign_parent_to_store_model_relation(value) if store_model_attribute?(value)
13
- value
11
+ super.tap do |value|
12
+ assign_parent_to_store_model_relation(value)
13
+ end
14
14
  end
15
15
 
16
16
  def _write_attribute(*)
17
- value = super
18
- assign_parent_to_store_model_relation(value) if store_model_attribute?(value)
19
- value
20
- end
21
-
22
- private
23
-
24
- def store_model_attribute?(value)
25
- case value
26
- when StoreModel::Model then true
27
- when Array then value.first.is_a?(StoreModel::Model)
28
- when Hash then value.each_value.any? { |v| v.is_a?(StoreModel::Model) }
29
- else false
17
+ super.tap do |value|
18
+ assign_parent_to_store_model_relation(value)
30
19
  end
31
20
  end
32
21
  end
@@ -6,17 +6,14 @@ module StoreModel
6
6
  private
7
7
 
8
8
  def assign_parent_to_store_model_relation(attribute)
9
- assign_parent_to_singular_store_model(attribute)
10
- return if !attribute.is_a?(Array) && !attribute.is_a?(Hash)
11
-
12
- (attribute.try(:values) || attribute).each(&method(:assign_parent_to_singular_store_model))
13
- end
14
-
15
- def assign_parent_to_singular_store_model(item)
16
- return unless item.is_a?(StoreModel::Model)
17
- return if item.frozen?
18
-
19
- item.parent = self
9
+ case attribute
10
+ when StoreModel::Model
11
+ attribute.parent = self unless attribute.frozen?
12
+ when Array
13
+ attribute.each(&method(:assign_parent_to_store_model_relation))
14
+ when Hash
15
+ attribute.each_value(&method(:assign_parent_to_store_model_relation))
16
+ end
20
17
  end
21
18
  end
22
19
  end
@@ -4,6 +4,7 @@ require "store_model/types"
4
4
  require "store_model/enum"
5
5
  require "store_model/type_builders"
6
6
  require "store_model/nested_attributes"
7
+ require "store_model/unknown_attribute_assignment"
7
8
 
8
9
  module StoreModel
9
10
  # When included into class configures it to handle JSON column
@@ -26,6 +27,9 @@ module StoreModel
26
27
 
27
28
  base.attribute_method_suffix "?"
28
29
 
30
+ # Included last so it takes precedence over ActiveModel::AttributeAssignment.
31
+ base.include StoreModel::UnknownAttributeAssignment
32
+
29
33
  base.extend(ClassMethods)
30
34
  end
31
35
  # rubocop:enable Metrics/MethodLength
@@ -60,32 +64,30 @@ module StoreModel
60
64
  # @param options [Hash]
61
65
  #
62
66
  # @return [Hash]
63
- def as_json(options = {}) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
64
- serialize_unknown_attributes = if options.key?(:serialize_unknown_attributes)
67
+ def as_json(options = nil) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
68
+ serialize_unknown_attributes = if options&.key?(:serialize_unknown_attributes)
65
69
  options[:serialize_unknown_attributes]
66
70
  else
67
71
  StoreModel.config.serialize_unknown_attributes
68
72
  end
69
73
 
70
- serialize_enums_using_as_json = if options.key?(:serialize_enums_using_as_json)
74
+ serialize_enums_using_as_json = if options&.key?(:serialize_enums_using_as_json)
71
75
  options[:serialize_enums_using_as_json]
72
76
  else
73
77
  StoreModel.config.serialize_enums_using_as_json
74
78
  end
75
79
 
76
- serialize_empty_attributes = if options.key?(:serialize_empty_attributes)
80
+ serialize_empty_attributes = if options&.key?(:serialize_empty_attributes)
77
81
  options[:serialize_empty_attributes]
78
82
  else
79
83
  StoreModel.config.serialize_empty_attributes
80
84
  end
81
85
 
82
- # If the model is nested, we need to ensure that the serialization
83
-
84
86
  result = @attributes.keys.each_with_object({}) do |key, values|
85
87
  attr = @attributes.fetch(key)
86
88
  assign_serialization_options(attr, serialize_unknown_attributes, serialize_enums_using_as_json,
87
89
  serialize_empty_attributes)
88
- serialized = serialized_attribute(attr)
90
+ serialized = serialized_attribute(attr, options)
89
91
  values[key] = serialized if serialize_empty_attributes || !serialized.nil?
90
92
  end.with_indifferent_access
91
93
 
@@ -293,27 +295,29 @@ module StoreModel
293
295
  end
294
296
  end
295
297
 
296
- def serialized_attribute(attr)
298
+ def serialized_attribute(attr, options)
297
299
  case attr.value
298
300
  when StoreModel::Model
299
- Types::RawJSONEncoder.new(attr.value_for_database)
301
+ attr.value.as_json(options)
300
302
  when Array
301
- serialize_array_attribute(attr.value)
303
+ serialize_array_attribute(attr.value, options)
302
304
  when Hash # attribute :smth, json
303
- attr.value
305
+ serialize_hash_attribute(attr.value, options)
304
306
  else
305
307
  attr.value_for_database
306
308
  end
307
309
  end
308
310
 
309
- def serialize_array_attribute(array)
311
+ def serialize_array_attribute(array, options)
310
312
  return array.as_json unless array.any? && array.all? { |value| value.is_a?(StoreModel::Model) }
311
313
 
312
- array.as_json(
313
- serialize_unknown_attributes: array.first.serialize_unknown_attributes?,
314
- serialize_enums_using_as_json: array.first.serialize_enums_using_as_json?,
315
- serialize_empty_attributes: array.first.serialize_empty_attributes?
316
- )
314
+ array.as_json(options)
315
+ end
316
+
317
+ def serialize_hash_attribute(hash, options)
318
+ return hash unless hash.any? && hash.values.all? { |value| value.nil? || value.is_a?(StoreModel::Model) }
319
+
320
+ hash.transform_values { |value| value&.as_json(options) }
317
321
  end
318
322
 
319
323
  def assign_serialization_options(attr, serialize_unknown_attributes, serialize_enums_using_as_json, serialize_empty_attributes) # rubocop:disable Layout/LineLength
@@ -28,7 +28,7 @@ module StoreModel
28
28
  # @param value [Object] a value to cast
29
29
  #
30
30
  # @return StoreModel::Model
31
- def cast_value(value) # rubocop:disable Metrics/MethodLength
31
+ def cast_value(value)
32
32
  return nil if value.nil?
33
33
 
34
34
  if value.is_a?(String)
@@ -40,8 +40,6 @@ module StoreModel
40
40
  else
41
41
  raise_cast_error(value)
42
42
  end
43
- rescue ActiveModel::UnknownAttributeError => e
44
- handle_unknown_attribute(value, e)
45
43
  end
46
44
 
47
45
  # Casts a value from the ruby type to a type that the database knows how
@@ -93,9 +91,7 @@ module StoreModel
93
91
  end
94
92
 
95
93
  def model_instance(value)
96
- @model_klass.new(value)
97
- rescue ActiveModel::UnknownAttributeError => e
98
- handle_unknown_attribute(value, e)
94
+ StoreModel::UnknownAttributeAssignment.collect { @model_klass.new(value) }
99
95
  end
100
96
 
101
97
  def deserialize_by_types(hash)
@@ -37,20 +37,8 @@ module StoreModel
37
37
  def decode_and_initialize(value)
38
38
  decoded = ActiveSupport::JSON.decode(value) rescue nil
39
39
  model_instance(decoded) unless decoded.nil?
40
- rescue ActiveModel::UnknownAttributeError => e
41
- handle_unknown_attribute(decoded, e)
42
40
  end
43
41
  # rubocop:enable Style/RescueModifier
44
-
45
- def handle_unknown_attribute(value, exception)
46
- attribute = exception.attribute.to_sym
47
- value_symbolized = value.symbolize_keys
48
- value_symbolized = value_symbolized[:attributes] if value_symbolized.key?(:attributes)
49
-
50
- cast_value(value_symbolized.except(attribute)).tap do |configuration|
51
- configuration.unknown_attributes[attribute.to_s] = value_symbolized[attribute]
52
- end
53
- end
54
42
  end
55
43
  end
56
44
  end
@@ -31,7 +31,7 @@ module StoreModel
31
31
  # @param value [Object] a value to cast
32
32
  #
33
33
  # @return StoreModel::Model
34
- def cast_value(value) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
34
+ def cast_value(value) # rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity
35
35
  return nil if value.nil?
36
36
  return nil if @union && value.respond_to?(:empty?) && value.empty?
37
37
 
@@ -44,8 +44,6 @@ module StoreModel
44
44
  else
45
45
  raise_cast_error(value)
46
46
  end
47
- rescue ActiveModel::UnknownAttributeError => e
48
- handle_unknown_attribute(value, e)
49
47
  end
50
48
 
51
49
  # Casts a value from the ruby type to a type that the database knows how
@@ -95,15 +93,9 @@ module StoreModel
95
93
  return nil if decoded.nil?
96
94
  return nil if @union && decoded.respond_to?(:empty?) && decoded.empty?
97
95
 
98
- model_instance(decoded)
99
- rescue ActiveModel::UnknownAttributeError => e
100
- handle_unknown_attribute(decoded, e)
96
+ extract_model_klass(decoded).to_type.cast_value(decoded)
101
97
  end
102
98
  # rubocop:enable Style/RescueModifier
103
-
104
- def model_instance(value)
105
- extract_model_klass(value).new(value)
106
- end
107
99
  end
108
100
  end
109
101
  end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StoreModel
4
+ # Keeps input keys without a writer in #unknown_attributes while a StoreModel
5
+ # type builds the model, instead of raising ActiveModel::UnknownAttributeError.
6
+ module UnknownAttributeAssignment
7
+ PENDING_KEY = :store_model_collect_unknown_attributes
8
+ BUILDING_KEY = :store_model_building_with_unknown_attributes
9
+
10
+ # Only the next StoreModel instance built inside the block collects unknown
11
+ # keys, so models built by its writers or a custom initializer stay strict.
12
+ def self.collect
13
+ previous = Thread.current[PENDING_KEY]
14
+ Thread.current[PENDING_KEY] = true
15
+ yield
16
+ ensure
17
+ Thread.current[PENDING_KEY] = previous
18
+ end
19
+
20
+ # `new` takes the pending flag before any custom `initialize` runs and can
21
+ # build other models.
22
+ def self.building
23
+ previous = Thread.current[BUILDING_KEY]
24
+ Thread.current[BUILDING_KEY] = Thread.current[PENDING_KEY]
25
+ Thread.current[PENDING_KEY] = nil
26
+ yield
27
+ ensure
28
+ Thread.current[BUILDING_KEY] = previous
29
+ end
30
+
31
+ def self.included(base)
32
+ base.extend(ClassMethods)
33
+ end
34
+
35
+ module ClassMethods # :nodoc:
36
+ def new(...)
37
+ UnknownAttributeAssignment.building { super }
38
+ end
39
+ end
40
+
41
+ # Falls back to the pending flag for factories that call Class#new directly.
42
+ def initialize(attributes = {})
43
+ @collecting_unknown_attributes = Thread.current[BUILDING_KEY] || Thread.current[PENDING_KEY]
44
+ Thread.current[BUILDING_KEY] = Thread.current[PENDING_KEY] = nil
45
+ super
46
+ ensure
47
+ @collecting_unknown_attributes = nil
48
+ end
49
+
50
+ private
51
+
52
+ # Checking for the writer up front avoids raising NoMethodError per unknown key,
53
+ # which goes through ActiveModel's method_missing and casts every attribute.
54
+ # Declared attributes can still be served by method_missing, for example after
55
+ # attribute_method_suffix undefines the generated writers.
56
+ def _assign_attribute(key, value)
57
+ return super unless @collecting_unknown_attributes
58
+
59
+ setter = :"#{key}="
60
+ if respond_to_without_attributes?(setter) || has_attribute?(key)
61
+ public_send(setter, value)
62
+ else
63
+ unknown_attributes[key.to_s] = value
64
+ end
65
+ end
66
+ end
67
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module StoreModel # :nodoc:
4
- VERSION = "4.6.1"
4
+ VERSION = "4.6.2"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: store_model
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.6.1
4
+ version: 4.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - DmitryTsepelev
@@ -107,6 +107,7 @@ files:
107
107
  - lib/store_model/types/one_polymorphic.rb
108
108
  - lib/store_model/types/polymorphic_helper.rb
109
109
  - lib/store_model/types/raw_json.rb
110
+ - lib/store_model/unknown_attribute_assignment.rb
110
111
  - lib/store_model/version.rb
111
112
  homepage: https://github.com/DmitryTsepelev/store_model
112
113
  licenses: