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
@@ -0,0 +1,27 @@
|
|
1
|
+
module AttrJson
|
2
|
+
module Model
|
3
|
+
# Meant for mix-in in a AttrJson::Model class, defines some methods that
|
4
|
+
# [cocoon](https://github.com/nathanvda/cocoon) insists upon, even though the
|
5
|
+
# implementation doesn't really matter for getting cocoon to work with our Models
|
6
|
+
# as nested models in forms with cocoon -- the methods just need to be there.
|
7
|
+
module CocoonCompat
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
class_methods do
|
11
|
+
# cocoon wants this. PR to cocoon to not?
|
12
|
+
def reflect_on_association(*args)
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# cocoon insists on asking, we don't know the answer, we'll just say 'no'
|
18
|
+
# PR to cocoon to not insist on this?
|
19
|
+
def new_record?
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
def marked_for_destruction?
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'attr_json/nested_attributes/builder'
|
2
|
+
require 'attr_json/nested_attributes/writer'
|
3
|
+
|
4
|
+
module AttrJson
|
5
|
+
# The implementation is based on ActiveRecord::NestedAttributes, from
|
6
|
+
# https://github.com/rails/rails/blob/a45f234b028fd4dda5338e5073a3bf2b8bf2c6fd/activerecord/lib/active_record/nested_attributes.rb
|
7
|
+
#
|
8
|
+
# Re-used, and customized/overrode methods to match our implementation.
|
9
|
+
# Copied over some implementation so we can use in ActiveModel's that original
|
10
|
+
# isn't compatible with.
|
11
|
+
# The original is pretty well put together and has had very low churn history.
|
12
|
+
#
|
13
|
+
#
|
14
|
+
# Much of the AR implementation, copied, just works,
|
15
|
+
# if we define `'#{attribute_name}_attributes='` methods that work. That's mostly what
|
16
|
+
# we have to do here.
|
17
|
+
#
|
18
|
+
# Unlike AR, we try to put most of our implementation in seperate
|
19
|
+
# implementation helper instances, instead of adding a bazillion methods to the model itself.
|
20
|
+
module NestedAttributes
|
21
|
+
extend ActiveSupport::Concern
|
22
|
+
|
23
|
+
class_methods do
|
24
|
+
# Much like ActiveRecord `accepts_nested_attributes_for`, but used with embedded
|
25
|
+
# AttrJson::Model-type attributes (single or array). See doc page on Forms support.
|
26
|
+
#
|
27
|
+
# Note some AR options are _not_ supported.
|
28
|
+
#
|
29
|
+
# * _allow_destroy_, no such option. Effectively always true, doesn't make sense to try to gate this with our implementation.
|
30
|
+
# * _update_only_, no such option. Not really relevant to this architecture where you're embedded models have no independent existence.
|
31
|
+
#
|
32
|
+
# @overload attr_json_accepts_nested_attributes_for(define_build_method: true, reject_if: nil, limit: nil)
|
33
|
+
# @param define_build_method [Boolean] Default true, provide `build_attribute_name`
|
34
|
+
# method that works like you expect. [Cocoon](https://github.com/nathanvda/cocoon),
|
35
|
+
# for example, requires this. When true, you will get `model.build_#{attr_name}`
|
36
|
+
# methods. For array attributes, the attr_name will be singularized, as AR does.
|
37
|
+
# @param reject_if [Symbol,Proc] Allows you to specify a Proc or a Symbol pointing to a method
|
38
|
+
# that checks whether a record should be built for a certain attribute
|
39
|
+
# hash. Much like in AR accepts_nested_attributes_for.
|
40
|
+
# @param limit [Integer,Proc,Symbol] Allows you to specify the maximum number of associated records that
|
41
|
+
# can be processed with the nested attributes. Much like AR accepts_nested_attributes for.
|
42
|
+
def attr_json_accepts_nested_attributes_for(*attr_names)
|
43
|
+
options = { define_build_method: true }
|
44
|
+
options.update(attr_names.extract_options!)
|
45
|
+
options.assert_valid_keys(:reject_if, :limit, :define_build_method)
|
46
|
+
options[:reject_if] = ActiveRecord::NestedAttributes::ClassMethods::REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank
|
47
|
+
|
48
|
+
unless respond_to?(:nested_attributes_options)
|
49
|
+
# Add it when we're in a AttrJson::Model. In an ActiveRecord::Base we'll just use the
|
50
|
+
# existing one, it'll be okay.
|
51
|
+
# https://github.com/rails/rails/blob/c14deceb9f36f82cd5ca3db214d85e1642eb0bfd/activerecord/lib/active_record/nested_attributes.rb#L16
|
52
|
+
class_attribute :nested_attributes_options, instance_writer: false
|
53
|
+
self.nested_attributes_options ||= {}
|
54
|
+
end
|
55
|
+
|
56
|
+
attr_names.each do |attr_name|
|
57
|
+
attr_def = attr_json_registry[attr_name]
|
58
|
+
|
59
|
+
unless attr_def
|
60
|
+
raise ArgumentError, "No attr_json found for name '#{attr_name}'. Has it been defined yet?"
|
61
|
+
end
|
62
|
+
|
63
|
+
# We're sharing AR class attr in an AR, or using our own in a Model.
|
64
|
+
nested_attributes_options = self.nested_attributes_options.dup
|
65
|
+
nested_attributes_options[attr_name.to_sym] = options
|
66
|
+
self.nested_attributes_options = nested_attributes_options
|
67
|
+
|
68
|
+
_attr_jsons_module.module_eval do
|
69
|
+
if method_defined?(:"#{attr_name}_attributes=")
|
70
|
+
remove_method(:"#{attr_name}_attributes=")
|
71
|
+
end
|
72
|
+
define_method "#{attr_name}_attributes=" do |attributes|
|
73
|
+
Writer.new(self, attr_name).assign_nested_attributes(attributes)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
if options[:define_build_method]
|
78
|
+
_attr_jsons_module.module_eval do
|
79
|
+
build_method_name = "build_#{attr_name.to_s.singularize}"
|
80
|
+
if method_defined?(build_method_name)
|
81
|
+
remove_method(build_method_name)
|
82
|
+
end
|
83
|
+
define_method build_method_name do |params = {}|
|
84
|
+
Builder.new(self, attr_name).build(params)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module AttrJson
|
2
|
+
module NestedAttributes
|
3
|
+
# Implementation of `build_` methods, called by the `build_` methods
|
4
|
+
# {NestedAttributes} adds.
|
5
|
+
class Builder
|
6
|
+
attr_reader :model, :attr_name, :attr_def
|
7
|
+
|
8
|
+
def initialize(model, attr_name)
|
9
|
+
@model, @attr_name = model, attr_name,
|
10
|
+
@attr_def = model.class.attr_json_registry[attr_name]
|
11
|
+
end
|
12
|
+
|
13
|
+
def build(params = {})
|
14
|
+
if attr_def.array_type?
|
15
|
+
model.send("#{attr_name}=", (model.send(attr_name) || []) + [params])
|
16
|
+
return model.send("#{attr_name}").last
|
17
|
+
else
|
18
|
+
model.send("#{attr_name}=", params)
|
19
|
+
return model.send("#{attr_name}")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module AttrJson
|
2
|
+
module NestedAttributes
|
3
|
+
# Rails has a weird "multiparameter attribute" thing, that is used for simple_form's
|
4
|
+
# date/time html entry (datetime may be ALL it's ever been used for in Rails!),
|
5
|
+
# using weird parameters in the HTTP query params like "dateattribute(2i)".
|
6
|
+
# It is weird code, and I do NOT really understand the implementation, but it's also
|
7
|
+
# very low-churn, hasn't changed much in recent Rails history.
|
8
|
+
#
|
9
|
+
# In Rails at present it's only on ActiveRecord, we need it used on our AttrJson::Models
|
10
|
+
# too, so we copy and paste extract it here, from:
|
11
|
+
# https://github.com/rails/rails/blob/42a16a4d6514f28e05f1c22a5f9125d194d9c7cb/activerecord/lib/active_record/attribute_assignment.rb
|
12
|
+
#
|
13
|
+
# We only use it in the `#{attr_name}_attributes=` methods added by {NestedAttributes},
|
14
|
+
# that's enough to get what we need for support of this stuff in our stuff, for form submisisons
|
15
|
+
# using rails-style date/time inputs as used eg in simple_form. And then we don't
|
16
|
+
# need to polute anything outside of NestedAttributes module with this crazy stuff.
|
17
|
+
class MultiparameterAttributeWriter
|
18
|
+
attr_reader :model
|
19
|
+
def initialize(model)
|
20
|
+
@model = model
|
21
|
+
end
|
22
|
+
|
23
|
+
# Copied from Rails. https://github.com/rails/rails/blob/42a16a4d6514f28e05f1c22a5f9125d194d9c7cb/activerecord/lib/active_record/attribute_assignment.rb#L39
|
24
|
+
#
|
25
|
+
# Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
|
26
|
+
# by calling new on the column type or aggregation type (through composed_of) object with these parameters.
|
27
|
+
# So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
|
28
|
+
# written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
|
29
|
+
# parentheses to have the parameters typecasted before they're used in the constructor. Use i for Integer and
|
30
|
+
# f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+.
|
31
|
+
def assign_multiparameter_attributes(pairs)
|
32
|
+
execute_callstack_for_multiparameter_attributes(
|
33
|
+
extract_callstack_for_multiparameter_attributes(pairs)
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
|
39
|
+
# copied from Rails https://github.com/rails/rails/blob/42a16a4d6514f28e05f1c22a5f9125d194d9c7cb/activerecord/lib/active_record/attribute_assignment.rb#L45
|
40
|
+
def execute_callstack_for_multiparameter_attributes(callstack)
|
41
|
+
errors = []
|
42
|
+
callstack.each do |name, values_with_empty_parameters|
|
43
|
+
begin
|
44
|
+
if values_with_empty_parameters.each_value.all?(&:nil?)
|
45
|
+
values = nil
|
46
|
+
else
|
47
|
+
values = values_with_empty_parameters
|
48
|
+
end
|
49
|
+
model.send("#{name}=", values)
|
50
|
+
rescue => ex
|
51
|
+
errors << ActiveRecord::AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
unless errors.empty?
|
55
|
+
error_descriptions = errors.map(&:message).join(",")
|
56
|
+
raise ActiveRecord::MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# copied from Rails https://github.com/rails/rails/blob/42a16a4d6514f28e05f1c22a5f9125d194d9c7cb/activerecord/lib/active_record/attribute_assignment.rb#L65
|
61
|
+
def extract_callstack_for_multiparameter_attributes(pairs)
|
62
|
+
attributes = {}
|
63
|
+
|
64
|
+
pairs.each do |(multiparameter_name, value)|
|
65
|
+
attribute_name = multiparameter_name.split("(").first
|
66
|
+
attributes[attribute_name] ||= {}
|
67
|
+
|
68
|
+
parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
|
69
|
+
attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
|
70
|
+
end
|
71
|
+
|
72
|
+
attributes
|
73
|
+
end
|
74
|
+
|
75
|
+
# copied from Rails https://github.com/rails/rails/blob/42a16a4d6514f28e05f1c22a5f9125d194d9c7cb/activerecord/lib/active_record/attribute_assignment.rb#L79
|
76
|
+
def type_cast_attribute_value(multiparameter_name, value)
|
77
|
+
multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value
|
78
|
+
end
|
79
|
+
|
80
|
+
# copied from Rails https://github.com/rails/rails/blob/42a16a4d6514f28e05f1c22a5f9125d194d9c7cb/activerecord/lib/active_record/attribute_assignment.rb#L83
|
81
|
+
def find_parameter_position(multiparameter_name)
|
82
|
+
multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,215 @@
|
|
1
|
+
require 'attr_json/nested_attributes/multiparameter_attribute_writer'
|
2
|
+
|
3
|
+
module AttrJson
|
4
|
+
module NestedAttributes
|
5
|
+
# Implementation of `assign_nested_attributes` methods, called by the model
|
6
|
+
# method of that name that {NestedAttributes} adds.
|
7
|
+
class Writer
|
8
|
+
attr_reader :model, :attr_name, :attr_def
|
9
|
+
|
10
|
+
def initialize(model, attr_name)
|
11
|
+
@model, @attr_name = model, attr_name
|
12
|
+
@attr_def = model.class.attr_json_registry.fetch(attr_name)
|
13
|
+
end
|
14
|
+
|
15
|
+
delegate :nested_attributes_options, to: :model
|
16
|
+
|
17
|
+
def assign_nested_attributes(attributes)
|
18
|
+
if attr_def.array_type?
|
19
|
+
assign_nested_attributes_for_model_array(attributes)
|
20
|
+
else
|
21
|
+
assign_nested_attributes_for_single_model(attributes)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
def model_send(method, *args)
|
28
|
+
model.send(method, *args)
|
29
|
+
end
|
30
|
+
|
31
|
+
def unassignable_keys
|
32
|
+
if model.class.const_defined?(:UNASSIGNABLE_KEYS)
|
33
|
+
# https://github.com/rails/rails/blob/a45f234b028fd4dda5338e5073a3bf2b8bf2c6fd/activerecord/lib/active_record/nested_attributes.rb#L392
|
34
|
+
(model.class)::UNASSIGNABLE_KEYS
|
35
|
+
else
|
36
|
+
# No need to mark "id" as unassignable in our AttrJson::Model-based nested models
|
37
|
+
["_destroy"]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Copied with signficant modifications from:
|
42
|
+
# https://github.com/rails/rails/blob/master/activerecord/lib/active_record/nested_attributes.rb#L407
|
43
|
+
def assign_nested_attributes_for_single_model(attributes)
|
44
|
+
options = nested_attributes_options[attr_name]
|
45
|
+
if attributes.respond_to?(:permitted?)
|
46
|
+
attributes = attributes.to_h
|
47
|
+
end
|
48
|
+
attributes = attributes.with_indifferent_access
|
49
|
+
|
50
|
+
existing_record = model_send(attr_name)
|
51
|
+
|
52
|
+
if existing_record && has_destroy_flag?(attributes)
|
53
|
+
# We don't have mark_for_destroy like in AR we just
|
54
|
+
# set it to nil to eliminate it in the AttrJson, that's it.
|
55
|
+
model_send("#{attr_def.name}=", nil)
|
56
|
+
|
57
|
+
return model
|
58
|
+
end
|
59
|
+
|
60
|
+
multi_parameter_attributes = extract_multi_parameter_attributes(attributes)
|
61
|
+
|
62
|
+
if existing_record
|
63
|
+
existing_record.assign_attributes(attributes.except(*unassignable_keys))
|
64
|
+
elsif !reject_new_record?(attr_name, attributes)
|
65
|
+
# doesn't exist yet, using the setter casting will build it for us
|
66
|
+
# automatically.
|
67
|
+
model_send("#{attr_name}=", attributes.except(*unassignable_keys))
|
68
|
+
end
|
69
|
+
|
70
|
+
if multi_parameter_attributes.present?
|
71
|
+
MultiparameterAttributeWriter.new(
|
72
|
+
model.send(attr_name)
|
73
|
+
).assign_multiparameter_attributes(multi_parameter_attributes)
|
74
|
+
end
|
75
|
+
|
76
|
+
return model
|
77
|
+
end
|
78
|
+
|
79
|
+
# Copied with significant modification from
|
80
|
+
# https://github.com/rails/rails/blob/master/activerecord/lib/active_record/nested_attributes.rb#L466
|
81
|
+
def assign_nested_attributes_for_model_array(attributes_collection)
|
82
|
+
options = nested_attributes_options[attr_name]
|
83
|
+
|
84
|
+
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
|
85
|
+
raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
|
86
|
+
end
|
87
|
+
|
88
|
+
check_record_limit!(options[:limit], attributes_collection)
|
89
|
+
|
90
|
+
# Dunno what this is about but it's from ActiveRecord::NestedAttributes
|
91
|
+
if attributes_collection.is_a? Hash
|
92
|
+
keys = attributes_collection.keys
|
93
|
+
attributes_collection = if keys.include?("id") || keys.include?(:id)
|
94
|
+
[attributes_collection]
|
95
|
+
else
|
96
|
+
attributes_collection.values
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
if attributes_collection.respond_to?(:permitted?)
|
101
|
+
attributes_collection = attributes_collection.to_h
|
102
|
+
end
|
103
|
+
attributes_collection.collect!(&:stringify_keys)
|
104
|
+
|
105
|
+
# remove ones marked with _destroy key, or rejected
|
106
|
+
attributes_collection = attributes_collection.reject do |hash|
|
107
|
+
hash.respond_to?(:[]) && (has_destroy_flag?(hash) || reject_new_record?(attr_name, hash))
|
108
|
+
end
|
109
|
+
|
110
|
+
attributes_collection.collect! { |h| h.except(*unassignable_keys) }
|
111
|
+
|
112
|
+
multi_param_attr_array = attributes_collection.collect do |hash|
|
113
|
+
extract_multi_parameter_attributes(hash)
|
114
|
+
end
|
115
|
+
|
116
|
+
# the magic of our type casting, this should 'just work', we'll have
|
117
|
+
# a NEW array of models, unlike AR we don't re-use existing nested models
|
118
|
+
# on assignment.
|
119
|
+
model_send("#{attr_name}=", attributes_collection)
|
120
|
+
|
121
|
+
multi_param_attr_array.each_with_index do |multi_param_attrs, i|
|
122
|
+
unless multi_param_attrs.empty?
|
123
|
+
MultiparameterAttributeWriter.new(
|
124
|
+
model_send(attr_name)[i]
|
125
|
+
).assign_multiparameter_attributes(multi_param_attrs)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
return model
|
130
|
+
end
|
131
|
+
|
132
|
+
# Copied from ActiveRecord::NestedAttributes:
|
133
|
+
#
|
134
|
+
# Determines if a record with the particular +attributes+ should be
|
135
|
+
# rejected by calling the reject_if Symbol or Proc (if defined).
|
136
|
+
# The reject_if option is defined by +accepts_nested_attributes_for+.
|
137
|
+
#
|
138
|
+
# Returns false if there is a +destroy_flag+ on the attributes.
|
139
|
+
def call_reject_if(association_name, attributes)
|
140
|
+
return false if will_be_destroyed?(association_name, attributes)
|
141
|
+
|
142
|
+
case callback = nested_attributes_options[association_name][:reject_if]
|
143
|
+
when Symbol
|
144
|
+
method(callback).arity == 0 ? send(callback) : send(callback, attributes)
|
145
|
+
when Proc
|
146
|
+
callback.call(attributes)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Copied from ActiveRecord::NestedAttributes unaltered.
|
151
|
+
#
|
152
|
+
# Determines if a hash contains a truthy _destroy key.
|
153
|
+
def has_destroy_flag?(hash)
|
154
|
+
ActiveModel::Type::Boolean.new.cast(hash["_destroy"])
|
155
|
+
end
|
156
|
+
|
157
|
+
# Copied from ActiveRecord::NestedAttributes
|
158
|
+
#
|
159
|
+
# Takes in a limit and checks if the attributes_collection has too many
|
160
|
+
# records. It accepts limit in the form of symbol, proc, or
|
161
|
+
# number-like object (anything that can be compared with an integer).
|
162
|
+
#
|
163
|
+
# Raises TooManyRecords error if the attributes_collection is
|
164
|
+
# larger than the limit.
|
165
|
+
def check_record_limit!(limit, attributes_collection)
|
166
|
+
if limit
|
167
|
+
limit = \
|
168
|
+
case limit
|
169
|
+
when Symbol
|
170
|
+
model.send(limit)
|
171
|
+
when Proc
|
172
|
+
limit.call
|
173
|
+
else
|
174
|
+
limit
|
175
|
+
end
|
176
|
+
|
177
|
+
if limit && attributes_collection.size > limit
|
178
|
+
raise ActiveRecord::NestedAttributes::TooManyRecords, "Maximum #{limit} records are allowed. Got #{attributes_collection.size} records instead."
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# Copied from ActiveRecord::NestedAttributes
|
184
|
+
#
|
185
|
+
# Determines if a new record should be rejected by checking
|
186
|
+
# has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this
|
187
|
+
# association and evaluates to +true+.
|
188
|
+
def reject_new_record?(association_name, attributes)
|
189
|
+
will_be_destroyed?(association_name, attributes) || call_reject_if(association_name, attributes)
|
190
|
+
end
|
191
|
+
|
192
|
+
# Unlike ActiveRecord, we don't have an allow_destroy option, so
|
193
|
+
# this is just `has_destroy_flag?`
|
194
|
+
def will_be_destroyed?(association_name, attributes)
|
195
|
+
has_destroy_flag?(attributes)
|
196
|
+
end
|
197
|
+
|
198
|
+
# mutates attributes passsed in to remove multiparameter attributes,
|
199
|
+
# and returns multiparam in their own hash. Based on:
|
200
|
+
# https://github.com/rails/rails/blob/42a16a4d6514f28e05f1c22a5f9125d194d9c7cb/activerecord/lib/active_record/attribute_assignment.rb#L15-L25
|
201
|
+
# See AttrJson::NestedAttributes::MultiparameterAttributeWriter
|
202
|
+
def extract_multi_parameter_attributes(attributes)
|
203
|
+
multi_parameter_attributes = {}
|
204
|
+
|
205
|
+
attributes.each do |k, v|
|
206
|
+
if k.include?("(")
|
207
|
+
multi_parameter_attributes[k] = attributes.delete(k)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
return multi_parameter_attributes
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|