attr_json 1.4.0 → 2.6.0

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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +22 -22
  3. data/.github/workflows/future_rails_ci.yml +3 -3
  4. data/Appraisals +40 -29
  5. data/CHANGELOG.md +144 -1
  6. data/Gemfile +8 -2
  7. data/README.md +83 -63
  8. data/attr_json.gemspec +8 -7
  9. data/doc_src/forms.md +3 -14
  10. data/gemfiles/rails_6_0.gemfile +3 -2
  11. data/gemfiles/rails_6_1.gemfile +2 -2
  12. data/gemfiles/rails_7_0.gemfile +2 -3
  13. data/gemfiles/{rails_5_1.gemfile → rails_7_1.gemfile} +4 -4
  14. data/gemfiles/{rails_5_2.gemfile → rails_7_2.gemfile} +4 -4
  15. data/gemfiles/{rails_5_0.gemfile → rails_8_0.gemfile} +5 -6
  16. data/gemfiles/rails_8_1.gemfile +18 -0
  17. data/gemfiles/rails_edge.gemfile +4 -4
  18. data/lib/attr_json/attribute_definition/registry.rb +43 -9
  19. data/lib/attr_json/attribute_definition.rb +51 -14
  20. data/lib/attr_json/config.rb +1 -2
  21. data/lib/attr_json/model/nested_model_validator.rb +27 -0
  22. data/lib/attr_json/model.rb +185 -53
  23. data/lib/attr_json/nested_attributes/builder.rb +2 -0
  24. data/lib/attr_json/nested_attributes/multiparameter_attribute_writer.rb +2 -0
  25. data/lib/attr_json/nested_attributes/writer.rb +6 -6
  26. data/lib/attr_json/nested_attributes.rb +7 -1
  27. data/lib/attr_json/record/query_builder.rb +2 -0
  28. data/lib/attr_json/record/query_scopes.rb +2 -0
  29. data/lib/attr_json/record.rb +113 -88
  30. data/lib/attr_json/serialization_coder_from_type.rb +2 -0
  31. data/lib/attr_json/type/array.rb +12 -3
  32. data/lib/attr_json/type/container_attribute.rb +2 -0
  33. data/lib/attr_json/type/model.rb +15 -4
  34. data/lib/attr_json/type/polymorphic_model.rb +43 -23
  35. data/lib/attr_json/version.rb +1 -1
  36. data/lib/attr_json.rb +21 -6
  37. data/playground_models.rb +2 -2
  38. metadata +15 -32
  39. data/doc_src/dirty_tracking.md +0 -155
  40. data/lib/attr_json/record/dirty.rb +0 -281
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'attr_json/attribute_definition'
2
4
  require 'attr_json/attribute_definition/registry'
3
5
  require 'attr_json/type/container_attribute'
@@ -24,35 +26,61 @@ module AttrJson
24
26
 
25
27
  class_attribute :attr_json_registry, instance_accessor: false
26
28
  self.attr_json_registry = AttrJson::AttributeDefinition::Registry.new
27
- end
28
29
 
29
- protected
30
+ # Ensure that rails attributes tracker knows about values we just fetched
31
+ after_initialize do
32
+ attr_json_sync_to_rails_attributes
33
+ end
30
34
 
31
- # adapted from ActiveRecord query_attribute method
32
- # https://github.com/rails/rails/blob/v5.2.3/activerecord/lib/active_record/attribute_methods/query.rb#L12
35
+ # After a safe, rails attribute dirty tracking ends up re-creating
36
+ # new objects for attribute values, so we need to sync again
37
+ # so mutation effects both.
38
+ after_save do
39
+ attr_json_sync_to_rails_attributes
40
+ end
41
+ end
42
+
43
+ # Sync all values FROM the json_attributes json column TO rails attributes
33
44
  #
34
- # Sadly we could not re-use Rails code here, becuase the built-in method assumes attribute
35
- # can be obtained with `self[attr_name]`, which you can not with attr_json (is that bad?), as
36
- # well as `self.class.columns_hash[attr_name]` which you definitely can not (which is probably not bad),
37
- # and has no way to use the value-translation semantics independently of that. May be a problem if
38
- # ActiveRecord changes it's query method semantics in the future, will have to be sync'd here.
45
+ # If values have for some reason gotten out of sync this will make them the
46
+ # identical objects again, with the container hash value being the source.
39
47
  #
40
- # Used to implement query methods on attr_json attributes, like `attr_json :foo, :string`, method `#foo?`
41
- def self.attr_json_query_method(record, attribute)
42
- value = record.send(attribute)
43
-
44
- case value
45
- when true
46
- true
47
- when false, nil, ActiveModel::Type::Boolean::FALSE_VALUES
48
- false
49
- else
50
- if value.respond_to?(:to_i) && ( Numeric === value || value.to_s !~ /[^0-9]/ )
51
- !value.to_i.zero?
52
- elsif value.respond_to?(:zero?)
53
- !value.zero?
54
- else
55
- !value.blank?
48
+ # In some cases, the values may already be equivalent but different objects --
49
+ # This is meant to ensure they are the _same object_ in both places, so
50
+ # mutation of mutable object will effect both places, for instance for dirty
51
+ # tracking.
52
+ def attr_json_sync_to_rails_attributes
53
+ self.class.attr_json_registry.definitions.group_by(&:container_attribute).each_pair do |container_attribute, definitions|
54
+ begin
55
+ # column may have eg been left out of an explicit 'select'
56
+ next unless has_attribute?(container_attribute)
57
+
58
+ container_value = public_send(container_attribute)
59
+
60
+ # isn't expected to be possible to be nil rather than empty hash, but
61
+ # if it is from some edge case, well, we don't have values to sync, fine
62
+ next if container_value.nil?
63
+
64
+ definitions.each do |attribute_def|
65
+ attr_name = attribute_def.name
66
+ value = container_value[attribute_def.store_key]
67
+
68
+ unless value.nil?
69
+ # TODO, can we just make this use the setter?
70
+ write_attribute(attr_name, value)
71
+
72
+ clear_attribute_change(attr_name) if persisted?
73
+
74
+ # writing and clearning will result in a new object stored in
75
+ # rails attributes, we want
76
+ # to make sure the exact same object is in the json attribute,
77
+ # so in-place mutation changes to it are reflected in both places.
78
+ container_value[attribute_def.store_key] = read_attribute(attr_name)
79
+ end
80
+ end
81
+ rescue AttrJson::Type::Model::BadCast, AttrJson::Type::PolymorphicModel::TypeError => e
82
+ # There was bad data in the DB, we're just going to skip the Rails attribute sync.
83
+ # Should we log?
56
84
  end
57
85
  end
58
86
  end
@@ -93,7 +121,8 @@ module AttrJson
93
121
  end
94
122
 
95
123
 
96
-
124
+ # Registers an attr_json attribute, and a Rails attribute covering it.
125
+ #
97
126
  # Type can be a symbol that will be looked up in `ActiveModel::Type.lookup`,
98
127
  # or an ActiveModel:::Type::Value).
99
128
  #
@@ -102,6 +131,8 @@ module AttrJson
102
131
  # @param type [ActiveModel::Type::Value] An instance of an ActiveModel::Type::Value (or subclass)
103
132
  #
104
133
  # @option options [Boolean] :array (false) Make this attribute an array of given type.
134
+ # Array types default to an empty array. If you want to turn that off, you can add
135
+ # `default: AttrJson::AttributeDefinition::NO_DEFAULT_PROVIDED`
105
136
  #
106
137
  # @option options [Object] :default (nil) Default value, if a Proc object it will be #call'd
107
138
  # for default.
@@ -113,100 +144,94 @@ module AttrJson
113
144
  # json(b) ActiveRecord attribute/column to serialize as a key in. Defaults to
114
145
  # `attr_json_config.default_container_attribute`, which defaults to `:json_attributes`
115
146
  #
116
- # @option options [Boolean] :validate (true) Create an ActiveRecord::Validations::AssociatedValidator so
117
- # validation errors on the attributes post up to self.
147
+ # @option options [Boolean] :validate (true) validation errors on nested models in the attributes
148
+ # should post up to self similar to Rails ActiveRecord::Validations::AssociatedValidator on
149
+ # associated objects.
150
+ #
151
+ # @option options [Boolean,Hash] :accepts_nested_attributes. If true, equivalent
152
+ # of writing `attr_json_accepts_nested_attributes :attribute_name`. If value is a hash,
153
+ # then same, but with hash as options to `attr_json_accepts_nested_attributes`.
154
+ # Default taken from `attr_json_config.default_accepts_nested_attributes`, for
155
+ # array or model types where it is applicable.
118
156
  #
119
- # @option options [Boolean] :rails_attribute (false) Create an actual ActiveRecord
120
- # `attribute` for name param. A Rails attribute isn't needed for our functionality,
121
- # but registering thusly will let the type be picked up by simple_form and
122
- # other tools that may look for it via Rails attribute APIs. Default can be changed
123
- # with `attr_json_config(default_rails_attribute: true)`
124
157
  def attr_json(name, type, **options)
125
158
  options = {
126
- rails_attribute: self.attr_json_config.default_rails_attribute,
127
159
  validate: true,
128
160
  container_attribute: self.attr_json_config.default_container_attribute,
129
- accepts_nested_attributes: self.attr_json_config.default_accepts_nested_attributes
130
161
  }.merge!(options)
131
- options.assert_valid_keys(AttributeDefinition::VALID_OPTIONS + [:validate, :rails_attribute, :accepts_nested_attributes])
162
+ options.assert_valid_keys(AttributeDefinition::VALID_OPTIONS + [:validate, :accepts_nested_attributes])
132
163
  container_attribute = options[:container_attribute]
133
164
 
134
- # TODO arg check container_attribute make sure it exists. Hard cause
135
- # schema isn't loaded yet when class def is loaded. Maybe not.
136
165
 
137
- # Want to lazily add an attribute cover to the json container attribute,
138
- # only if it hasn't already been done. WARNING we are using internal
139
- # Rails API here, but only way to do this lazily, which I thought was
140
- # worth it. On the other hand, I think .attribute is idempotent, maybe we don't need it...
141
- #
142
- # We set default to empty hash, because that 'tricks' AR into knowing any
143
- # application of defaults is a change that needs to be saved.
144
- unless attributes_to_define_after_schema_loads[container_attribute.to_s] &&
145
- attributes_to_define_after_schema_loads[container_attribute.to_s].first.is_a?(AttrJson::Type::ContainerAttribute) &&
146
- attributes_to_define_after_schema_loads[container_attribute.to_s].first.model == self
147
- # If this is already defined, but was for superclass, we need to define it again for
148
- # this class.
166
+ # Make sure to "lazily" register attribute for *container* class if this is the first time
167
+ # this container attribute hsa been encountered for this specific class. The registry
168
+ # helps us keep track. Kinda messy, in future we may want a more explicit API
169
+ # that does not require us to implicitly track first-time per-container.
170
+ unless self.attr_json_registry.container_attribute_registered?(model: self, attribute_name: container_attribute.to_sym)
149
171
  attribute container_attribute.to_sym, AttrJson::Type::ContainerAttribute.new(self, container_attribute), default: -> { {} }
172
+ self.attr_json_registry.register_container_attribute(model: self, attribute_name: container_attribute.to_sym)
150
173
  end
151
174
 
152
175
  self.attr_json_registry = attr_json_registry.with(
153
- AttributeDefinition.new(name.to_sym, type, options.except(:rails_attribute, :validate, :accepts_nested_attributes))
176
+ AttributeDefinition.new(name.to_sym, type, options.except(:validate, :accepts_nested_attributes))
154
177
  )
155
178
 
156
- # By default, automatically validate nested models
179
+ # By default, automatically validate nested models, allowing nils.
157
180
  if type.kind_of?(AttrJson::Type::Model) && options[:validate]
158
- self.validates_with ActiveRecord::Validations::AssociatedValidator, attributes: [name.to_sym]
159
- end
160
-
161
- # We don't actually use this for anything, we provide our own covers. But registering
162
- # it with usual system will let simple_form and maybe others find it.
163
- if options[:rails_attribute]
164
- attr_json_definition = attr_json_registry[name]
165
-
166
- attribute_args = attr_json_definition.has_default? ? { default: attr_json_definition.default_argument } : {}
167
- self.attribute name.to_sym, attr_json_definition.type, **attribute_args
168
-
169
- # Ensure that rails attributes tracker knows about value we just fetched
170
- # for this particular attribute. Yes, we are registering an after_find for each
171
- # attr_json registered with rails_attribute:true, using the `name` from above under closure. .
172
- after_find do
173
- value = public_send(name)
174
- if value && has_attribute?(name.to_sym)
175
- write_attribute(name.to_sym, value)
176
- self.send(:clear_attribute_changes, [name.to_sym])
181
+ # implementation adopted from:
182
+ # https://github.com/rails/rails/blob/v7.0.4.1/activerecord/lib/active_record/validations/associated.rb#L6-L10
183
+ #
184
+ # but had to customize to allow nils in an array through
185
+ validates_each name.to_sym do |record, attr, value|
186
+ if Array(value).reject { |element| element.nil? || element.valid? }.any?
187
+ record.errors.add(attr, :invalid, value: value)
177
188
  end
178
189
  end
179
190
  end
180
191
 
181
- _attr_jsons_module.module_eval do
182
- # For getter and setter, we used to use read_store_attribute/write_store_attribute
183
- # copied from Rails store_accessor implementation.
184
- # https://github.com/rails/rails/blob/74c3e43fba458b9b863d27f0c45fd2d8dc603cbc/activerecord/lib/active_record/store.rb#L90-L96
185
- #
186
- # But in fact just getting/setting in the hash provided to us by ActiveRecord json type
187
- # container works BETTER for dirty tracking. We had a test that only passed doing it
188
- # this simple way.
192
+ # Register as a Rails attribute
193
+ attr_json_definition = attr_json_registry[name]
194
+ attribute_args = attr_json_definition.has_default? ? { default: attr_json_definition.default_argument } : {}
195
+ self.attribute name.to_sym, attr_json_definition.type, **attribute_args
189
196
 
197
+ # For getter and setter, we consider the container has the "canonical" data location.
198
+ # But setter also writes to rails attribute, and tries to keep them in sync with the
199
+ # *same object*, so mutations happen to both places.
200
+ #
201
+ # This began roughly modelled on approach of Rail sstore_accessor implementation:
202
+ # https://github.com/rails/rails/blob/74c3e43fba458b9b863d27f0c45fd2d8dc603cbc/activerecord/lib/active_record/store.rb#L90-L96
203
+ #
204
+ # ...But wound up with lots of evolution to try to get dirty tracking working as well
205
+ # as we could -- without a completely custom separate dirty tracking implementation
206
+ # like store_accessor tries!
207
+ _attr_jsons_module.module_eval do
190
208
  define_method("#{name}=") do |value|
191
- super(value) if defined?(super)
209
+ super(value) # should write to rails attribute
210
+
211
+ # write to container hash, with value read from attribute to try to keep objects
212
+ # sync'd to exact same object in rails attribute and container hash.
192
213
  attribute_def = self.class.attr_json_registry.fetch(name.to_sym)
193
- public_send(attribute_def.container_attribute)[attribute_def.store_key] = attribute_def.cast(value)
214
+ public_send(attribute_def.container_attribute)[attribute_def.store_key] = read_attribute(name)
194
215
  end
195
216
 
196
217
  define_method("#{name}") do
218
+ # read from container hash -- we consider that the canonical location.
197
219
  attribute_def = self.class.attr_json_registry.fetch(name.to_sym)
198
220
  public_send(attribute_def.container_attribute)[attribute_def.store_key]
199
221
  end
222
+ end
200
223
 
201
- define_method("#{name}?") do
202
- # implementation of `query_store_attribute` is based on Rails `query_attribute` implementation
203
- AttrJson::Record.attr_json_query_method(self, name)
204
- end
224
+ accepts_nested_attributes = if options.has_key?(:accepts_nested_attributes)
225
+ options[:accepts_nested_attributes]
226
+ elsif attr_json_definition.single_model_type? || attr_json_definition.array_type?
227
+ # use configured default only if we have a type appropriate for it!
228
+ self.attr_json_config.default_accepts_nested_attributes
229
+ else
230
+ false
205
231
  end
206
232
 
207
- # Default attr_json_accepts_nested_attributes_for values
208
- if options[:accepts_nested_attributes]
209
- options = options[:accepts_nested_attributes] == true ? {} : options[:accepts_nested_attributes]
233
+ if accepts_nested_attributes
234
+ options = accepts_nested_attributes == true ? {} : accepts_nested_attributes
210
235
  self.attr_json_accepts_nested_attributes_for name, **options
211
236
  end
212
237
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AttrJson
2
4
 
3
5
  # A little wrapper to provide an object that provides #dump and #load method for use
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AttrJson
2
4
  module Type
3
5
  # You can wrap any ActiveModel::Type in one of these, and it's magically
@@ -30,6 +32,10 @@ module AttrJson
30
32
  convert_to_array(value).collect { |v| base_type.deserialize(v) }
31
33
  end
32
34
 
35
+ def changed_in_place?(raw_old_value, new_value)
36
+ serialize(new_value) != raw_old_value
37
+ end
38
+
33
39
  # This is used only by our own keypath-chaining query stuff.
34
40
  def value_for_contains_query(key_path_arr, value)
35
41
  [
@@ -41,10 +47,13 @@ module AttrJson
41
47
  ]
42
48
  end
43
49
 
44
- # Hacky, hard-coded classes, but used in our weird primitive implementation in NestedAttributes,
45
- # better than putting the conditionals there
50
+ # Soft-deprecated. You probably want to use
51
+ #
52
+ # AttrJson::AttributeDefinition#array_of_primitive_type?
53
+ #
54
+ # instead where possible.
46
55
  def base_type_primitive?
47
- !(base_type.is_a?(AttrJson::Type::Model) || base_type.is_a?(AttrJson::Type::PolymorphicModel))
56
+ ! AttrJson::AttributeDefinition.single_model_type?(base_type)
48
57
  end
49
58
 
50
59
  protected
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AttrJson
2
4
  module Type
3
5
  # A type that gets applied to the AR container/store jsonb attribute,
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AttrJson
2
4
  module Type
3
5
  # An ActiveModel::Type representing a particular AttrJson::Model
@@ -8,13 +10,22 @@ module AttrJson
8
10
  # but normally that's only done in AttrJson::Model.to_type, there isn't
9
11
  # an anticipated need to create from any other place.
10
12
  #
13
+ #
11
14
  class Model < ::ActiveModel::Type::Value
12
15
  class BadCast < ArgumentError ; end
13
16
 
14
- attr_accessor :model
15
- def initialize(model)
17
+ attr_accessor :model, :strip_nils
18
+
19
+ # @param model [AttrJson::Model] the model _class_ object
20
+ # @param strip_nils [Symbol,Boolean] [true, false, or :safely]
21
+ # (default :safely), As a type, should we strip nils when serialiing?
22
+ # This value passed to AttrJson::Model#serialized_hash(strip_nils).
23
+ # by default it's :safely, we strip nils when it can be done safely
24
+ # to preserve default overrides.
25
+ def initialize(model, strip_nils: :safely)
16
26
  #TODO type check, it really better be a AttrJson::Model. maybe?
17
27
  @model = model
28
+ @strip_nils = strip_nils
18
29
  end
19
30
 
20
31
  def type
@@ -51,9 +62,9 @@ module AttrJson
51
62
  if v.nil?
52
63
  nil
53
64
  elsif v.kind_of?(model)
54
- v.serializable_hash
65
+ v.serializable_hash(strip_nils: strip_nils)
55
66
  else
56
- (cast_v = cast(v)) && cast_v.serializable_hash
67
+ (cast_v = cast(v)) && cast_v.serializable_hash(strip_nils: strip_nils)
57
68
  end
58
69
  end
59
70
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AttrJson
2
4
  module Type
3
5
  # AttrJson::Type::PolymorphicModel can be used to create attr_json attributes
@@ -100,17 +102,11 @@ module AttrJson
100
102
  end
101
103
 
102
104
  def cast(v)
103
- if v.nil?
104
- v
105
- elsif model_names.include?(v.class.name)
106
- v
107
- elsif v.respond_to?(:to_hash)
108
- cast_from_hash(v.to_hash)
109
- elsif v.respond_to?(:to_h)
110
- cast_from_hash(v.to_h)
111
- else
112
- raise_bad_model_name(v.class, v)
113
- end
105
+ cast_or_deserialize(v, :cast)
106
+ end
107
+
108
+ def deserialize(v)
109
+ cast_or_deserialize(v, :deserialize)
114
110
  end
115
111
 
116
112
  def serialize(v)
@@ -127,10 +123,6 @@ module AttrJson
127
123
  type.serialize(v).merge(type_key => model_name)
128
124
  end
129
125
 
130
- def deserialize(v)
131
- cast(v)
132
- end
133
-
134
126
  def type_for_model_name(model_name)
135
127
  model_type_lookup[model_name]
136
128
  end
@@ -153,15 +145,29 @@ module AttrJson
153
145
 
154
146
  protected
155
147
 
156
- def raise_missing_type_key(value)
157
- raise TypeError, "AttrJson::Type::Polymorphic can't cast without '#{type_key}' key: #{value}"
158
- end
159
-
160
- def raise_bad_model_name(name, value)
161
- raise TypeError, "This AttrJson::Type::PolymorphicType can only include {#{model_names.join(', ')}}, not '#{name}': #{value.inspect}"
148
+ # We need to make sure to call the correct operation on
149
+ # the model type, so that we get the same result as if
150
+ # we had called the type directly
151
+ #
152
+ # @param v [Object, nil] the value to cast or deserialize
153
+ # @param operation [Symbol] :cast or :deserialize
154
+ def cast_or_deserialize(v, operation)
155
+ if v.nil?
156
+ v
157
+ elsif model_names.include?(v.class.name)
158
+ v
159
+ elsif v.respond_to?(:to_hash)
160
+ model_from_hash(v.to_hash, operation)
161
+ elsif v.respond_to?(:to_h)
162
+ model_from_hash(v.to_h, operation)
163
+ else
164
+ raise_bad_model_name(v.class, v)
165
+ end
162
166
  end
163
167
 
164
- def cast_from_hash(hash)
168
+ # @param hash [Hash] the value to cast or deserialize
169
+ # @param operation [Symbol] :cast or :deserialize
170
+ def model_from_hash(hash, operation)
165
171
  new_hash = hash.stringify_keys
166
172
  model_name = new_hash.delete(type_key.to_s)
167
173
 
@@ -171,7 +177,21 @@ module AttrJson
171
177
 
172
178
  raise_bad_model_name(model_name, hash) if type.nil?
173
179
 
174
- type.cast(new_hash)
180
+ if operation == :deserialize
181
+ type.deserialize(new_hash)
182
+ elsif operation == :cast
183
+ type.cast(new_hash)
184
+ else
185
+ raise ArgumentError, "Unknown operation #{operation}"
186
+ end
187
+ end
188
+
189
+ def raise_missing_type_key(value)
190
+ raise TypeError, "AttrJson::Type::Polymorphic can't cast without '#{type_key}' key: #{value}"
191
+ end
192
+
193
+ def raise_bad_model_name(name, value)
194
+ raise TypeError, "This AttrJson::Type::PolymorphicType can only include {#{model_names.join(', ')}}, not '#{name}': #{value.inspect}"
175
195
  end
176
196
  end
177
197
  end
@@ -1,3 +1,3 @@
1
1
  module AttrJson
2
- VERSION = "1.4.0"
2
+ VERSION = "2.6.0"
3
3
  end
data/lib/attr_json.rb CHANGED
@@ -9,11 +9,26 @@ require 'attr_json/nested_attributes'
9
9
  require 'attr_json/record/query_scopes'
10
10
  require 'attr_json/type/polymorphic_model'
11
11
 
12
- # Dirty not supported on Rails 5.0
13
- if Gem.loaded_specs["activerecord"].version.release >= Gem::Version.new('5.1')
14
- require 'attr_json/record/dirty'
15
- end
16
-
17
12
  module AttrJson
18
-
13
+ # We need to convert Symbols to strings a lot at present -- ActiveRecord does too, so
14
+ # not too suprrising.
15
+ #
16
+ # In Rails 3.0 and above, we can use Symbol#name to get a frozen string back
17
+ # and avoid extra allocations. https://bugs.ruby-lang.org/issues/16150
18
+ #
19
+ # Ruby 2.7 doens't yet have it though. As long as we are supporting ruby 2.7,
20
+ # we'll just check at runtime to keep this lean
21
+ if RUBY_VERSION.split('.').first.to_i >= 3
22
+ def self.efficient_to_s(obj)
23
+ if obj.kind_of?(Symbol)
24
+ obj.name
25
+ else
26
+ obj.to_s
27
+ end
28
+ end
29
+ else
30
+ def self.efficient_to_s(obj)
31
+ obj.to_s
32
+ end
33
+ end
19
34
  end
data/playground_models.rb CHANGED
@@ -61,7 +61,7 @@ class MyModel < ActiveRecord::Base
61
61
  self.table_name = "products"
62
62
 
63
63
  include AttrJson::Record
64
- include AttrJson::Record::Dirty
64
+ #include AttrJson::Record::Dirty
65
65
 
66
66
  attr_json :str, :string
67
67
  attr_json :str_array, :string, array: true
@@ -76,7 +76,7 @@ end
76
76
  class Product < StaticProduct
77
77
  include AttrJson::Record
78
78
  include AttrJson::Record::QueryScopes
79
- include AttrJson::Record::Dirty
79
+ #include AttrJson::Record::Dirty
80
80
 
81
81
  attr_json :title, :string
82
82
  attr_json :rank, :integer
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: attr_json
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 2.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathan Rochkind
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2021-12-22 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: activerecord
@@ -16,20 +15,20 @@ dependencies:
16
15
  requirements:
17
16
  - - ">="
18
17
  - !ruby/object:Gem::Version
19
- version: 5.0.0
18
+ version: 6.0.0
20
19
  - - "<"
21
20
  - !ruby/object:Gem::Version
22
- version: '7.1'
21
+ version: '8.2'
23
22
  type: :runtime
24
23
  prerelease: false
25
24
  version_requirements: !ruby/object:Gem::Requirement
26
25
  requirements:
27
26
  - - ">="
28
27
  - !ruby/object:Gem::Version
29
- version: 5.0.0
28
+ version: 6.0.0
30
29
  - - "<"
31
30
  - !ruby/object:Gem::Version
32
- version: '7.1'
31
+ version: '8.2'
33
32
  - !ruby/object:Gem::Dependency
34
33
  name: bundler
35
34
  requirement: !ruby/object:Gem::Requirement
@@ -72,20 +71,6 @@ dependencies:
72
71
  - - "~>"
73
72
  - !ruby/object:Gem::Version
74
73
  version: '3.7'
75
- - !ruby/object:Gem::Dependency
76
- name: database_cleaner
77
- requirement: !ruby/object:Gem::Requirement
78
- requirements:
79
- - - "~>"
80
- - !ruby/object:Gem::Version
81
- version: '1.5'
82
- type: :development
83
- prerelease: false
84
- version_requirements: !ruby/object:Gem::Requirement
85
- requirements:
86
- - - "~>"
87
- - !ruby/object:Gem::Version
88
- version: '1.5'
89
74
  - !ruby/object:Gem::Dependency
90
75
  name: yard-activesupport-concern
91
76
  requirement: !ruby/object:Gem::Requirement
@@ -144,8 +129,8 @@ dependencies:
144
129
  version: '1.0'
145
130
  description: |-
146
131
  ActiveRecord attributes stored serialized in a json column, super smooth.
147
- For Rails 5.0, 5.1, or 5.2. Typed and cast like Active Record. Supporting nested models,
148
- dirty tracking, some querying (with postgres jsonb contains), and working smoothy with form builders.
132
+ Typed and cast like Active Record. Supporting nested models, dirty tracking, some querying
133
+ (with postgres jsonb contains), and working smoothy with form builders.
149
134
 
150
135
  Use your database as a typed object store via ActiveRecord, in the same models right next to
151
136
  ordinary ActiveRecord column-backed attributes and associations. Your json-serialized attr_json
@@ -173,15 +158,15 @@ files:
173
158
  - bin/rspec
174
159
  - bin/setup
175
160
  - config.ru
176
- - doc_src/dirty_tracking.md
177
161
  - doc_src/forms.md
178
162
  - gemfiles/.bundle/config
179
- - gemfiles/rails_5_0.gemfile
180
- - gemfiles/rails_5_1.gemfile
181
- - gemfiles/rails_5_2.gemfile
182
163
  - gemfiles/rails_6_0.gemfile
183
164
  - gemfiles/rails_6_1.gemfile
184
165
  - gemfiles/rails_7_0.gemfile
166
+ - gemfiles/rails_7_1.gemfile
167
+ - gemfiles/rails_7_2.gemfile
168
+ - gemfiles/rails_8_0.gemfile
169
+ - gemfiles/rails_8_1.gemfile
185
170
  - gemfiles/rails_edge.gemfile
186
171
  - lib/attr_json.rb
187
172
  - lib/attr_json/attribute_definition.rb
@@ -189,12 +174,12 @@ files:
189
174
  - lib/attr_json/config.rb
190
175
  - lib/attr_json/model.rb
191
176
  - lib/attr_json/model/cocoon_compat.rb
177
+ - lib/attr_json/model/nested_model_validator.rb
192
178
  - lib/attr_json/nested_attributes.rb
193
179
  - lib/attr_json/nested_attributes/builder.rb
194
180
  - lib/attr_json/nested_attributes/multiparameter_attribute_writer.rb
195
181
  - lib/attr_json/nested_attributes/writer.rb
196
182
  - lib/attr_json/record.rb
197
- - lib/attr_json/record/dirty.rb
198
183
  - lib/attr_json/record/query_builder.rb
199
184
  - lib/attr_json/record/query_scopes.rb
200
185
  - lib/attr_json/serialization_coder_from_type.rb
@@ -210,7 +195,6 @@ licenses:
210
195
  metadata:
211
196
  homepage_uri: https://github.com/jrochkind/attr_json
212
197
  source_code_uri: https://github.com/jrochkind/attr_json
213
- post_install_message:
214
198
  rdoc_options: []
215
199
  require_paths:
216
200
  - lib
@@ -218,15 +202,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
218
202
  requirements:
219
203
  - - ">="
220
204
  - !ruby/object:Gem::Version
221
- version: 2.4.0
205
+ version: 2.6.0
222
206
  required_rubygems_version: !ruby/object:Gem::Requirement
223
207
  requirements:
224
208
  - - ">="
225
209
  - !ruby/object:Gem::Version
226
210
  version: '0'
227
211
  requirements: []
228
- rubygems_version: 3.1.6
229
- signing_key:
212
+ rubygems_version: 3.7.1
230
213
  specification_version: 4
231
214
  summary: ActiveRecord attributes stored serialized in a json column, super smooth.
232
215
  test_files: []