attr_json 0.1.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 +7 -0
- data/.gitignore +11 -0
- data/.rspec +2 -0
- data/.travis.yml +17 -0
- data/.yardopts +1 -0
- data/Gemfile +42 -0
- data/LICENSE.txt +21 -0
- data/README.md +426 -0
- data/Rakefile +8 -0
- data/bin/console +23 -0
- data/bin/rake +29 -0
- data/bin/rspec +29 -0
- data/bin/setup +11 -0
- data/config.ru +9 -0
- data/doc_src/dirty_tracking.md +155 -0
- data/doc_src/forms.md +124 -0
- data/json_attribute.gemspec +50 -0
- data/lib/attr_json.rb +18 -0
- data/lib/attr_json/attribute_definition.rb +93 -0
- data/lib/attr_json/attribute_definition/registry.rb +93 -0
- data/lib/attr_json/model.rb +270 -0
- data/lib/attr_json/model/cocoon_compat.rb +27 -0
- data/lib/attr_json/nested_attributes.rb +92 -0
- data/lib/attr_json/nested_attributes/builder.rb +24 -0
- data/lib/attr_json/nested_attributes/multiparameter_attribute_writer.rb +86 -0
- data/lib/attr_json/nested_attributes/writer.rb +215 -0
- data/lib/attr_json/record.rb +140 -0
- data/lib/attr_json/record/dirty.rb +281 -0
- data/lib/attr_json/record/query_builder.rb +84 -0
- data/lib/attr_json/record/query_scopes.rb +35 -0
- data/lib/attr_json/type/array.rb +55 -0
- data/lib/attr_json/type/container_attribute.rb +56 -0
- data/lib/attr_json/type/model.rb +77 -0
- data/lib/attr_json/version.rb +3 -0
- data/playground_models.rb +101 -0
- metadata +177 -0
data/lib/attr_json.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require "attr_json/version"
|
2
|
+
|
3
|
+
require "active_record"
|
4
|
+
require "active_record/connection_adapters/postgresql_adapter"
|
5
|
+
|
6
|
+
require 'attr_json/record'
|
7
|
+
require 'attr_json/model'
|
8
|
+
require 'attr_json/nested_attributes'
|
9
|
+
require 'attr_json/record/query_scopes'
|
10
|
+
|
11
|
+
# Dirty not supported on Rails 5.0
|
12
|
+
if Gem.loaded_specs["activerecord"].version.release >= Gem::Version.new('5.1')
|
13
|
+
require 'attr_json/record/dirty'
|
14
|
+
end
|
15
|
+
|
16
|
+
module AttrJson
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'attr_json/type/array'
|
2
|
+
|
3
|
+
module AttrJson
|
4
|
+
|
5
|
+
# Represents a `attr_json` definition, on either a AttrJson::Record
|
6
|
+
# or AttrJson::Model. Normally this class is only used by
|
7
|
+
# AttrJson::AttributeDefinition::{Registry}.
|
8
|
+
class AttributeDefinition
|
9
|
+
NO_DEFAULT_PROVIDED = Object.new.freeze
|
10
|
+
VALID_OPTIONS = %i{container_attribute store_key default array}.freeze
|
11
|
+
|
12
|
+
attr_reader :name, :type, :original_args, :container_attribute
|
13
|
+
|
14
|
+
# @param name [Symbol,String]
|
15
|
+
# @param type [Symbol,ActiveModel::Type::Value]
|
16
|
+
#
|
17
|
+
# @option options store_key [Symbol,String]
|
18
|
+
# @option options container_attribute [Symbol,ActiveModel::Type::Value]
|
19
|
+
# Only means something in a AttrJson::Record, no meaning in a AttrJson::Model.
|
20
|
+
# @option options default [Object,Symbol,Proc] (nil)
|
21
|
+
# @option options array [Boolean] (false)
|
22
|
+
def initialize(name, type, options = {})
|
23
|
+
options.assert_valid_keys *VALID_OPTIONS
|
24
|
+
# saving original args for reflection useful for debugging, maybe other things.
|
25
|
+
@original_args = [name, type, options]
|
26
|
+
|
27
|
+
@name = name.to_sym
|
28
|
+
|
29
|
+
@container_attribute = options[:container_attribute] && options[:container_attribute].to_s
|
30
|
+
|
31
|
+
@store_key = options[:store_key] && options[:store_key].to_s
|
32
|
+
|
33
|
+
@default = if options.has_key?(:default)
|
34
|
+
options[:default]
|
35
|
+
else
|
36
|
+
NO_DEFAULT_PROVIDED
|
37
|
+
end
|
38
|
+
|
39
|
+
if type.is_a? Symbol
|
40
|
+
# ActiveModel::Type.lookup may make more sense, but ActiveModel::Type::Date
|
41
|
+
# seems to have a bug with multi-param assignment. Mostly they return
|
42
|
+
# the same types, but ActiveRecord::Type::Date works with multi-param assignment.
|
43
|
+
type = ActiveRecord::Type.lookup(type)
|
44
|
+
elsif ! type.is_a? ActiveModel::Type::Value
|
45
|
+
raise ArgumentError, "Second argument (#{type}) must be a symbol or instance of an ActiveModel::Type::Value subclass"
|
46
|
+
end
|
47
|
+
@type = (options[:array] == true ? AttrJson::Type::Array.new(type) : type)
|
48
|
+
end
|
49
|
+
|
50
|
+
def cast(value)
|
51
|
+
type.cast(value)
|
52
|
+
end
|
53
|
+
|
54
|
+
def serialize(value)
|
55
|
+
type.serialize(value)
|
56
|
+
end
|
57
|
+
|
58
|
+
def deserialize(value)
|
59
|
+
type.deserialize(value)
|
60
|
+
end
|
61
|
+
|
62
|
+
def has_custom_store_key?
|
63
|
+
!!@store_key
|
64
|
+
end
|
65
|
+
|
66
|
+
def store_key
|
67
|
+
(@store_key || name).to_s
|
68
|
+
end
|
69
|
+
|
70
|
+
def has_default?
|
71
|
+
@default != NO_DEFAULT_PROVIDED
|
72
|
+
end
|
73
|
+
|
74
|
+
def provide_default!
|
75
|
+
unless has_default?
|
76
|
+
raise ArgumentError.new("This #{self.class.name} does not have a default defined!")
|
77
|
+
end
|
78
|
+
|
79
|
+
# Seems weird to assume a Proc can't be the default itself, but I guess
|
80
|
+
# Proc's aren't serializable, so fine assumption. Modeled after:
|
81
|
+
# https://github.com/rails/rails/blob/f2dfd5c6fdffdf65e6f07aae8e855ac802f9302f/activerecord/lib/active_record/attribute/user_provided_default.rb#L12-L16
|
82
|
+
if @default.is_a?(Proc)
|
83
|
+
cast(@default.call)
|
84
|
+
else
|
85
|
+
cast(@default)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def array_type?
|
90
|
+
type.is_a? AttrJson::Type::Array
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'attr_json/attribute_definition'
|
2
|
+
|
3
|
+
module AttrJson
|
4
|
+
class AttributeDefinition
|
5
|
+
# Attached to a class to record the json attributes registered,
|
6
|
+
# with either AttrJson::Record or AttrJson::Model.
|
7
|
+
#
|
8
|
+
# Think of it as mostly like a hash keyed by attribute name, value
|
9
|
+
# an AttributeDefinition.
|
10
|
+
#
|
11
|
+
# It is expected to be used by AttrJson::Record and AttrJson::Model,
|
12
|
+
# you shouldn't need to interact with it directly.
|
13
|
+
#
|
14
|
+
# It is intentionally immutable to make it harder to accidentally mutate
|
15
|
+
# a registry shared with superclass in a `class_attribute`, instead of
|
16
|
+
# properly assigning a new modified registry.
|
17
|
+
#
|
18
|
+
# self.some_registry_attribute = self.some_registry_attribute.with(
|
19
|
+
# attr_definition_1, attr_definition_2
|
20
|
+
# )
|
21
|
+
# # => Returns a NEW AttributeDefinition object
|
22
|
+
#
|
23
|
+
# All references in code to "definition" are to a AttrJson::AttributeDefinition instance.
|
24
|
+
class Registry
|
25
|
+
def initialize(hash = {})
|
26
|
+
@name_to_definition = hash
|
27
|
+
@store_key_to_definition = {}
|
28
|
+
definitions.each { |d| store_key_index!(d) }
|
29
|
+
end
|
30
|
+
|
31
|
+
def fetch(key, *args, &block)
|
32
|
+
@name_to_definition.fetch(key.to_sym, *args, &block)
|
33
|
+
end
|
34
|
+
|
35
|
+
def [](key)
|
36
|
+
@name_to_definition[key.to_sym]
|
37
|
+
end
|
38
|
+
|
39
|
+
def has_attribute?(key)
|
40
|
+
@name_to_definition.has_key?(key.to_sym)
|
41
|
+
end
|
42
|
+
|
43
|
+
def type_for_attribute(key)
|
44
|
+
self[key].type
|
45
|
+
end
|
46
|
+
|
47
|
+
# Can return nil if none found.
|
48
|
+
def store_key_lookup(container_attribute, store_key)
|
49
|
+
@store_key_to_definition[container_attribute.to_s] &&
|
50
|
+
@store_key_to_definition[container_attribute.to_s][store_key.to_s]
|
51
|
+
end
|
52
|
+
|
53
|
+
def definitions
|
54
|
+
@name_to_definition.values
|
55
|
+
end
|
56
|
+
|
57
|
+
def container_attributes
|
58
|
+
@store_key_to_definition.keys.collect(&:to_s)
|
59
|
+
end
|
60
|
+
|
61
|
+
# This is how you register additional definitions, as a non-mutating
|
62
|
+
# return-a-copy operation.
|
63
|
+
def with(*definitions)
|
64
|
+
self.class.new(@name_to_definition).tap do |copied|
|
65
|
+
definitions.each do |defin|
|
66
|
+
copied.add!(defin)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
protected
|
72
|
+
|
73
|
+
def add!(definition)
|
74
|
+
if @name_to_definition.has_key?(definition.name)
|
75
|
+
raise ArgumentError, "Can't add, conflict with existing attribute name `#{definition.name.to_sym}`: #{@name_to_definition[definition.name].original_args}"
|
76
|
+
end
|
77
|
+
@name_to_definition[definition.name.to_sym] = definition
|
78
|
+
store_key_index!(definition)
|
79
|
+
end
|
80
|
+
|
81
|
+
def store_key_index!(definition)
|
82
|
+
container_hash = (@store_key_to_definition[definition.container_attribute.to_s] ||= {})
|
83
|
+
|
84
|
+
if container_hash.has_key?(definition.store_key.to_s)
|
85
|
+
existing = container_hash[definition.store_key.to_s]
|
86
|
+
raise ArgumentError, "Can't add, store key `#{definition.store_key}` conflicts with existing attribute: #{existing.original_args}"
|
87
|
+
end
|
88
|
+
|
89
|
+
container_hash[definition.store_key.to_s] = definition
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,270 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'active_model/type'
|
3
|
+
|
4
|
+
require 'attr_json/attribute_definition'
|
5
|
+
require 'attr_json/attribute_definition/registry'
|
6
|
+
|
7
|
+
require 'attr_json/type/model'
|
8
|
+
require 'attr_json/model/cocoon_compat'
|
9
|
+
|
10
|
+
module AttrJson
|
11
|
+
|
12
|
+
# Meant for use in a plain class, turns it into an ActiveModel::Model
|
13
|
+
# with attr_json support. NOT for use in an ActiveRecord::Base model,
|
14
|
+
# see `Record` for ActiveRecord use.
|
15
|
+
#
|
16
|
+
# Creates an ActiveModel object with _typed_ attributes, easily serializable
|
17
|
+
# to json, and with a corresponding ActiveModel::Type representing the class.
|
18
|
+
# Meant for use as an attribute of a AttrJson::Record. Can be nested,
|
19
|
+
# AttrJson::Models can have attributes that are other AttrJson::Models.
|
20
|
+
#
|
21
|
+
# @note Includes ActiveModel::Model whether you like it or not. TODO, should it?
|
22
|
+
#
|
23
|
+
# You can control what happens if you set an unknown key (one that you didn't
|
24
|
+
# register with `attr_json`) with the class attribute `attr_json_unknown_key`.
|
25
|
+
# * :raise (default) raise ActiveModel::UnknownAttributeError
|
26
|
+
# * :strip Ignore the unknown key and do not include it, without raising.
|
27
|
+
# * :allow Allow the unknown key and it's value to be in the serialized hash,
|
28
|
+
# and written to the database. May be useful for legacy data or columns
|
29
|
+
# that other software touches, to let unknown keys just flow through.
|
30
|
+
module Model
|
31
|
+
extend ActiveSupport::Concern
|
32
|
+
|
33
|
+
include ActiveModel::Model
|
34
|
+
include ActiveModel::Serialization
|
35
|
+
#include ActiveModel::Dirty
|
36
|
+
|
37
|
+
included do
|
38
|
+
if self < ActiveRecord::Base
|
39
|
+
raise TypeError, "AttrJson::Model is not for an ActiveRecord::Base model. #{self} appears to be one. Are you looking for ::AttrJson::Record?"
|
40
|
+
end
|
41
|
+
|
42
|
+
class_attribute :attr_json_registry, instance_accessor: false
|
43
|
+
self.attr_json_registry = ::AttrJson::AttributeDefinition::Registry.new
|
44
|
+
|
45
|
+
# :raise, :strip, :allow. :raise is default. Is there some way to enforce this.
|
46
|
+
class_attribute :attr_json_unknown_key
|
47
|
+
self.attr_json_unknown_key ||= :raise
|
48
|
+
end
|
49
|
+
|
50
|
+
class_methods do
|
51
|
+
# Like `.new`, but translate store keys in hash
|
52
|
+
def new_from_serializable(attributes = {})
|
53
|
+
attributes = attributes.transform_keys do |key|
|
54
|
+
# store keys in arguments get translated to attribute names on initialize.
|
55
|
+
if attribute_def = self.attr_json_registry.store_key_lookup("", key.to_s)
|
56
|
+
attribute_def.name.to_s
|
57
|
+
else
|
58
|
+
key
|
59
|
+
end
|
60
|
+
end
|
61
|
+
self.new(attributes)
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_type
|
65
|
+
@type ||= AttrJson::Type::Model.new(self)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Type can be an instance of an ActiveModel::Type::Value subclass, or a symbol that will
|
69
|
+
# be looked up in `ActiveModel::Type.lookup`
|
70
|
+
#
|
71
|
+
# @param name [Symbol,String] name of attribute
|
72
|
+
#
|
73
|
+
# @param type [ActiveModel::Type::Value] An instance of an ActiveModel::Type::Value (or subclass)
|
74
|
+
#
|
75
|
+
# @option options [Boolean] :array (false) Make this attribute an array of given type.
|
76
|
+
#
|
77
|
+
# @option options [Object] :default (nil) Default value, if a Proc object it will be #call'd
|
78
|
+
# for default.
|
79
|
+
#
|
80
|
+
# @option options [String,Symbol] :store_key (nil) Serialize to JSON using
|
81
|
+
# given store_key, rather than name as would be usual.
|
82
|
+
#
|
83
|
+
# @option options [Boolean] :validate (true) Create an ActiveRecord::Validations::AssociatedValidator so
|
84
|
+
# validation errors on the attributes post up to self.
|
85
|
+
def attr_json(name, type, **options)
|
86
|
+
options.assert_valid_keys(*(AttributeDefinition::VALID_OPTIONS - [:container_attribute] + [:validate]))
|
87
|
+
|
88
|
+
self.attr_json_registry = attr_json_registry.with(
|
89
|
+
AttributeDefinition.new(name.to_sym, type, options.except(:validate))
|
90
|
+
)
|
91
|
+
|
92
|
+
# By default, automatically validate nested models
|
93
|
+
if type.kind_of?(AttrJson::Type::Model) && options[:validate] != false
|
94
|
+
# Yes. we're passing an ActiveRecord::Validations validator, but
|
95
|
+
# it works fine for ActiveModel. If this changes in the future, tests will catch.
|
96
|
+
self.validates_with ActiveRecord::Validations::AssociatedValidator, attributes: [name.to_sym]
|
97
|
+
end
|
98
|
+
|
99
|
+
_attr_jsons_module.module_eval do
|
100
|
+
define_method("#{name}=") do |value|
|
101
|
+
_attr_json_write(name.to_s, value)
|
102
|
+
end
|
103
|
+
|
104
|
+
define_method("#{name}") do
|
105
|
+
attributes[name.to_s]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# This should kind of be considered 'protected', but the semantics
|
111
|
+
# of how we want to call it don't give us a visibility modifier that works.
|
112
|
+
# Prob means refactoring called for. TODO?
|
113
|
+
def fill_in_defaults(hash)
|
114
|
+
# Only if we need to mutate it to add defaults, we'll dup it first. deep_dup not neccesary
|
115
|
+
# since we're only modifying top-level here.
|
116
|
+
duped = false
|
117
|
+
attr_json_registry.definitions.each do |definition|
|
118
|
+
if definition.has_default? && ! (hash.has_key?(definition.store_key.to_s) || hash.has_key?(definition.store_key.to_sym))
|
119
|
+
unless duped
|
120
|
+
hash = hash.dup
|
121
|
+
duped = true
|
122
|
+
end
|
123
|
+
|
124
|
+
hash[definition.store_key] = definition.provide_default!
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
hash
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
# Define an anonymous module and include it, so can still be easily
|
134
|
+
# overridden by concrete class. Design cribbed from ActiveRecord::Store
|
135
|
+
# https://github.com/rails/rails/blob/4590d7729e241cb7f66e018a2a9759cb3baa36e5/activerecord/lib/active_record/store.rb
|
136
|
+
def _attr_jsons_module # :nodoc:
|
137
|
+
@_attr_jsons_module ||= begin
|
138
|
+
mod = Module.new
|
139
|
+
include mod
|
140
|
+
mod
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def initialize(attributes = {})
|
146
|
+
if !attributes.respond_to?(:transform_keys)
|
147
|
+
raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
|
148
|
+
end
|
149
|
+
|
150
|
+
super(self.class.fill_in_defaults(attributes))
|
151
|
+
end
|
152
|
+
|
153
|
+
def attributes
|
154
|
+
@attributes ||= {}
|
155
|
+
end
|
156
|
+
|
157
|
+
# ActiveModel method, called in initialize. overridden.
|
158
|
+
# from https://github.com/rails/rails/blob/42a16a4d6514f28e05f1c22a5f9125d194d9c7cb/activemodel/lib/active_model/attribute_assignment.rb
|
159
|
+
def assign_attributes(new_attributes)
|
160
|
+
if !new_attributes.respond_to?(:stringify_keys)
|
161
|
+
raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
|
162
|
+
end
|
163
|
+
return if new_attributes.empty?
|
164
|
+
|
165
|
+
# stringify keys just like https://github.com/rails/rails/blob/4f99a2186479d5f77460622f2c0f37708b3ec1bc/activemodel/lib/active_model/attribute_assignment.rb#L34
|
166
|
+
new_attributes.stringify_keys.each do |k, v|
|
167
|
+
setter = :"#{k}="
|
168
|
+
if respond_to?(setter)
|
169
|
+
public_send(setter, v)
|
170
|
+
else
|
171
|
+
_attr_json_write_unknown_attribute(k, v)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# This attribute from ActiveRecord makes SimpleForm happy, and able to detect
|
177
|
+
# type.
|
178
|
+
def type_for_attribute(attr_name)
|
179
|
+
self.class.attr_json_registry.type_for_attribute(attr_name)
|
180
|
+
end
|
181
|
+
|
182
|
+
# This attribute from ActiveRecord make SimpleForm happy, and able to detect
|
183
|
+
# type.
|
184
|
+
def has_attribute?(str)
|
185
|
+
self.class.attr_json_registry.has_attribute?(str)
|
186
|
+
end
|
187
|
+
|
188
|
+
# Override from ActiveModel::Serialization to #serialize
|
189
|
+
# by type to make sure any values set directly on hash still
|
190
|
+
# get properly type-serialized.
|
191
|
+
def serializable_hash(*options)
|
192
|
+
super.collect do |key, value|
|
193
|
+
if attribute_def = self.class.attr_json_registry[key.to_sym]
|
194
|
+
key = attribute_def.store_key
|
195
|
+
if value.kind_of?(Time) || value.kind_of?(DateTime)
|
196
|
+
value = value.utc.change(usec: 0)
|
197
|
+
end
|
198
|
+
|
199
|
+
value = attribute_def.serialize(value)
|
200
|
+
end
|
201
|
+
# Do we need unknown key handling here? Apparently not?
|
202
|
+
[key, value]
|
203
|
+
end.to_h
|
204
|
+
end
|
205
|
+
|
206
|
+
# ActiveRecord JSON serialization will insist on calling
|
207
|
+
# this, instead of the specified type's #serialize, at least in some cases.
|
208
|
+
# So it's important we define it -- the default #as_json added by ActiveSupport
|
209
|
+
# will serialize all instance variables, which is not what we want.
|
210
|
+
def as_json(*options)
|
211
|
+
serializable_hash(*options)
|
212
|
+
end
|
213
|
+
|
214
|
+
# We deep_dup on #to_h, you want attributes unduped, ask for #attributes.
|
215
|
+
def to_h
|
216
|
+
attributes.deep_dup
|
217
|
+
end
|
218
|
+
|
219
|
+
# Two AttrJson::Model objects are equal if they are the same class
|
220
|
+
# or one is a subclass of the other, AND their #attributes are equal.
|
221
|
+
# TODO: Should we allow subclasses to be equal, or should they have to be the
|
222
|
+
# exact same class?
|
223
|
+
def ==(other_object)
|
224
|
+
(other_object.is_a?(self.class) || self.is_a?(other_object.class)) &&
|
225
|
+
other_object.attributes == self.attributes
|
226
|
+
end
|
227
|
+
|
228
|
+
# ActiveRecord objects [have a](https://github.com/rails/rails/blob/v5.1.5/activerecord/lib/active_record/nested_attributes.rb#L367-L374)
|
229
|
+
# `_destroy`, related to `marked_for_destruction?` functionality used with AR nested attributes.
|
230
|
+
# We don't mark for destruction, our nested attributes implementation just deletes immediately,
|
231
|
+
# but having this simple method always returning false makes things work more compatibly
|
232
|
+
# and smoothly with standard code for nested attributes deletion in form builders.
|
233
|
+
def _destroy
|
234
|
+
false
|
235
|
+
end
|
236
|
+
|
237
|
+
private
|
238
|
+
|
239
|
+
def _attr_json_write(key, value)
|
240
|
+
if attribute_def = self.class.attr_json_registry[key.to_sym]
|
241
|
+
attributes[key.to_s] = attribute_def.cast(value)
|
242
|
+
else
|
243
|
+
# TODO, strict mode, ignore, raise, allow.
|
244
|
+
attributes[key.to_s] = value
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
|
249
|
+
def _attr_json_write_unknown_attribute(key, value)
|
250
|
+
case attr_json_unknown_key
|
251
|
+
when :strip
|
252
|
+
# drop it, no-op
|
253
|
+
when :allow
|
254
|
+
# just put it in the hash and let standard JSON casting have it
|
255
|
+
_attr_json_write(key, value)
|
256
|
+
else
|
257
|
+
# default, :raise
|
258
|
+
raise ActiveModel::UnknownAttributeError.new(self, key)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
# ActiveModel override.
|
263
|
+
# Don't take from instance variables, take from the attributes
|
264
|
+
# hash itself. Docs suggest we can override this for this very
|
265
|
+
# use case: https://github.com/rails/rails/blob/e1e3be7c02acb0facbf81a97bbfe6d1a6e9ca598/activemodel/lib/active_model/serialization.rb#L152-L168
|
266
|
+
def read_attribute_for_serialization(key)
|
267
|
+
attributes[key]
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|