attr_json 1.1.0 → 1.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -1
- data/README.md +78 -1
- data/lib/attr_json/attribute_definition.rb +6 -0
- data/lib/attr_json/config.rb +12 -2
- data/lib/attr_json/model.rb +39 -6
- data/lib/attr_json/record.rb +19 -3
- data/lib/attr_json/serialization_coder_from_type.rb +40 -0
- data/lib/attr_json/type/model.rb +8 -5
- data/lib/attr_json/version.rb +1 -1
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b6bbeb0707de928eae27b0bceafa5691486a2784d3065c71f91757e825bbaa94
|
4
|
+
data.tar.gz: 64b4d70c31b1b9499ee74f764940b283d45231ec6f4809cec35459e7d6ed9288
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 91aab7ce45bf723d4c76d9b2756ad65650bef1b223b2214bc9091118a8e4a04cb347fbac2438e7aaab4ed302f92c26f88b23f602e626b494f1aa5251f81473d7
|
7
|
+
data.tar.gz: 7862793a46004520e65cbb8b98a4857ec5441d6f0456dd8e368c4af7be8fafaefd8f0ef780f38f7361ea45b1117dbee4fe384492e44f5044aa295ee56488a99b
|
data/CHANGELOG.md
CHANGED
@@ -4,7 +4,26 @@ Notable changes to this project will be documented in this file.
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
6
6
|
|
7
|
-
## [Unreleased](https://github.com/jrochkind/attr_json/compare/v1.
|
7
|
+
## [Unreleased](https://github.com/jrochkind/attr_json/compare/v1.2.0...HEAD)
|
8
|
+
|
9
|
+
|
10
|
+
|
11
|
+
## [1.2.0](https://github.com/jrochkind/attr_json/compare/v1.1.0...v1.2.0)
|
12
|
+
|
13
|
+
### Added
|
14
|
+
|
15
|
+
* attr_json_config(bad_cast: :as_nil) to avoid raising on data that can't be cast to a
|
16
|
+
AttrJson::Model, instead just casting to nil. https://github.com/jrochkind/attr_json/pull/95
|
17
|
+
|
18
|
+
* Documented and tested support for using ActiveRecord serialize to map one AttrJson::Model
|
19
|
+
to an entire column on it's own. https://github.com/jrochkind/attr_json/pull/89 and
|
20
|
+
https://github.com/jrochkind/attr_json/pull/93
|
21
|
+
|
22
|
+
* Better synchronization with ActiveRecord attributes when using rails_attribute:true, and a configurable true default_rails_attribute. Thanks @volkanunsal . https://github.com/jrochkind/attr_json/pull/94
|
23
|
+
|
24
|
+
### Changed
|
25
|
+
|
26
|
+
* AttrJson::Model#== now requires same class for equality. And doesn't raise on certain arguments. https://github.com/jrochkind/attr_json/pull/90 Thanks @caiofilipemr for related bug report.
|
8
27
|
|
9
28
|
## [1.1.0](https://github.com/jrochkind/attr_json/compare/v1.0.0...v1.1.0)
|
10
29
|
|
data/README.md
CHANGED
@@ -228,6 +228,26 @@ m.attr_jsons_before_type_cast
|
|
228
228
|
|
229
229
|
You can nest AttrJson::Model objects inside each other, as deeply as you like.
|
230
230
|
|
231
|
+
### Model-type defaults
|
232
|
+
|
233
|
+
If you want to set a default for an AttrJson::Model type, you should use a proc argument for
|
234
|
+
the default, to avoid accidentally re-using a shared global default value, similar to issues
|
235
|
+
people have with ruby Hash default.
|
236
|
+
|
237
|
+
```ruby
|
238
|
+
attr_json :lang_and_value, LangAndValue.to_type, default: -> { LangAndValue.new(lang: "en", value: "default") }
|
239
|
+
```
|
240
|
+
|
241
|
+
You can also use a Hash value that will be cast to your model, no need for proc argument
|
242
|
+
in this case.
|
243
|
+
|
244
|
+
```ruby
|
245
|
+
attr_json :lang_and_value, LangAndValue.to_type, default: { lang: "en", value: "default" }
|
246
|
+
```
|
247
|
+
|
248
|
+
|
249
|
+
### Polymorphic model types
|
250
|
+
|
231
251
|
There is some support for "polymorphic" attributes that can hetereogenously contain instances of different AttrJson::Model classes, see comment docs at [AttrJson::Type::PolymorphicModel](./lib/attr_json/type/polymorphic_model.rb).
|
232
252
|
|
233
253
|
|
@@ -286,6 +306,63 @@ always mean 'contains' -- the previous query needs a `my_labels.hello`
|
|
286
306
|
which is a hash that includes the key/value, `lang: en`, it can have
|
287
307
|
other key/values in it too. String values will need to match exactly.
|
288
308
|
|
309
|
+
## Single AttrJson::Model serialized to an entire json column
|
310
|
+
|
311
|
+
The main use case of the gem is set up to let you combine multiple primitives and nested models
|
312
|
+
under different keys combined in a single json or jsonb column.
|
313
|
+
|
314
|
+
But you may also want to have one AttrJson::Model class that serializes to map one model class, as
|
315
|
+
a hash, to an entire json column on it's own.
|
316
|
+
|
317
|
+
`AttrJson::Model` can supply a simple coder for the [ActiveRecord serialization](https://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Serialization/ClassMethods.html)
|
318
|
+
feature to easily do that.
|
319
|
+
|
320
|
+
```ruby
|
321
|
+
class MyModel
|
322
|
+
include AttrJson::Model
|
323
|
+
|
324
|
+
attr_json :some_string, :string
|
325
|
+
attr_json :some_int, :int
|
326
|
+
end
|
327
|
+
|
328
|
+
class MyTable < ApplicationRecord
|
329
|
+
serialize :some_json_column, MyModel.to_serialization_coder
|
330
|
+
end
|
331
|
+
|
332
|
+
MyTable.create(some_json_column: MyModel.new(some_string: "string"))
|
333
|
+
|
334
|
+
# will cast from hash for you
|
335
|
+
MyTable.create(some_json_column: { some_int: 12 })
|
336
|
+
|
337
|
+
# etc
|
338
|
+
```
|
339
|
+
|
340
|
+
To avoid errors raised at inconvenient times, we recommend you set these settings to make 'bad'
|
341
|
+
data turn into `nil`, consistent with most ActiveRecord types:
|
342
|
+
|
343
|
+
```ruby
|
344
|
+
class MyModel
|
345
|
+
include AttrJson::Model
|
346
|
+
|
347
|
+
attr_json_config(bad_cast: :as_nil, unknown_key: :strip)
|
348
|
+
# ...
|
349
|
+
end
|
350
|
+
```
|
351
|
+
|
352
|
+
And/or define a setter method to cast, and raise early on data problems:
|
353
|
+
|
354
|
+
```ruby
|
355
|
+
class MyTable < ApplicationRecord
|
356
|
+
serialize :some_json_column, MyModel.to_serialization_coder
|
357
|
+
|
358
|
+
def some_json_column=(val)
|
359
|
+
super( )
|
360
|
+
end
|
361
|
+
end
|
362
|
+
```
|
363
|
+
|
364
|
+
Serializing a model to an entire json column is a relatively recent feature, please let us know how it's working for you.
|
365
|
+
|
289
366
|
<a name="arbitrary-json-data"></a>
|
290
367
|
## Storing Arbitrary JSON data
|
291
368
|
|
@@ -308,7 +385,7 @@ Use with Rails form builders is supported pretty painlessly. Including with [sim
|
|
308
385
|
|
309
386
|
If you have nested AttrJson::Models you'd like to use in your forms much like Rails associated records: Where you would use Rails `accepts_nested_attributes_for`, instead `include AttrJson::NestedAttributes` and use `attr_json_accepts_nested_attributes_for`. Multiple levels of nesting are supported.
|
310
387
|
|
311
|
-
To get simple_form to properly detect your attribute types, define your attributes with `rails_attribute: true`.
|
388
|
+
To get simple_form to properly detect your attribute types, define your attributes with `rails_attribute: true`. You can default rails_attribute to true with `attr_json_config(default_rails_attribute: true)`
|
312
389
|
|
313
390
|
For more info, see doc page on [Use with Forms and Form Builders](doc_src/forms.md).
|
314
391
|
|
@@ -78,6 +78,12 @@
|
|
78
78
|
@default != NO_DEFAULT_PROVIDED
|
79
79
|
end
|
80
80
|
|
81
|
+
# Can be value or proc!
|
82
|
+
def default_argument
|
83
|
+
return nil unless has_default?
|
84
|
+
@default
|
85
|
+
end
|
86
|
+
|
81
87
|
def provide_default!
|
82
88
|
unless has_default?
|
83
89
|
raise ArgumentError.new("This #{self.class.name} does not have a default defined!")
|
data/lib/attr_json/config.rb
CHANGED
@@ -3,10 +3,20 @@ module AttrJson
|
|
3
3
|
# and rails class_attribute. Instead, you set to new Config object
|
4
4
|
# changed with {#merge}.
|
5
5
|
class Config
|
6
|
-
RECORD_ALLOWED_KEYS = %i{
|
7
|
-
|
6
|
+
RECORD_ALLOWED_KEYS = %i{
|
7
|
+
default_container_attribute
|
8
|
+
default_rails_attribute
|
9
|
+
default_accepts_nested_attributes
|
10
|
+
}
|
11
|
+
|
12
|
+
MODEL_ALLOWED_KEYS = %i{
|
13
|
+
unknown_key
|
14
|
+
bad_cast
|
15
|
+
}
|
16
|
+
|
8
17
|
DEFAULTS = {
|
9
18
|
default_container_attribute: "json_attributes",
|
19
|
+
default_rails_attribute: false,
|
10
20
|
unknown_key: :raise
|
11
21
|
}
|
12
22
|
|
data/lib/attr_json/model.rb
CHANGED
@@ -7,6 +7,8 @@ require 'attr_json/attribute_definition/registry'
|
|
7
7
|
require 'attr_json/type/model'
|
8
8
|
require 'attr_json/model/cocoon_compat'
|
9
9
|
|
10
|
+
require 'attr_json/serialization_coder_from_type'
|
11
|
+
|
10
12
|
module AttrJson
|
11
13
|
|
12
14
|
# Meant for use in a plain class, turns it into an ActiveModel::Model
|
@@ -30,9 +32,39 @@ module AttrJson
|
|
30
32
|
#
|
31
33
|
# class Something
|
32
34
|
# include AttrJson::Model
|
33
|
-
# attr_json_config(unknown_key: :
|
35
|
+
# attr_json_config(unknown_key: :allow)
|
36
|
+
# #...
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# Similarly, trying to set a Model-valued attribute with an object that
|
40
|
+
# can't be cast to a Hash or Model at all will normally raise a
|
41
|
+
# AttrJson::Type::Model::BadCast error, but you can set config `bad_cast: :as_nil`
|
42
|
+
# to make it cast to nil, more like typical ActiveRecord cast.
|
43
|
+
#
|
44
|
+
# class Something
|
45
|
+
# include AttrJson::Model
|
46
|
+
# attr_json_config(bad_cast: :as_nil)
|
34
47
|
# #...
|
35
48
|
# end
|
49
|
+
#
|
50
|
+
# ## ActiveRecord `serialize`
|
51
|
+
#
|
52
|
+
# If you want to map a single AttrJson::Model to a json/jsonb column, you
|
53
|
+
# can use ActiveRecord `serialize` feature.
|
54
|
+
#
|
55
|
+
# https://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Serialization/ClassMethods.html
|
56
|
+
#
|
57
|
+
# We provide a simple shim to give you the right API for a "coder" for AR serialize:
|
58
|
+
#
|
59
|
+
# class ValueModel
|
60
|
+
# include AttrJson::Model
|
61
|
+
# attr_json :some_string, :string
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
# class SomeModel < ApplicationRecord
|
65
|
+
# serialize :some_json_column, ValueModel.to_serialize_coder
|
66
|
+
# end
|
67
|
+
#
|
36
68
|
module Model
|
37
69
|
extend ActiveSupport::Concern
|
38
70
|
|
@@ -88,6 +120,10 @@ module AttrJson
|
|
88
120
|
@type ||= AttrJson::Type::Model.new(self)
|
89
121
|
end
|
90
122
|
|
123
|
+
def to_serialization_coder
|
124
|
+
@serialization_coder ||= AttrJson::SerializationCoderFromType.new(to_type)
|
125
|
+
end
|
126
|
+
|
91
127
|
# Type can be an instance of an ActiveModel::Type::Value subclass, or a symbol that will
|
92
128
|
# be looked up in `ActiveModel::Type.lookup`
|
93
129
|
#
|
@@ -240,12 +276,9 @@ module AttrJson
|
|
240
276
|
end
|
241
277
|
|
242
278
|
# Two AttrJson::Model objects are equal if they are the same class
|
243
|
-
#
|
244
|
-
# TODO: Should we allow subclasses to be equal, or should they have to be the
|
245
|
-
# exact same class?
|
279
|
+
# AND their #attributes are equal.
|
246
280
|
def ==(other_object)
|
247
|
-
|
248
|
-
other_object.attributes == self.attributes
|
281
|
+
other_object.class == self.class && other_object.attributes == self.attributes
|
249
282
|
end
|
250
283
|
|
251
284
|
# ActiveRecord objects [have a](https://github.com/rails/rails/blob/v5.1.5/activerecord/lib/active_record/nested_attributes.rb#L367-L374)
|
data/lib/attr_json/record.rb
CHANGED
@@ -119,10 +119,11 @@ module AttrJson
|
|
119
119
|
# @option options [Boolean] :rails_attribute (false) Create an actual ActiveRecord
|
120
120
|
# `attribute` for name param. A Rails attribute isn't needed for our functionality,
|
121
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.
|
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)`
|
123
124
|
def attr_json(name, type, **options)
|
124
125
|
options = {
|
125
|
-
rails_attribute:
|
126
|
+
rails_attribute: self.attr_json_config.default_rails_attribute,
|
126
127
|
validate: true,
|
127
128
|
container_attribute: self.attr_json_config.default_container_attribute,
|
128
129
|
accepts_nested_attributes: self.attr_json_config.default_accepts_nested_attributes
|
@@ -160,7 +161,21 @@ module AttrJson
|
|
160
161
|
# We don't actually use this for anything, we provide our own covers. But registering
|
161
162
|
# it with usual system will let simple_form and maybe others find it.
|
162
163
|
if options[:rails_attribute]
|
163
|
-
|
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])
|
177
|
+
end
|
178
|
+
end
|
164
179
|
end
|
165
180
|
|
166
181
|
_attr_jsons_module.module_eval do
|
@@ -173,6 +188,7 @@ module AttrJson
|
|
173
188
|
# this simple way.
|
174
189
|
|
175
190
|
define_method("#{name}=") do |value|
|
191
|
+
super(value) if defined?(super)
|
176
192
|
attribute_def = self.class.attr_json_registry.fetch(name.to_sym)
|
177
193
|
public_send(attribute_def.container_attribute)[attribute_def.store_key] = attribute_def.cast(value)
|
178
194
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module AttrJson
|
2
|
+
|
3
|
+
# A little wrapper to provide an object that provides #dump and #load method for use
|
4
|
+
# as a coder second-argument for [ActiveRecord Serialization](https://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Serialization/ClassMethods.html),
|
5
|
+
# that simply delegates to #serialize and #deserialize from a ActiveModel::Type object.
|
6
|
+
#
|
7
|
+
# Created to be used with an AttrJson::Model type (AttrJson::Type::Model), but hypothetically
|
8
|
+
# could be a shim from anything with serialize/deserialize to dump/load instead.
|
9
|
+
#
|
10
|
+
# class ValueModel
|
11
|
+
# include AttrJson::Model
|
12
|
+
# attr_json :some_string, :string
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# class SomeModel < ApplicationRecord
|
16
|
+
# serialize :some_json_column, ValueModel.to_serialize_coder
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# Note when used with an AttrJson::Model, it will dump/load from a HASH, not a
|
20
|
+
# string. It assumes it's writing to a Json(b) column that wants/provides hashes,
|
21
|
+
# not strings.
|
22
|
+
class SerializationCoderFromType
|
23
|
+
attr_reader :type
|
24
|
+
def initialize(type)
|
25
|
+
@type = type
|
26
|
+
end
|
27
|
+
|
28
|
+
# Dump and load methods to support ActiveRecord Serialization
|
29
|
+
# too.
|
30
|
+
def dump(value)
|
31
|
+
type.serialize(value)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Dump and load methods to support ActiveRecord Serialization
|
35
|
+
# too. https://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Serialization/ClassMethods.html
|
36
|
+
def load(value)
|
37
|
+
type.deserialize(value)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/attr_json/type/model.rb
CHANGED
@@ -7,6 +7,7 @@ module AttrJson
|
|
7
7
|
# You create one with AttrJson::Model::Type.new(attr_json_model_class),
|
8
8
|
# but normally that's only done in AttrJson::Model.to_type, there isn't
|
9
9
|
# an anticipated need to create from any other place.
|
10
|
+
#
|
10
11
|
class Model < ::ActiveModel::Type::Value
|
11
12
|
class BadCast < ArgumentError ; end
|
12
13
|
|
@@ -35,11 +36,13 @@ module AttrJson
|
|
35
36
|
elsif v.respond_to?(:to_h)
|
36
37
|
# TODO Maybe we ought not to do this on #to_h?
|
37
38
|
model.new_from_serializable(v.to_h)
|
39
|
+
elsif model.attr_json_config.bad_cast == :as_nil
|
40
|
+
# This was originally default behavior, to be like existing ActiveRecord
|
41
|
+
# which kind of silently does this for non-castable basic values. That
|
42
|
+
# ended up being confusing in the basic case, so now we raise by default,
|
43
|
+
# but this is still configurable.
|
44
|
+
nil
|
38
45
|
else
|
39
|
-
# Bad input. Originally we were trying to return nil, to be like
|
40
|
-
# existing ActiveRecord which kind of silently does a basic value
|
41
|
-
# with null input. But that ended up making things confusing, let's
|
42
|
-
# just raise.
|
43
46
|
raise BadCast.new("Can not cast from #{v.inspect} to #{self.type}")
|
44
47
|
end
|
45
48
|
end
|
@@ -50,7 +53,7 @@ module AttrJson
|
|
50
53
|
elsif v.kind_of?(model)
|
51
54
|
v.serializable_hash
|
52
55
|
else
|
53
|
-
cast(v).serializable_hash
|
56
|
+
(cast_v = cast(v)) && cast_v.serializable_hash
|
54
57
|
end
|
55
58
|
end
|
56
59
|
|
data/lib/attr_json/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: attr_json
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jonathan Rochkind
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-06-
|
11
|
+
date: 2020-06-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -167,6 +167,7 @@ files:
|
|
167
167
|
- lib/attr_json/record/dirty.rb
|
168
168
|
- lib/attr_json/record/query_builder.rb
|
169
169
|
- lib/attr_json/record/query_scopes.rb
|
170
|
+
- lib/attr_json/serialization_coder_from_type.rb
|
170
171
|
- lib/attr_json/type/array.rb
|
171
172
|
- lib/attr_json/type/container_attribute.rb
|
172
173
|
- lib/attr_json/type/model.rb
|
@@ -179,7 +180,7 @@ licenses:
|
|
179
180
|
metadata:
|
180
181
|
homepage_uri: https://github.com/jrochkind/attr_json
|
181
182
|
source_code_uri: https://github.com/jrochkind/attr_json
|
182
|
-
post_install_message:
|
183
|
+
post_install_message:
|
183
184
|
rdoc_options: []
|
184
185
|
require_paths:
|
185
186
|
- lib
|
@@ -194,9 +195,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
194
195
|
- !ruby/object:Gem::Version
|
195
196
|
version: '0'
|
196
197
|
requirements: []
|
197
|
-
|
198
|
-
|
199
|
-
signing_key:
|
198
|
+
rubygems_version: 3.0.3
|
199
|
+
signing_key:
|
200
200
|
specification_version: 4
|
201
201
|
summary: ActiveRecord attributes stored serialized in a json column, super smooth.
|
202
202
|
test_files: []
|