attr_json 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|