dm-core 0.9.5 → 0.9.6
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 +3 -0
- data/lib/dm-core.rb +14 -20
- data/lib/dm-core/adapters.rb +18 -0
- data/lib/dm-core/adapters/abstract_adapter.rb +17 -10
- data/lib/dm-core/adapters/data_objects_adapter.rb +17 -22
- data/lib/dm-core/adapters/in_memory_adapter.rb +87 -0
- data/lib/dm-core/adapters/mysql_adapter.rb +1 -1
- data/lib/dm-core/adapters/postgres_adapter.rb +2 -2
- data/lib/dm-core/adapters/sqlite3_adapter.rb +1 -1
- data/lib/dm-core/associations.rb +3 -2
- data/lib/dm-core/associations/many_to_many.rb +3 -3
- data/lib/dm-core/associations/one_to_many.rb +10 -2
- data/lib/dm-core/associations/relationship.rb +20 -16
- data/lib/dm-core/auto_migrations.rb +5 -4
- data/lib/dm-core/collection.rb +10 -6
- data/lib/dm-core/dependency_queue.rb +2 -1
- data/lib/dm-core/identity_map.rb +3 -6
- data/lib/dm-core/model.rb +48 -27
- data/lib/dm-core/property.rb +57 -37
- data/lib/dm-core/property_set.rb +29 -22
- data/lib/dm-core/query.rb +57 -49
- data/lib/dm-core/repository.rb +3 -3
- data/lib/dm-core/resource.rb +17 -15
- data/lib/dm-core/scope.rb +7 -7
- data/lib/dm-core/support/kernel.rb +6 -2
- data/lib/dm-core/transaction.rb +7 -7
- data/lib/dm-core/version.rb +1 -1
- data/script/performance.rb +114 -22
- data/spec/integration/association_spec.rb +31 -2
- data/spec/integration/association_through_spec.rb +2 -0
- data/spec/integration/associations/many_to_many_spec.rb +152 -0
- data/spec/integration/associations/one_to_many_spec.rb +40 -3
- data/spec/integration/dependency_queue_spec.rb +0 -12
- data/spec/integration/postgres_adapter_spec.rb +1 -1
- data/spec/integration/property_spec.rb +3 -3
- data/spec/integration/query_spec.rb +39 -8
- data/spec/integration/resource_spec.rb +10 -6
- data/spec/integration/sti_spec.rb +22 -0
- data/spec/integration/strategic_eager_loading_spec.rb +21 -6
- data/spec/integration/type_spec.rb +1 -0
- data/spec/lib/model_loader.rb +10 -1
- data/spec/models/content.rb +16 -0
- data/spec/spec_helper.rb +4 -1
- data/spec/unit/adapters/data_objects_adapter_spec.rb +11 -11
- data/spec/unit/adapters/in_memory_adapter_spec.rb +98 -0
- data/spec/unit/associations/many_to_many_spec.rb +16 -1
- data/spec/unit/model_spec.rb +0 -16
- data/spec/unit/property_set_spec.rb +8 -1
- data/spec/unit/property_spec.rb +476 -240
- data/spec/unit/query_spec.rb +41 -0
- data/spec/unit/resource_spec.rb +75 -56
- data/tasks/ci.rb +4 -36
- data/tasks/dm.rb +3 -3
- metadata +5 -2
data/Manifest.txt
CHANGED
@@ -13,6 +13,7 @@ lib/dm-core.rb
|
|
13
13
|
lib/dm-core/adapters.rb
|
14
14
|
lib/dm-core/adapters/abstract_adapter.rb
|
15
15
|
lib/dm-core/adapters/data_objects_adapter.rb
|
16
|
+
lib/dm-core/adapters/in_memory_adapter.rb
|
16
17
|
lib/dm-core/adapters/mysql_adapter.rb
|
17
18
|
lib/dm-core/adapters/postgres_adapter.rb
|
18
19
|
lib/dm-core/adapters/sqlite3_adapter.rb
|
@@ -86,6 +87,7 @@ spec/lib/logging_helper.rb
|
|
86
87
|
spec/lib/mock_adapter.rb
|
87
88
|
spec/lib/model_loader.rb
|
88
89
|
spec/lib/publicize_methods.rb
|
90
|
+
spec/models/content.rb
|
89
91
|
spec/models/vehicles.rb
|
90
92
|
spec/models/zoo.rb
|
91
93
|
spec/spec.opts
|
@@ -93,6 +95,7 @@ spec/spec_helper.rb
|
|
93
95
|
spec/unit/adapters/abstract_adapter_spec.rb
|
94
96
|
spec/unit/adapters/adapter_shared_spec.rb
|
95
97
|
spec/unit/adapters/data_objects_adapter_spec.rb
|
98
|
+
spec/unit/adapters/in_memory_adapter_spec.rb
|
96
99
|
spec/unit/adapters/postgres_adapter_spec.rb
|
97
100
|
spec/unit/associations/many_to_many_spec.rb
|
98
101
|
spec/unit/associations/many_to_one_spec.rb
|
data/lib/dm-core.rb
CHANGED
@@ -22,6 +22,7 @@ require 'addressable/uri'
|
|
22
22
|
|
23
23
|
gem 'extlib', '>=0.9.5'
|
24
24
|
require 'extlib'
|
25
|
+
require "extlib/inflection"
|
25
26
|
|
26
27
|
begin
|
27
28
|
require 'fastthread'
|
@@ -132,8 +133,8 @@ module DataMapper
|
|
132
133
|
case uri_or_options
|
133
134
|
when Hash
|
134
135
|
adapter_name = uri_or_options[:adapter].to_s
|
135
|
-
when String, Addressable::URI
|
136
|
-
uri_or_options =
|
136
|
+
when String, DataObjects::URI, Addressable::URI
|
137
|
+
uri_or_options = DataObjects::URI.parse(uri_or_options) if uri_or_options.kind_of?(String)
|
137
138
|
adapter_name = uri_or_options.scheme
|
138
139
|
end
|
139
140
|
|
@@ -168,26 +169,19 @@ module DataMapper
|
|
168
169
|
# @param [Symbol] args the name of a repository to act within or return, :default is default
|
169
170
|
# @yield [Proc] (optional) block to execute within the context of the named repository
|
170
171
|
# @demo spec/integration/repository_spec.rb
|
171
|
-
def self.repository(
|
172
|
-
if args.size > 1
|
173
|
-
raise ArgumentError, "Can only pass in one optional argument, but passed in #{args.size} arguments", caller
|
174
|
-
end
|
175
|
-
|
176
|
-
if args.any? && !args.first.kind_of?(Symbol)
|
177
|
-
raise ArgumentError, "First optional argument must be a Symbol, but was #{args.first.inspect}", caller
|
178
|
-
end
|
179
|
-
|
180
|
-
name = args.first
|
181
|
-
|
172
|
+
def self.repository(name = nil) # :yields: current_context
|
182
173
|
current_repository = if name
|
174
|
+
raise ArgumentError, "First optional argument must be a Symbol, but was #{args.first.inspect}" unless name.is_a?(Symbol)
|
183
175
|
Repository.context.detect { |r| r.name == name } || Repository.new(name)
|
184
176
|
else
|
185
177
|
Repository.context.last || Repository.new(Repository.default_name)
|
186
178
|
end
|
187
179
|
|
188
|
-
|
189
|
-
|
190
|
-
|
180
|
+
if block_given?
|
181
|
+
current_repository.scope { |*block_args| yield(*block_args) }
|
182
|
+
else
|
183
|
+
current_repository
|
184
|
+
end
|
191
185
|
end
|
192
186
|
|
193
187
|
# A logger should always be present. Lets be consistent with DO
|
@@ -205,12 +199,12 @@ module DataMapper
|
|
205
199
|
# drops and recreates the repository upwards to match model definitions
|
206
200
|
#
|
207
201
|
# @param [Symbol] name repository to act on, :default is the default
|
208
|
-
def self.auto_migrate!(
|
209
|
-
|
202
|
+
def self.auto_migrate!(repository_name = nil)
|
203
|
+
AutoMigrator.auto_migrate(repository_name)
|
210
204
|
end
|
211
205
|
|
212
|
-
def self.auto_upgrade!(
|
213
|
-
|
206
|
+
def self.auto_upgrade!(repository_name = nil)
|
207
|
+
AutoMigrator.auto_upgrade(repository_name)
|
214
208
|
end
|
215
209
|
|
216
210
|
def self.prepare(*args, &blk)
|
data/lib/dm-core/adapters.rb
CHANGED
@@ -1,4 +1,22 @@
|
|
1
1
|
dir = Pathname(__FILE__).dirname.expand_path / 'adapters'
|
2
2
|
|
3
3
|
require dir / 'abstract_adapter'
|
4
|
+
require dir / 'in_memory_adapter'
|
5
|
+
|
6
|
+
# TODO Factor these out into dm-more
|
4
7
|
require dir / 'data_objects_adapter'
|
8
|
+
begin
|
9
|
+
require dir / 'sqlite3_adapter'
|
10
|
+
rescue LoadError
|
11
|
+
# ignore it
|
12
|
+
end
|
13
|
+
begin
|
14
|
+
require dir / 'mysql_adapter'
|
15
|
+
rescue LoadError
|
16
|
+
# ignore it
|
17
|
+
end
|
18
|
+
begin
|
19
|
+
require dir / 'postgres_adapter'
|
20
|
+
rescue LoadError
|
21
|
+
# ignore it
|
22
|
+
end
|
@@ -38,7 +38,7 @@ module DataMapper
|
|
38
38
|
# connection string for configuration.
|
39
39
|
def initialize(name, uri_or_options)
|
40
40
|
assert_kind_of 'name', name, Symbol
|
41
|
-
assert_kind_of 'uri_or_options', uri_or_options, Addressable::URI, Hash, String
|
41
|
+
assert_kind_of 'uri_or_options', uri_or_options, Addressable::URI, DataObjects::URI, Hash, String
|
42
42
|
|
43
43
|
@name = name
|
44
44
|
@uri = normalize_uri(uri_or_options)
|
@@ -46,12 +46,7 @@ module DataMapper
|
|
46
46
|
@resource_naming_convention = NamingConventions::Resource::UnderscoredAndPluralized
|
47
47
|
@field_naming_convention = NamingConventions::Field::Underscored
|
48
48
|
|
49
|
-
@transactions =
|
50
|
-
hash.delete_if do |k, v|
|
51
|
-
!k.respond_to?(:alive?) || !k.alive?
|
52
|
-
end
|
53
|
-
hash[key] = []
|
54
|
-
end
|
49
|
+
@transactions = {}
|
55
50
|
end
|
56
51
|
|
57
52
|
# TODO: move to dm-more/dm-migrations
|
@@ -144,7 +139,7 @@ module DataMapper
|
|
144
139
|
#
|
145
140
|
# TODO: move to dm-more/dm-transaction
|
146
141
|
def push_transaction(transaction)
|
147
|
-
|
142
|
+
transactions(Thread.current) << transaction
|
148
143
|
end
|
149
144
|
|
150
145
|
#
|
@@ -156,7 +151,7 @@ module DataMapper
|
|
156
151
|
#
|
157
152
|
# TODO: move to dm-more/dm-transaction
|
158
153
|
def pop_transaction
|
159
|
-
|
154
|
+
transactions(Thread.current).pop
|
160
155
|
end
|
161
156
|
|
162
157
|
#
|
@@ -169,7 +164,7 @@ module DataMapper
|
|
169
164
|
#
|
170
165
|
# TODO: move to dm-more/dm-transaction
|
171
166
|
def current_transaction
|
172
|
-
|
167
|
+
transactions(Thread.current).last
|
173
168
|
end
|
174
169
|
|
175
170
|
#
|
@@ -194,6 +189,18 @@ module DataMapper
|
|
194
189
|
def transaction_primitive
|
195
190
|
raise NotImplementedError
|
196
191
|
end
|
192
|
+
|
193
|
+
private
|
194
|
+
def transactions(thread)
|
195
|
+
unless @transactions[thread]
|
196
|
+
@transactions.delete_if do |key, value|
|
197
|
+
!key.respond_to?(:alive?) || !key.alive?
|
198
|
+
end
|
199
|
+
@transactions[thread] = []
|
200
|
+
end
|
201
|
+
@transactions[thread]
|
202
|
+
end
|
203
|
+
|
197
204
|
end
|
198
205
|
|
199
206
|
include Transaction
|
@@ -1,4 +1,4 @@
|
|
1
|
-
gem 'data_objects', '
|
1
|
+
gem 'data_objects', '>=0.9.5'
|
2
2
|
require 'data_objects'
|
3
3
|
|
4
4
|
module DataMapper
|
@@ -42,7 +42,10 @@ module DataMapper
|
|
42
42
|
command.set_types(query.fields.map { |p| p.primitive })
|
43
43
|
|
44
44
|
begin
|
45
|
-
|
45
|
+
bind_values = query.bind_values.map do |v|
|
46
|
+
v == [] ? [nil] : v
|
47
|
+
end
|
48
|
+
reader = command.execute_reader(*bind_values)
|
46
49
|
|
47
50
|
while(reader.next!)
|
48
51
|
collection.load(reader.values)
|
@@ -114,12 +117,12 @@ module DataMapper
|
|
114
117
|
protected
|
115
118
|
|
116
119
|
def normalize_uri(uri_or_options)
|
117
|
-
if uri_or_options.kind_of?(String)
|
118
|
-
uri_or_options =
|
120
|
+
if uri_or_options.kind_of?(String) || uri_or_options.kind_of?(Addressable::URI)
|
121
|
+
uri_or_options = DataObjects::URI.parse(uri_or_options)
|
119
122
|
end
|
120
123
|
|
121
|
-
if uri_or_options.kind_of?(
|
122
|
-
return uri_or_options
|
124
|
+
if uri_or_options.kind_of?(DataObjects::URI)
|
125
|
+
return uri_or_options
|
123
126
|
end
|
124
127
|
|
125
128
|
adapter = uri_or_options.delete(:adapter).to_s
|
@@ -131,7 +134,7 @@ module DataMapper
|
|
131
134
|
query = uri_or_options.to_a.map { |pair| pair * '=' } * '&'
|
132
135
|
query = nil if query == ''
|
133
136
|
|
134
|
-
return Addressable::URI.new(adapter, user, password, host, port, database, query, nil)
|
137
|
+
return DataObjects::URI.parse(Addressable::URI.new(adapter, user, password, host, port, database, query, nil))
|
135
138
|
end
|
136
139
|
|
137
140
|
# TODO: clean up once transaction related methods move to dm-more/dm-transactions
|
@@ -161,7 +164,7 @@ module DataMapper
|
|
161
164
|
end
|
162
165
|
end
|
163
166
|
|
164
|
-
def with_connection
|
167
|
+
def with_connection
|
165
168
|
connection = nil
|
166
169
|
begin
|
167
170
|
connection = create_connection
|
@@ -174,7 +177,7 @@ module DataMapper
|
|
174
177
|
end
|
175
178
|
end
|
176
179
|
|
177
|
-
def with_reader(statement, bind_values = []
|
180
|
+
def with_reader(statement, bind_values = [])
|
178
181
|
with_connection do |connection|
|
179
182
|
reader = nil
|
180
183
|
begin
|
@@ -264,8 +267,7 @@ module DataMapper
|
|
264
267
|
table_name = query.model.storage_name(query.repository.name)
|
265
268
|
|
266
269
|
statement = ''
|
267
|
-
|
268
|
-
query.links.reverse.each do |relationship|
|
270
|
+
query.links.each do |relationship|
|
269
271
|
parent_table_name = relationship.parent_model.storage_name(query.repository.name)
|
270
272
|
child_table_name = relationship.child_model.storage_name(query.repository.name)
|
271
273
|
|
@@ -326,17 +328,10 @@ module DataMapper
|
|
326
328
|
opposite = condition == left_condition ? right_condition : left_condition
|
327
329
|
query.merge_subquery(operator, opposite, condition)
|
328
330
|
"(#{read_statement(condition)})"
|
329
|
-
|
331
|
+
|
332
|
+
# [].all? is always true
|
333
|
+
elsif condition.kind_of?(Array) && condition.any? && condition.all? { |p| p.kind_of?(Property) }
|
330
334
|
property_values = condition.map { |p| property_to_column_name(query.repository, p, qualify) }
|
331
|
-
# An IN() clause with an empty set is the same as a false condition
|
332
|
-
# IN() with an empty set is not supported on all RDMS, but this is.
|
333
|
-
if property_values.empty?
|
334
|
-
if [:eql, :in, :not].include? operator
|
335
|
-
return operator == :not ? "1=1" : "0=1"
|
336
|
-
else
|
337
|
-
raise "Invalid query operator #{operator.inspect} for #{property_values.inspect}"
|
338
|
-
end
|
339
|
-
end
|
340
335
|
"(#{property_values * ', '})"
|
341
336
|
else
|
342
337
|
'?'
|
@@ -354,7 +349,7 @@ module DataMapper
|
|
354
349
|
else raise "Invalid query operator: #{operator.inspect}"
|
355
350
|
end
|
356
351
|
|
357
|
-
conditions * " #{comparison} "
|
352
|
+
"(" + (conditions * " #{comparison} ") + ")"
|
358
353
|
end
|
359
354
|
|
360
355
|
def equality_operator(operand)
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Adapters
|
3
|
+
class InMemoryAdapter < AbstractAdapter
|
4
|
+
def initialize(name, uri_or_options)
|
5
|
+
@records = Hash.new { |hash,model| hash[model] = Array.new }
|
6
|
+
end
|
7
|
+
|
8
|
+
def create(resources)
|
9
|
+
resources.each do |resource|
|
10
|
+
@records[resource.model] << resource
|
11
|
+
end.size # just return the number of records
|
12
|
+
end
|
13
|
+
|
14
|
+
def update(attributes, query)
|
15
|
+
read_many(query).each do |resource|
|
16
|
+
attributes.each do |property,value|
|
17
|
+
property.set!(resource, value)
|
18
|
+
end
|
19
|
+
end.size
|
20
|
+
end
|
21
|
+
|
22
|
+
def read_one(query)
|
23
|
+
read(query, query.model, false)
|
24
|
+
end
|
25
|
+
|
26
|
+
def read_many(query)
|
27
|
+
Collection.new(query) do |set|
|
28
|
+
read(query, set, true)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def delete(query)
|
33
|
+
records = @records[query.model]
|
34
|
+
|
35
|
+
read_many(query).each do |resource|
|
36
|
+
records.delete(resource)
|
37
|
+
end.size
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def read(query, set, many = true)
|
43
|
+
model = query.model
|
44
|
+
conditions = query.conditions
|
45
|
+
|
46
|
+
match_with = many ? :select : :detect
|
47
|
+
|
48
|
+
# Iterate over the records for this model, and return
|
49
|
+
# the ones that match the conditions
|
50
|
+
result = @records[model].send(match_with) do |resource|
|
51
|
+
conditions.all? do |tuple|
|
52
|
+
operator, property, bind_value = *tuple
|
53
|
+
|
54
|
+
value = property.get!(resource)
|
55
|
+
|
56
|
+
case operator
|
57
|
+
when :eql, :in then equality_comparison(bind_value, value)
|
58
|
+
when :not then !equality_comparison(bind_value, value)
|
59
|
+
when :like then Regexp.new(bind_value) =~ value
|
60
|
+
when :gt then !value.nil? && value > bind_value
|
61
|
+
when :gte then !value.nil? && value >= bind_value
|
62
|
+
when :lt then !value.nil? && value < bind_value
|
63
|
+
when :lte then !value.nil? && value <= bind_value
|
64
|
+
else raise "Invalid query operator: #{operator.inspect}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
return result unless many
|
70
|
+
|
71
|
+
# TODO Sort
|
72
|
+
|
73
|
+
# TODO Limit
|
74
|
+
|
75
|
+
set.replace(result)
|
76
|
+
end
|
77
|
+
|
78
|
+
def equality_comparison(bind_value, value)
|
79
|
+
case bind_value
|
80
|
+
when Array, Range then bind_value.include?(value)
|
81
|
+
when NilClass then value.nil?
|
82
|
+
else bind_value == value
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
gem 'do_postgres', '
|
1
|
+
gem 'do_postgres', '>=0.9.5'
|
2
2
|
require 'do_postgres'
|
3
3
|
|
4
4
|
module DataMapper
|
@@ -86,7 +86,7 @@ module DataMapper
|
|
86
86
|
end
|
87
87
|
|
88
88
|
# TODO: move to dm-more/dm-migrations
|
89
|
-
def without_notices
|
89
|
+
def without_notices
|
90
90
|
# execute the block with NOTICE messages disabled
|
91
91
|
begin
|
92
92
|
execute('SET client_min_messages = warning')
|
data/lib/dm-core/associations.rb
CHANGED
@@ -45,8 +45,8 @@ module DataMapper
|
|
45
45
|
end
|
46
46
|
|
47
47
|
def relationships(repository_name = default_repository_name)
|
48
|
-
@relationships ||=
|
49
|
-
@relationships[repository_name]
|
48
|
+
@relationships ||= {}
|
49
|
+
@relationships[repository_name] ||= repository_name == Repository.default_name ? {} : relationships(Repository.default_name).dup
|
50
50
|
end
|
51
51
|
|
52
52
|
def n
|
@@ -145,6 +145,7 @@ module DataMapper
|
|
145
145
|
#
|
146
146
|
# @api public
|
147
147
|
def belongs_to(name, options={})
|
148
|
+
@_valid_relations = false
|
148
149
|
relationship = ManyToOne.setup(name, self, options)
|
149
150
|
# Please leave this in - I will release contextual serialization soon
|
150
151
|
# which requires this -- guyvdb
|
@@ -42,13 +42,13 @@ module DataMapper
|
|
42
42
|
opts[:child_model] ||= opts.delete(:class_name) || Extlib::Inflection.classify(name)
|
43
43
|
opts[:parent_model] = model
|
44
44
|
opts[:repository_name] = repository_name
|
45
|
-
opts[:remote_relationship_name] ||= opts.delete(:remote_name) ||
|
45
|
+
opts[:remote_relationship_name] ||= opts.delete(:remote_name) || Extlib::Inflection.tableize(opts[:child_model])
|
46
46
|
opts[:parent_key] = opts[:parent_key]
|
47
47
|
opts[:child_key] = opts[:child_key]
|
48
48
|
opts[:mutable] = true
|
49
49
|
|
50
50
|
names = [ opts[:child_model], opts[:parent_model].name ].sort
|
51
|
-
model_name = names.join
|
51
|
+
model_name = names.join.gsub("::", "")
|
52
52
|
storage_name = Extlib::Inflection.tableize(Extlib::Inflection.pluralize(names[0]) + names[1])
|
53
53
|
|
54
54
|
opts[:near_relationship_name] = Extlib::Inflection.tableize(model_name).to_sym
|
@@ -67,7 +67,7 @@ module DataMapper
|
|
67
67
|
EOS
|
68
68
|
|
69
69
|
names.each do |n|
|
70
|
-
model.belongs_to(Extlib::Inflection.underscore(n).to_sym)
|
70
|
+
model.belongs_to(Extlib::Inflection.underscore(n).gsub("/", "_").to_sym, :class_name => n)
|
71
71
|
end
|
72
72
|
|
73
73
|
Object.const_set(model_name, model)
|
@@ -132,7 +132,7 @@ module DataMapper
|
|
132
132
|
orphan_resource(super)
|
133
133
|
end
|
134
134
|
|
135
|
-
def delete(resource
|
135
|
+
def delete(resource)
|
136
136
|
assert_mutable
|
137
137
|
orphan_resource(super)
|
138
138
|
end
|
@@ -153,6 +153,14 @@ module DataMapper
|
|
153
153
|
assert_mutable
|
154
154
|
attributes = default_attributes.merge(attributes)
|
155
155
|
resource = children.respond_to?(:build) ? super(attributes) : new_child(attributes)
|
156
|
+
resource
|
157
|
+
end
|
158
|
+
|
159
|
+
def new(attributes = {})
|
160
|
+
assert_mutable
|
161
|
+
raise UnsavedParentError, 'You cannot intialize until the parent is saved' if @parent.new_record?
|
162
|
+
attributes = default_attributes.merge(attributes)
|
163
|
+
resource = children.respond_to?(:new) ? super(attributes) : @relationship.child_model.new(attributes)
|
156
164
|
self << resource
|
157
165
|
resource
|
158
166
|
end
|
@@ -196,7 +204,7 @@ module DataMapper
|
|
196
204
|
end
|
197
205
|
|
198
206
|
def save
|
199
|
-
|
207
|
+
return true if children.frozen?
|
200
208
|
|
201
209
|
# save every resource in the collection
|
202
210
|
each { |resource| save_resource(resource) }
|