store_model 4.6.0 → 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 +4 -4
- data/lib/store_model/ext/active_record/base.rb +5 -16
- data/lib/store_model/ext/parent_assignment.rb +8 -11
- data/lib/store_model/model.rb +25 -18
- data/lib/store_model/types/one.rb +3 -7
- data/lib/store_model/types/one_base.rb +0 -12
- data/lib/store_model/types/one_polymorphic.rb +2 -10
- data/lib/store_model/unknown_attribute_assignment.rb +67 -0
- data/lib/store_model/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cdc2533305687b1b68c6f89ac876f26db75da028d8678739f54f716fdf6d36b4
|
|
4
|
+
data.tar.gz: 7cc3699f31b7109983e03520caba608f3aacf5739d4b31440a58c0eb1e778b5b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
18
|
-
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
data/lib/store_model/model.rb
CHANGED
|
@@ -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 =
|
|
64
|
-
serialize_unknown_attributes = if options
|
|
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
|
|
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
|
|
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,24 +295,29 @@ module StoreModel
|
|
|
293
295
|
end
|
|
294
296
|
end
|
|
295
297
|
|
|
296
|
-
def serialized_attribute(attr)
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
298
|
+
def serialized_attribute(attr, options)
|
|
299
|
+
case attr.value
|
|
300
|
+
when StoreModel::Model
|
|
301
|
+
attr.value.as_json(options)
|
|
302
|
+
when Array
|
|
303
|
+
serialize_array_attribute(attr.value, options)
|
|
304
|
+
when Hash # attribute :smth, json
|
|
305
|
+
serialize_hash_attribute(attr.value, options)
|
|
301
306
|
else
|
|
302
307
|
attr.value_for_database
|
|
303
308
|
end
|
|
304
309
|
end
|
|
305
310
|
|
|
306
|
-
def serialize_array_attribute(array)
|
|
311
|
+
def serialize_array_attribute(array, options)
|
|
307
312
|
return array.as_json unless array.any? && array.all? { |value| value.is_a?(StoreModel::Model) }
|
|
308
313
|
|
|
309
|
-
array.as_json(
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
)
|
|
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) }
|
|
314
321
|
end
|
|
315
322
|
|
|
316
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)
|
|
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
|
|
@@ -74,7 +72,7 @@ module StoreModel
|
|
|
74
72
|
case value
|
|
75
73
|
when String
|
|
76
74
|
payload = ActiveSupport::JSON.decode(value) rescue {}
|
|
77
|
-
model_instance(deserialize_by_types(payload))
|
|
75
|
+
model_instance(deserialize_by_types(payload)) unless payload.nil?
|
|
78
76
|
when ::Hash
|
|
79
77
|
model_instance(deserialize_by_types(value))
|
|
80
78
|
when nil
|
|
@@ -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/
|
|
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
|
-
|
|
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
|
data/lib/store_model/version.rb
CHANGED
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.
|
|
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:
|