attr_json 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|