dm-core 0.9.5 → 0.9.6
Sign up to get free protection for your applications and to get access to all the features.
- 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) }
|