snusnu-dm-accepts_nested_attributes 0.0.6 → 0.10.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.
- data/Manifest.txt +13 -8
- data/Rakefile +2 -3
- data/TODO +5 -0
- data/lib/dm-accepts_nested_attributes.rb +7 -14
- data/lib/dm-accepts_nested_attributes/error_collecting.rb +35 -0
- data/lib/dm-accepts_nested_attributes/model.rb +132 -0
- data/lib/dm-accepts_nested_attributes/resource.rb +218 -29
- data/lib/dm-accepts_nested_attributes/save.rb +13 -0
- data/lib/dm-accepts_nested_attributes/transactional_save.rb +15 -0
- data/lib/dm-accepts_nested_attributes/version.rb +1 -1
- data/spec/fixtures/person.rb +8 -0
- data/spec/fixtures/profile.rb +9 -0
- data/spec/fixtures/project.rb +8 -0
- data/spec/fixtures/project_membership.rb +8 -0
- data/spec/fixtures/task.rb +9 -0
- data/spec/integration/belongs_to_spec.rb +10 -134
- data/spec/integration/has_1_spec.rb +9 -121
- data/spec/integration/has_n_spec.rb +10 -149
- data/spec/integration/has_n_through_spec.rb +10 -162
- data/spec/{shared → lib}/rspec_tmbundle_support.rb +1 -1
- data/spec/shared/belongs_to_spec.rb +127 -0
- data/spec/shared/has_1_spec.rb +103 -0
- data/spec/shared/has_n_spec.rb +114 -0
- data/spec/shared/has_n_through_spec.rb +139 -0
- data/spec/spec_helper.rb +12 -9
- data/spec/unit/accepts_nested_attributes_for_spec.rb +39 -118
- data/tasks/changelog.rb +18 -0
- data/tasks/spec.rb +0 -1
- metadata +19 -14
- data/lib/dm-accepts_nested_attributes/association_proxies.rb +0 -55
- data/lib/dm-accepts_nested_attributes/association_validation.rb +0 -49
- data/lib/dm-accepts_nested_attributes/nested_attributes.rb +0 -350
- data/spec/unit/resource_spec.rb +0 -174
data/tasks/changelog.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
desc 'update changelog'
|
2
|
+
task :changelog do
|
3
|
+
File.open('CHANGELOG', 'w+') do |changelog|
|
4
|
+
`git log -z --abbrev-commit`.split("\0").each do |commit|
|
5
|
+
next if commit =~ /^Merge: \d*/
|
6
|
+
ref, author, time, _, title, _, message = commit.split("\n", 7)
|
7
|
+
ref = ref[/commit ([0-9a-f]+)/, 1]
|
8
|
+
author = author[/Author: (.*)/, 1].strip
|
9
|
+
time = Time.parse(time[/Date: (.*)/, 1]).utc
|
10
|
+
title.strip!
|
11
|
+
|
12
|
+
changelog.puts "[#{ref} | #{time}] #{author}"
|
13
|
+
changelog.puts '', " * #{title}"
|
14
|
+
changelog.puts '', message.rstrip if message
|
15
|
+
changelog.puts
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/tasks/spec.rb
CHANGED
@@ -8,7 +8,6 @@ begin
|
|
8
8
|
desc 'Run specifications'
|
9
9
|
Spec::Rake::SpecTask.new(:spec) do |t|
|
10
10
|
t.spec_opts << '--options' << 'spec/spec.opts' if File.exists?('spec/spec.opts')
|
11
|
-
t.spec_files = Pathname.glob((ROOT + 'spec/**/*_spec.rb').to_s).map { |f| f.to_s }
|
12
11
|
|
13
12
|
begin
|
14
13
|
gem 'rcov', '~>0.8'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: snusnu-dm-accepts_nested_attributes
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- "Martin Gamsj\xC3\xA4ger"
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-06-17 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -20,17 +20,17 @@ dependencies:
|
|
20
20
|
requirements:
|
21
21
|
- - ">="
|
22
22
|
- !ruby/object:Gem::Version
|
23
|
-
version: 0.
|
23
|
+
version: 0.10.0
|
24
24
|
version:
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
|
-
name:
|
26
|
+
name: dm-validations
|
27
27
|
type: :runtime
|
28
28
|
version_requirement:
|
29
29
|
version_requirements: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 0.10.0
|
34
34
|
version:
|
35
35
|
description: A DataMapper plugin that adds the possibility to perform nested model attribute assignment
|
36
36
|
email:
|
@@ -52,25 +52,30 @@ files:
|
|
52
52
|
- TODO
|
53
53
|
- CHANGELOG
|
54
54
|
- lib/dm-accepts_nested_attributes.rb
|
55
|
-
- lib/dm-accepts_nested_attributes/
|
56
|
-
- lib/dm-accepts_nested_attributes/
|
57
|
-
- lib/dm-accepts_nested_attributes/nested_attributes.rb
|
55
|
+
- lib/dm-accepts_nested_attributes/error_collecting.rb
|
56
|
+
- lib/dm-accepts_nested_attributes/model.rb
|
58
57
|
- lib/dm-accepts_nested_attributes/resource.rb
|
58
|
+
- lib/dm-accepts_nested_attributes/save.rb
|
59
|
+
- lib/dm-accepts_nested_attributes/transactional_save.rb
|
59
60
|
- lib/dm-accepts_nested_attributes/version.rb
|
61
|
+
- spec/spec.opts
|
62
|
+
- spec/spec_helper.rb
|
63
|
+
- spec/lib/rspec_tmbundle_support.rb
|
60
64
|
- spec/fixtures/person.rb
|
61
65
|
- spec/fixtures/profile.rb
|
62
66
|
- spec/fixtures/project.rb
|
63
67
|
- spec/fixtures/project_membership.rb
|
64
68
|
- spec/fixtures/task.rb
|
69
|
+
- spec/shared/belongs_to_spec.rb
|
70
|
+
- spec/shared/has_1_spec.rb
|
71
|
+
- spec/shared/has_n_spec.rb
|
72
|
+
- spec/shared/has_n_through_spec.rb
|
73
|
+
- spec/unit/accepts_nested_attributes_for_spec.rb
|
65
74
|
- spec/integration/belongs_to_spec.rb
|
66
75
|
- spec/integration/has_1_spec.rb
|
67
76
|
- spec/integration/has_n_spec.rb
|
68
77
|
- spec/integration/has_n_through_spec.rb
|
69
|
-
-
|
70
|
-
- spec/spec.opts
|
71
|
-
- spec/spec_helper.rb
|
72
|
-
- spec/unit/accepts_nested_attributes_for_spec.rb
|
73
|
-
- spec/unit/resource_spec.rb
|
78
|
+
- tasks/changelog.rb
|
74
79
|
- tasks/gemspec.rb
|
75
80
|
- tasks/hoe.rb
|
76
81
|
- tasks/install.rb
|
@@ -1,55 +0,0 @@
|
|
1
|
-
module DataMapper
|
2
|
-
module Associations
|
3
|
-
|
4
|
-
module ManyToOne
|
5
|
-
|
6
|
-
class Proxy
|
7
|
-
|
8
|
-
def save
|
9
|
-
|
10
|
-
return false if @parent.nil?
|
11
|
-
|
12
|
-
# original dm-core-0.9.11 code:
|
13
|
-
# return true unless parent.new_record?
|
14
|
-
|
15
|
-
# and the backwards compatible extension to it (allows update of belongs_to model)
|
16
|
-
if !parent.new_record? && !@relationship.child_model.autosave_associations.key?(@relationship.name)
|
17
|
-
return true
|
18
|
-
end
|
19
|
-
|
20
|
-
@relationship.with_repository(parent) do
|
21
|
-
result = parent.marked_for_destruction? ? parent.destroy : parent.save
|
22
|
-
@relationship.child_key.set(@child, @relationship.parent_key.get(parent)) if result
|
23
|
-
result
|
24
|
-
end
|
25
|
-
|
26
|
-
end
|
27
|
-
|
28
|
-
end
|
29
|
-
|
30
|
-
end
|
31
|
-
|
32
|
-
|
33
|
-
module OneToMany
|
34
|
-
|
35
|
-
class Proxy
|
36
|
-
|
37
|
-
private
|
38
|
-
|
39
|
-
def save_resource(resource, parent = @parent)
|
40
|
-
@relationship.with_repository(resource) do |r|
|
41
|
-
if parent.nil? && resource.model.respond_to?(:many_to_many)
|
42
|
-
resource.destroy
|
43
|
-
else
|
44
|
-
@relationship.attach_parent(resource, parent)
|
45
|
-
resource.marked_for_destruction? ? resource.destroy : resource.save
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
end
|
51
|
-
|
52
|
-
end
|
53
|
-
|
54
|
-
end
|
55
|
-
end
|
@@ -1,49 +0,0 @@
|
|
1
|
-
module DataMapper
|
2
|
-
module NestedAttributes
|
3
|
-
|
4
|
-
module AssociationValidation
|
5
|
-
|
6
|
-
def save_child_associations(saved, context)
|
7
|
-
return super if context.nil? # preserve save! behavior
|
8
|
-
child_associations.each do |a|
|
9
|
-
if a.respond_to?(:valid?)
|
10
|
-
a.errors.each { |e| self.errors.add(:general, e) } unless a.valid?(context)
|
11
|
-
else
|
12
|
-
self.errors.add(:general, "child association is missing")
|
13
|
-
end
|
14
|
-
saved |= a.save
|
15
|
-
end
|
16
|
-
saved
|
17
|
-
end
|
18
|
-
|
19
|
-
def save_self
|
20
|
-
self.valid? && super
|
21
|
-
end
|
22
|
-
|
23
|
-
def save_parent_associations(saved, context)
|
24
|
-
parent_associations.each do |a|
|
25
|
-
if a.respond_to?(:each)
|
26
|
-
a.each do |r|
|
27
|
-
r.errors.each { |e| self.errors.add(:general, e) } unless r.valid?(context)
|
28
|
-
end
|
29
|
-
else
|
30
|
-
a.errors.each { |e| self.errors.add(:general, e) } unless a.valid?(context)
|
31
|
-
end
|
32
|
-
saved |= a.save
|
33
|
-
end
|
34
|
-
saved
|
35
|
-
end
|
36
|
-
|
37
|
-
# everything works the same if this method isn't overwritten with a no-op
|
38
|
-
# however, i suspect that this is the case because the registered before(:save) hook
|
39
|
-
# somehow gets lost when overwriting Resource#save here in this module.
|
40
|
-
# I'll leave it in for now, to make the purpose clear
|
41
|
-
|
42
|
-
def check_validations(context = :default)
|
43
|
-
true # no-op, validations are checked inside #save
|
44
|
-
end
|
45
|
-
|
46
|
-
end
|
47
|
-
|
48
|
-
end
|
49
|
-
end
|
@@ -1,350 +0,0 @@
|
|
1
|
-
module DataMapper
|
2
|
-
module NestedAttributes
|
3
|
-
|
4
|
-
module ClassMethods
|
5
|
-
|
6
|
-
def autosave_associations
|
7
|
-
@autosave_associations ||= {}
|
8
|
-
end
|
9
|
-
|
10
|
-
# Defines an attributes reader and writer for the specified association(s).
|
11
|
-
# If you are using <tt>attr_protected</tt> or <tt>attr_accessible</tt>,
|
12
|
-
# then you will need to add the attribute writer to the allowed list.
|
13
|
-
#
|
14
|
-
# After any params are passed to the attributes writer they are available
|
15
|
-
# via the attributes reader (they are stored in an instance variable of
|
16
|
-
# the same name). The attributes reader returns nil if the attributes
|
17
|
-
# writer has not been called.
|
18
|
-
#
|
19
|
-
# Supported options:
|
20
|
-
# [:allow_destroy]
|
21
|
-
# If true, destroys any members from the attributes hash with a
|
22
|
-
# <tt>_delete</tt> key and a value that evaluates to +true+
|
23
|
-
# (eg. 1, '1', true, or 'true'). This option is off by default.
|
24
|
-
# [:reject_if]
|
25
|
-
# Allows you to specify a Proc that checks whether a record should be
|
26
|
-
# built for a certain attribute hash. The hash is passed to the Proc
|
27
|
-
# and the Proc should return either +true+ or +false+. When no Proc
|
28
|
-
# is specified a record will be built for all attribute hashes that
|
29
|
-
# do not have a <tt>_delete</tt> that evaluates to true.
|
30
|
-
#
|
31
|
-
# Examples:
|
32
|
-
# # creates avatar_attributes
|
33
|
-
# # creates avatar_attributes=
|
34
|
-
# accepts_nested_attributes_for :avatar, :reject_if => proc { |attributes| attributes['name'].blank? }
|
35
|
-
# # creates avatar_attributes and posts_attributes
|
36
|
-
# # creates avatar_attributes= and posts_attributes=
|
37
|
-
# accepts_nested_attributes_for :avatar, :posts, :allow_destroy => true
|
38
|
-
def accepts_nested_attributes_for(association_name, options = {})
|
39
|
-
|
40
|
-
assert_kind_of 'association_name', association_name, Symbol, String
|
41
|
-
assert_kind_of 'options', options, Hash
|
42
|
-
|
43
|
-
options = { :allow_destroy => false }.update(options)
|
44
|
-
|
45
|
-
# raises if the specified option keys aren't valid
|
46
|
-
assert_valid_autosave_options(options)
|
47
|
-
|
48
|
-
# raises if the specified association doesn't exist
|
49
|
-
# we don't need the return value here, just the check
|
50
|
-
# ------------------------------------------------------
|
51
|
-
# also, when using the return value from this call to
|
52
|
-
# replace association_name with association.name,
|
53
|
-
# has(1, :through) are broken, because they seem to have
|
54
|
-
# a different name
|
55
|
-
|
56
|
-
association_for_name(association_name)
|
57
|
-
|
58
|
-
|
59
|
-
# should be safe to go on
|
60
|
-
|
61
|
-
include InstanceMethods
|
62
|
-
|
63
|
-
if ::DataMapper.const_defined?('Validate')
|
64
|
-
|
65
|
-
require Pathname(__FILE__).dirname.expand_path + 'association_validation'
|
66
|
-
|
67
|
-
include AssociationValidation
|
68
|
-
|
69
|
-
end
|
70
|
-
|
71
|
-
autosave_associations[association_name] = options
|
72
|
-
|
73
|
-
type = nr_of_possible_child_instances(association_name) > 1 ? :collection : :one_to_one
|
74
|
-
|
75
|
-
class_eval %{
|
76
|
-
|
77
|
-
def save(context = :default)
|
78
|
-
saved = false # preserve Resource#save api contract
|
79
|
-
transaction { |t| t.rollback unless saved = super }
|
80
|
-
saved
|
81
|
-
end
|
82
|
-
|
83
|
-
def #{association_name}_attributes
|
84
|
-
@#{association_name}_attributes
|
85
|
-
end
|
86
|
-
|
87
|
-
def #{association_name}_attributes=(attributes)
|
88
|
-
attributes = sanitize_nested_attributes(attributes)
|
89
|
-
@#{association_name}_attributes = attributes
|
90
|
-
assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes, #{options[:allow_destroy]})
|
91
|
-
end
|
92
|
-
|
93
|
-
if association_type(:#{association_name}) == :many_to_one || association_type(:#{association_name}) == :one_to_one
|
94
|
-
|
95
|
-
def get_#{association_name}
|
96
|
-
#{association_name.to_s} || self.class.associated_model_for_name(:#{association_name}).new
|
97
|
-
end
|
98
|
-
|
99
|
-
end
|
100
|
-
|
101
|
-
}, __FILE__, __LINE__ + 1
|
102
|
-
|
103
|
-
end
|
104
|
-
|
105
|
-
def reject_new_nested_attributes_proc_for(association_name)
|
106
|
-
autosave_associations[association_name] ? autosave_associations[association_name][:reject_if] : nil
|
107
|
-
end
|
108
|
-
|
109
|
-
|
110
|
-
# utility methods
|
111
|
-
|
112
|
-
def nr_of_possible_child_instances(association_name, repository = :default)
|
113
|
-
# belongs_to seems to generate no options[:max]
|
114
|
-
association_for_name(association_name, repository).options[:max] || 1
|
115
|
-
end
|
116
|
-
|
117
|
-
# i have the feeling this should be refactored
|
118
|
-
def associated_model_for_name(association_name, repository = :default)
|
119
|
-
a = association_for_name(association_name, repository)
|
120
|
-
case association_type(association_name)
|
121
|
-
when :many_to_one
|
122
|
-
a.parent_model
|
123
|
-
when :one_to_one
|
124
|
-
a.child_model
|
125
|
-
when :one_to_many
|
126
|
-
a.child_model
|
127
|
-
when :many_to_many
|
128
|
-
Object.full_const_get(a.options[:child_model])
|
129
|
-
else
|
130
|
-
raise ArgumentError, "Unknown association type #{a.inspect}"
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
# maybe this should be provided by dm-core somehow
|
135
|
-
# DataMapper::Association::Relationship would be a place maybe?
|
136
|
-
def association_type(association_name)
|
137
|
-
a = association_for_name(association_name)
|
138
|
-
if a.options[:max].nil? # belongs_to
|
139
|
-
:many_to_one
|
140
|
-
elsif a.options[:max] == 1 # has(1)
|
141
|
-
:one_to_one
|
142
|
-
elsif a.options[:max] > 1 && !a.is_a?(DataMapper::Associations::RelationshipChain) # has(n)
|
143
|
-
:one_to_many
|
144
|
-
elsif a.is_a?(DataMapper::Associations::RelationshipChain) # has(n, :through) MUST be checked after has(n) here
|
145
|
-
:many_to_many
|
146
|
-
else
|
147
|
-
raise ArgumentError, "Unknown association type #{a.inspect}"
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
# avoid nil access by always going through this
|
152
|
-
# this method raises if the association named name is not established in this model
|
153
|
-
def association_for_name(name, repository = :default)
|
154
|
-
association = self.relationships(repository)[name]
|
155
|
-
# TODO think about using a specific Error class like UnknownAssociationError
|
156
|
-
raise(ArgumentError, "Relationship #{name.inspect} does not exist in \#{model}") unless association
|
157
|
-
association
|
158
|
-
end
|
159
|
-
|
160
|
-
private
|
161
|
-
|
162
|
-
# think about storing valid options in a classlevel constant
|
163
|
-
def assert_valid_autosave_options(options)
|
164
|
-
unless options.all? { |k,v| [ :allow_destroy, :reject_if ].include?(k) }
|
165
|
-
raise ArgumentError, 'accepts_nested_attributes_for only takes :allow_destroy and :reject_if as options'
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
end
|
170
|
-
|
171
|
-
|
172
|
-
module InstanceMethods
|
173
|
-
|
174
|
-
# This method can be used to remove ambiguities from the passed attributes.
|
175
|
-
# Consider a situation with a belongs_to association where both a valid value
|
176
|
-
# for the foreign_key attribute *and* nested_attributes for a new record are
|
177
|
-
# present (i.e. item_type_id and item_type_attributes are present).
|
178
|
-
# Also see http://is.gd/sz2d on the rails-core ml for a discussion on this.
|
179
|
-
# The basic idea is, that there should be a well defined behavior for what
|
180
|
-
# exactly happens when such a situation occurs. I'm currently in favor for
|
181
|
-
# using the foreign_key if it is present, but this probably needs more thinking.
|
182
|
-
# For now, this method basically is a no-op, but at least it provides a hook where
|
183
|
-
# everyone can perform it's own sanitization (just overwrite this method)
|
184
|
-
def sanitize_nested_attributes(attrs)
|
185
|
-
attrs
|
186
|
-
end
|
187
|
-
|
188
|
-
# returns nil if no resource has been associated yet
|
189
|
-
def associated_instance_get(association_name, repository = :default)
|
190
|
-
send(self.class.association_for_name(association_name, repository).name)
|
191
|
-
end
|
192
|
-
|
193
|
-
|
194
|
-
private
|
195
|
-
|
196
|
-
# Attribute hash keys that should not be assigned as normal attributes.
|
197
|
-
# These hash keys are nested attributes implementation details.
|
198
|
-
UNASSIGNABLE_KEYS = [ :id, :_delete ]
|
199
|
-
|
200
|
-
|
201
|
-
# Assigns the given attributes to the association.
|
202
|
-
#
|
203
|
-
# If the given attributes include an <tt>:id</tt> that matches the existing
|
204
|
-
# record’s id, then the existing record will be modified. Otherwise a new
|
205
|
-
# record will be built.
|
206
|
-
#
|
207
|
-
# If the given attributes include a matching <tt>:id</tt> attribute _and_ a
|
208
|
-
# <tt>:_delete</tt> key set to a truthy value, then the existing record
|
209
|
-
# will be marked for destruction.
|
210
|
-
def assign_nested_attributes_for_one_to_one_association(association_name, attributes, allow_destroy)
|
211
|
-
if attributes[:id].blank?
|
212
|
-
unless reject_new_record?(association_name, attributes)
|
213
|
-
model = self.class.associated_model_for_name(association_name)
|
214
|
-
send("#{association_name}=", model.new(attributes.except(*UNASSIGNABLE_KEYS)))
|
215
|
-
end
|
216
|
-
else (existing_record = associated_instance_get(association_name)) && existing_record.id.to_s == attributes[:id].to_s
|
217
|
-
assign_to_or_mark_for_destruction(association_name, existing_record, attributes, allow_destroy)
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
|
-
# Assigns the given attributes to the collection association.
|
222
|
-
#
|
223
|
-
# Hashes with an <tt>:id</tt> value matching an existing associated record
|
224
|
-
# will update that record. Hashes without an <tt>:id</tt> value will build
|
225
|
-
# a new record for the association. Hashes with a matching <tt>:id</tt>
|
226
|
-
# value and a <tt>:_delete</tt> key set to a truthy value will mark the
|
227
|
-
# matched record for destruction.
|
228
|
-
#
|
229
|
-
# For example:
|
230
|
-
#
|
231
|
-
# assign_nested_attributes_for_collection_association(:people, {
|
232
|
-
# '1' => { :id => '1', :name => 'Peter' },
|
233
|
-
# '2' => { :name => 'John' },
|
234
|
-
# '3' => { :id => '2', :_delete => true }
|
235
|
-
# })
|
236
|
-
#
|
237
|
-
# Will update the name of the Person with ID 1, build a new associated
|
238
|
-
# person with the name `John', and mark the associatied Person with ID 2
|
239
|
-
# for destruction.
|
240
|
-
#
|
241
|
-
# Also accepts an Array of attribute hashes:
|
242
|
-
#
|
243
|
-
# assign_nested_attributes_for_collection_association(:people, [
|
244
|
-
# { :id => '1', :name => 'Peter' },
|
245
|
-
# { :name => 'John' },
|
246
|
-
# { :id => '2', :_delete => true }
|
247
|
-
# ])
|
248
|
-
def assign_nested_attributes_for_collection_association(association_name, attributes_collection, allow_destroy)
|
249
|
-
|
250
|
-
assert_kind_of 'association_name', association_name, Symbol
|
251
|
-
assert_kind_of 'attributes_collection', attributes_collection, Hash, Array
|
252
|
-
|
253
|
-
if attributes_collection.is_a? Hash
|
254
|
-
attributes_collection = attributes_collection.sort_by { |index, _| index.to_i }.map { |_, attributes| attributes }
|
255
|
-
end
|
256
|
-
|
257
|
-
attributes_collection.each do |attributes|
|
258
|
-
if attributes[:id].blank?
|
259
|
-
unless reject_new_record?(association_name, attributes)
|
260
|
-
case self.class.association_type(association_name)
|
261
|
-
when :one_to_many
|
262
|
-
build_new_has_n_association(association_name, attributes)
|
263
|
-
when :many_to_many
|
264
|
-
build_new_has_n_through_association(association_name, attributes)
|
265
|
-
end
|
266
|
-
end
|
267
|
-
elsif existing_record = send(association_name).detect { |record| record.id.to_s == attributes[:id].to_s }
|
268
|
-
assign_to_or_mark_for_destruction(association_name, existing_record, attributes, allow_destroy)
|
269
|
-
end
|
270
|
-
end
|
271
|
-
|
272
|
-
end
|
273
|
-
|
274
|
-
def build_new_has_n_association(association_name, attributes)
|
275
|
-
send(association_name).build(attributes.except(*UNASSIGNABLE_KEYS))
|
276
|
-
end
|
277
|
-
|
278
|
-
def build_new_has_n_through_association(association_name, attributes)
|
279
|
-
# fetch the association to have the information ready
|
280
|
-
association = self.class.association_for_name(association_name)
|
281
|
-
|
282
|
-
# do what's done in dm-core/specs/integration/association_through_spec.rb
|
283
|
-
|
284
|
-
# explicitly build the join entry and assign it to the join association
|
285
|
-
join_entry = self.class.associated_model_for_name(association.name).new
|
286
|
-
self.send(association.name) << join_entry
|
287
|
-
self.save
|
288
|
-
# explicitly build the child entry and assign the join entry to its join association
|
289
|
-
child_entry = self.class.associated_model_for_name(association_name).new(attributes)
|
290
|
-
child_entry.send(association.name) << join_entry
|
291
|
-
child_entry.save
|
292
|
-
end
|
293
|
-
|
294
|
-
# Updates a record with the +attributes+ or marks it for destruction if
|
295
|
-
# +allow_destroy+ is +true+ and has_delete_flag? returns +true+.
|
296
|
-
def assign_to_or_mark_for_destruction(association_name, record, attributes, allow_destroy)
|
297
|
-
if has_delete_flag?(attributes) && allow_destroy
|
298
|
-
if self.class.association_type(association_name) == :many_to_many
|
299
|
-
# destroy the join record
|
300
|
-
record.send(self.class.association_for_name(association_name).name).destroy!
|
301
|
-
# destroy the child record
|
302
|
-
record.destroy
|
303
|
-
else
|
304
|
-
record.mark_for_destruction
|
305
|
-
end
|
306
|
-
else
|
307
|
-
record.attributes = attributes.except(*UNASSIGNABLE_KEYS)
|
308
|
-
if self.class.association_type(association_name) == :many_to_many
|
309
|
-
record.save
|
310
|
-
end
|
311
|
-
end
|
312
|
-
end
|
313
|
-
|
314
|
-
# Determines if a hash contains a truthy _delete key.
|
315
|
-
def has_delete_flag?(hash)
|
316
|
-
# TODO find out if this activerecord code needs to be ported
|
317
|
-
# ConnectionAdapters::Column.value_to_boolean hash['_delete']
|
318
|
-
hash[:_delete]
|
319
|
-
end
|
320
|
-
|
321
|
-
# Determines if a new record should be build by checking for
|
322
|
-
# has_delete_flag? or if a <tt>:reject_if</tt> proc exists for this
|
323
|
-
# association and evaluates to +true+.
|
324
|
-
def reject_new_record?(association_name, attributes)
|
325
|
-
guard = self.class.reject_new_nested_attributes_proc_for(association_name)
|
326
|
-
has_delete_flag?(attributes) || (guard.respond_to?(:call) && guard.call(attributes))
|
327
|
-
end
|
328
|
-
|
329
|
-
end
|
330
|
-
|
331
|
-
module CommonInstanceMethods
|
332
|
-
|
333
|
-
# Reloads the attributes of the object as usual and removes a mark for destruction.
|
334
|
-
def reload
|
335
|
-
@marked_for_destruction = false
|
336
|
-
super
|
337
|
-
end
|
338
|
-
|
339
|
-
def marked_for_destruction?
|
340
|
-
@marked_for_destruction
|
341
|
-
end
|
342
|
-
|
343
|
-
def mark_for_destruction
|
344
|
-
@marked_for_destruction = true
|
345
|
-
end
|
346
|
-
|
347
|
-
end
|
348
|
-
|
349
|
-
end
|
350
|
-
end
|