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/lib/dm-core/transaction.rb
CHANGED
@@ -13,12 +13,12 @@ module DataMapper
|
|
13
13
|
# In fact, it just calls #link with the given arguments at the end of the
|
14
14
|
# constructor.
|
15
15
|
#
|
16
|
-
def initialize(*things
|
16
|
+
def initialize(*things)
|
17
17
|
@transaction_primitives = {}
|
18
18
|
@state = :none
|
19
19
|
@adapters = {}
|
20
20
|
link(*things)
|
21
|
-
commit(
|
21
|
+
commit { |*block_args| yield(*block_args) } if block_given?
|
22
22
|
end
|
23
23
|
|
24
24
|
#
|
@@ -39,7 +39,7 @@ module DataMapper
|
|
39
39
|
# within this transaction. The transaction will begin and commit around
|
40
40
|
# the block, and rollback if an exception is raised.
|
41
41
|
#
|
42
|
-
def link(*things
|
42
|
+
def link(*things)
|
43
43
|
raise "Illegal state for link: #{@state}" unless @state == :none
|
44
44
|
things.each do |thing|
|
45
45
|
if thing.is_a?(Array)
|
@@ -56,7 +56,7 @@ module DataMapper
|
|
56
56
|
raise "Unknown argument to #{self}#link: #{thing.inspect}"
|
57
57
|
end
|
58
58
|
end
|
59
|
-
return commit(
|
59
|
+
return commit { |*block_args| yield(*block_args) } if block_given?
|
60
60
|
return self
|
61
61
|
end
|
62
62
|
|
@@ -83,12 +83,12 @@ module DataMapper
|
|
83
83
|
# If no block is given, it will simply commit any changes made since the
|
84
84
|
# Transaction did #begin.
|
85
85
|
#
|
86
|
-
def commit
|
86
|
+
def commit
|
87
87
|
if block_given?
|
88
88
|
raise "Illegal state for commit with block: #{@state}" unless @state == :none
|
89
89
|
begin
|
90
90
|
self.begin
|
91
|
-
rval = within(
|
91
|
+
rval = within { |*block_args| yield(*block_args) }
|
92
92
|
self.commit if @state == :begin
|
93
93
|
return rval
|
94
94
|
rescue Exception => e
|
@@ -128,7 +128,7 @@ module DataMapper
|
|
128
128
|
# adapter it is associated with, and it will ensures that it will pop the
|
129
129
|
# Transaction away again after the block is finished.
|
130
130
|
#
|
131
|
-
def within
|
131
|
+
def within
|
132
132
|
raise "No block provided" unless block_given?
|
133
133
|
raise "Illegal state for within: #{@state}" unless @state == :begin
|
134
134
|
@adapters.each do |adapter, state|
|
data/lib/dm-core/version.rb
CHANGED
data/script/performance.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
require File.join(File.dirname(__FILE__), '..', 'lib', 'dm-core')
|
4
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'dm-core', 'version')
|
4
5
|
|
5
6
|
require 'rubygems'
|
6
7
|
require 'ftools'
|
@@ -38,11 +39,11 @@ configuration_options[:socket] = socket_file unless socket_file.nil?
|
|
38
39
|
log_dir = DataMapper.root / 'log'
|
39
40
|
log_dir.mkdir unless log_dir.directory?
|
40
41
|
|
41
|
-
DataMapper::Logger.new(log_dir / 'dm.log', :
|
42
|
+
DataMapper::Logger.new(log_dir / 'dm.log', :off)
|
42
43
|
adapter = DataMapper.setup(:default, "mysql://root@localhost/data_mapper_1?socket=#{socket_file}")
|
43
44
|
|
44
45
|
if configuration_options[:adapter]
|
45
|
-
sqlfile = File.join(File.dirname(__FILE__),'..','tmp','
|
46
|
+
sqlfile = File.join(File.dirname(__FILE__),'..','tmp','performance.sql')
|
46
47
|
mysql_bin = %w[mysql mysql5].select{|bin| `which #{bin}`.length > 0 }
|
47
48
|
mysqldump_bin = %w[mysqldump mysqldump5].select{|bin| `which #{bin}`.length > 0 }
|
48
49
|
end
|
@@ -54,6 +55,15 @@ ActiveRecord::Base.establish_connection(configuration_options)
|
|
54
55
|
|
55
56
|
class ARExhibit < ActiveRecord::Base #:nodoc:
|
56
57
|
set_table_name 'exhibits'
|
58
|
+
|
59
|
+
belongs_to :user, :class_name => 'ARUser', :foreign_key => 'user_id'
|
60
|
+
end
|
61
|
+
|
62
|
+
class ARUser < ActiveRecord::Base #:nodoc:
|
63
|
+
set_table_name 'users'
|
64
|
+
|
65
|
+
has_many :exhibits, :foreign_key => 'user_id'
|
66
|
+
|
57
67
|
end
|
58
68
|
|
59
69
|
ARExhibit.find_by_sql('SELECT 1')
|
@@ -64,17 +74,39 @@ class Exhibit
|
|
64
74
|
property :id, Serial
|
65
75
|
property :name, String
|
66
76
|
property :zoo_id, Integer
|
77
|
+
property :user_id, Integer
|
67
78
|
property :notes, Text, :lazy => true
|
68
79
|
property :created_on, Date
|
80
|
+
|
81
|
+
belongs_to :user
|
69
82
|
# property :updated_at, DateTime
|
70
83
|
end
|
71
84
|
|
85
|
+
class User
|
86
|
+
include DataMapper::Resource
|
87
|
+
|
88
|
+
property :id, Serial
|
89
|
+
property :name, String
|
90
|
+
property :email, String
|
91
|
+
property :about, Text, :lazy => true
|
92
|
+
property :created_on, Date
|
93
|
+
|
94
|
+
end
|
95
|
+
|
72
96
|
touch_attributes = lambda do |exhibits|
|
73
97
|
[*exhibits].each do |exhibit|
|
74
98
|
exhibit.id
|
75
99
|
exhibit.name
|
76
100
|
exhibit.created_on
|
77
|
-
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
touch_relationships = lambda do |exhibits|
|
105
|
+
[*exhibits].each do |exhibit|
|
106
|
+
exhibit.id
|
107
|
+
exhibit.name
|
108
|
+
exhibit.created_on
|
109
|
+
exhibit.user
|
78
110
|
end
|
79
111
|
end
|
80
112
|
|
@@ -87,27 +119,49 @@ if sqlfile && File.exists?(sqlfile)
|
|
87
119
|
`#{mysql_bin} -u #{c[:username]} #{"-p#{c[:password]}" unless c[:password].blank?} #{c[:database]} < #{sqlfile}`
|
88
120
|
else
|
89
121
|
|
122
|
+
puts "Generating data for benchmarking..."
|
123
|
+
|
124
|
+
User.auto_migrate!
|
90
125
|
Exhibit.auto_migrate!
|
91
126
|
|
127
|
+
users = []
|
92
128
|
exhibits = []
|
129
|
+
|
93
130
|
# pre-compute the insert statements and fake data compilation,
|
94
131
|
# so the benchmarks below show the actual runtime for the execute
|
95
132
|
# method, minus the setup steps
|
96
|
-
|
133
|
+
|
134
|
+
# Using the same paragraph for all exhibits because it is very slow
|
135
|
+
# to generate unique paragraphs for all exhibits.
|
136
|
+
paragraph = Faker::Lorem.paragraphs.join($/)
|
137
|
+
|
138
|
+
10_000.times do |i|
|
139
|
+
users << [
|
140
|
+
'INSERT INTO `users` (`name`,`email`,`created_on`) VALUES (?, ?, ?)',
|
141
|
+
Faker::Name.name,
|
142
|
+
Faker::Internet.email,
|
143
|
+
Date.today
|
144
|
+
]
|
145
|
+
|
97
146
|
exhibits << [
|
98
|
-
'INSERT INTO `exhibits` (`name`, `zoo_id`, `notes`, `created_on`) VALUES (?, ?, ?, ?)',
|
147
|
+
'INSERT INTO `exhibits` (`name`, `zoo_id`, `user_id`, `notes`, `created_on`) VALUES (?, ?, ?, ?, ?)',
|
99
148
|
Faker::Company.name,
|
100
149
|
rand(10).ceil,
|
101
|
-
|
150
|
+
i,
|
151
|
+
paragraph,#Faker::Lorem.paragraphs.join($/),
|
102
152
|
Date.today
|
103
153
|
]
|
104
154
|
end
|
155
|
+
|
156
|
+
puts "Inserting 10,000 users..."
|
157
|
+
10_000.times { |i| adapter.execute(*users.at(i)) }
|
158
|
+
puts "Inserting 10,000 exhibits..."
|
105
159
|
10_000.times { |i| adapter.execute(*exhibits.at(i)) }
|
106
160
|
|
107
161
|
if sqlfile
|
108
162
|
answer = nil
|
109
163
|
until answer && answer[/^$|y|yes|n|no/]
|
110
|
-
print("Would you like to dump data into tmp/
|
164
|
+
print("Would you like to dump data into tmp/performance.sql (for faster setup)? [Yn]");
|
111
165
|
STDOUT.flush
|
112
166
|
answer = gets
|
113
167
|
end
|
@@ -115,7 +169,7 @@ else
|
|
115
169
|
if answer[/^$|y|yes/]
|
116
170
|
File.makedirs(File.dirname(sqlfile))
|
117
171
|
#adapter.execute("SELECT * INTO OUTFILE '#{sqlfile}' FROM exhibits;")
|
118
|
-
`#{mysqldump_bin} -u #{c[:username]} #{"-p#{c[:password]}" unless c[:password].blank?} #{c[:database]} exhibits > #{sqlfile}`
|
172
|
+
`#{mysqldump_bin} -u #{c[:username]} #{"-p#{c[:password]}" unless c[:password].blank?} #{c[:database]} exhibits users > #{sqlfile}`
|
119
173
|
puts "File saved\n"
|
120
174
|
end
|
121
175
|
end
|
@@ -127,37 +181,63 @@ TIMES = ENV['x'] ? ENV['x'].to_i : 10_000
|
|
127
181
|
puts "You can specify how many times you want to run the benchmarks with rake:perf x=(number)"
|
128
182
|
puts "Some tasks will be run 10 and 1000 times less than (number)"
|
129
183
|
puts "Benchmarks will now run #{TIMES} times"
|
184
|
+
# Inform about slow benchmark
|
185
|
+
# answer = nil
|
186
|
+
# until answer && answer[/^$|y|yes|n|no/]
|
187
|
+
# print("A slow benchmark exposing problems with SEL is newly added. It takes approx. 20s\n");
|
188
|
+
# print("you have scheduled it to run #{TIMES / 100} times.\nWould you still include the particular benchmark? [Yn]")
|
189
|
+
# STDOUT.flush
|
190
|
+
# answer = gets
|
191
|
+
# end
|
192
|
+
# run_rel_bench = answer[/^$|y|yes/] ? true : false
|
193
|
+
|
130
194
|
|
131
195
|
RBench.run(TIMES) do
|
132
196
|
|
133
197
|
column :times
|
134
|
-
column :dm, :title => "DM 0.9.4"
|
135
198
|
column :ar, :title => "AR 2.1"
|
136
|
-
column :
|
199
|
+
column :dm, :title => "DM #{DataMapper::VERSION}"
|
200
|
+
column :diff, :compare => [:ar,:dm]
|
201
|
+
|
202
|
+
report "Model.new (instantiation)" do
|
203
|
+
ar { ARExhibit.new }
|
204
|
+
dm { Exhibit.new }
|
205
|
+
end
|
206
|
+
|
207
|
+
report "Model.new (setting attributes)" do
|
208
|
+
attrs = {:name => 'sam', :zoo_id => 1}
|
209
|
+
ar { ARExhibit.new(attrs) }
|
210
|
+
dm { Exhibit.new(attrs) }
|
211
|
+
end
|
137
212
|
|
138
213
|
report "Model.get specific (not cached)" do
|
139
|
-
dm { touch_attributes[Exhibit.get(1)] }
|
140
214
|
ActiveRecord::Base.uncached { ar { touch_attributes[ARExhibit.find(1)] } }
|
215
|
+
dm { touch_attributes[Exhibit.get(1)] }
|
141
216
|
end
|
142
217
|
|
143
218
|
report "Model.get specific (cached)" do
|
144
|
-
Exhibit.repository(:default) { dm { touch_attributes[Exhibit.get(1)] } }
|
145
219
|
ActiveRecord::Base.cache { ar { touch_attributes[ARExhibit.find(1)] } }
|
220
|
+
Exhibit.repository(:default) { dm { touch_attributes[Exhibit.get(1)] } }
|
146
221
|
end
|
147
222
|
|
148
223
|
report "Model.first" do
|
149
|
-
dm { touch_attributes[Exhibit.first] }
|
150
224
|
ar { touch_attributes[ARExhibit.first] }
|
225
|
+
dm { touch_attributes[Exhibit.first] }
|
151
226
|
end
|
152
227
|
|
153
|
-
report "Model.all limit(100)", TIMES / 10 do
|
154
|
-
dm { touch_attributes[Exhibit.all(:limit => 100)] }
|
228
|
+
report "Model.all limit(100)", (TIMES / 10.0).ceil do
|
155
229
|
ar { touch_attributes[ARExhibit.find(:all, :limit => 100)] }
|
230
|
+
dm { touch_attributes[Exhibit.all(:limit => 100)] }
|
156
231
|
end
|
157
232
|
|
158
|
-
report "Model.all limit(
|
159
|
-
|
233
|
+
report "Model.all limit(100) with relationship", (TIMES / 10.0).ceil do
|
234
|
+
ar { touch_relationships[ARExhibit.all(:limit => 100, :include => [:user])] }
|
235
|
+
dm { touch_relationships[Exhibit.all(:limit => 100)] }
|
236
|
+
end
|
237
|
+
|
238
|
+
report "Model.all limit(10,000)", (TIMES / 1000.0).ceil do
|
160
239
|
ar { touch_attributes[ARExhibit.find(:all, :limit => 10_000)] }
|
240
|
+
dm { touch_attributes[Exhibit.all(:limit => 10_000)] }
|
161
241
|
end
|
162
242
|
|
163
243
|
create_exhibit = {
|
@@ -168,25 +248,37 @@ RBench.run(TIMES) do
|
|
168
248
|
}
|
169
249
|
|
170
250
|
report "Model.create" do
|
171
|
-
dm { Exhibit.create(create_exhibit) }
|
172
251
|
ar { ARExhibit.create(create_exhibit) }
|
252
|
+
dm { Exhibit.create(create_exhibit) }
|
253
|
+
end
|
254
|
+
|
255
|
+
report "Resource#attributes" do
|
256
|
+
attrs_first = {:name => 'sam', :zoo_id => 1}
|
257
|
+
attrs_second = {:name => 'tom', :zoo_id => 1}
|
258
|
+
ar { e = ARExhibit.new(attrs_first); e.attributes = attrs_second }
|
259
|
+
dm { e = Exhibit.new(attrs_first); e.attributes = attrs_second }
|
173
260
|
end
|
174
261
|
|
175
262
|
report "Resource#update" do
|
176
|
-
|
177
|
-
|
263
|
+
ar { e = ARExhibit.find(1); e.name = 'bob'; e.save }
|
264
|
+
dm { e = Exhibit.get(1); e.name = 'bob'; e.save }
|
178
265
|
end
|
179
266
|
|
180
267
|
report "Resource#destroy" do
|
181
|
-
dm { Exhibit.first.destroy }
|
182
268
|
ar { ARExhibit.first.destroy }
|
269
|
+
dm { Exhibit.first.destroy }
|
183
270
|
end
|
184
271
|
|
185
|
-
|
272
|
+
report "Model.transaction" do
|
273
|
+
ar { ARExhibit.transaction { ARExhibit.new } }
|
274
|
+
dm { Exhibit.transaction { Exhibit.new } }
|
275
|
+
end
|
186
276
|
|
277
|
+
summary "Total"
|
187
278
|
end
|
188
279
|
|
189
280
|
connection = adapter.send(:create_connection)
|
190
281
|
command = connection.create_command("DROP TABLE exhibits")
|
282
|
+
command = connection.create_command("DROP TABLE users")
|
191
283
|
command.execute_non_query rescue nil
|
192
284
|
connection.close
|
@@ -302,6 +302,18 @@ if ADAPTER
|
|
302
302
|
area.should respond_to(:machine=)
|
303
303
|
end
|
304
304
|
|
305
|
+
it 'should create the foreign key property immediately' do
|
306
|
+
class Duck
|
307
|
+
include DataMapper::Resource
|
308
|
+
property :id, Serial
|
309
|
+
belongs_to :sky
|
310
|
+
end
|
311
|
+
Duck.properties.slice(:sky_id).compact.should_not be_empty
|
312
|
+
duck = Duck.new
|
313
|
+
duck.should respond_to(:sky_id)
|
314
|
+
duck.should respond_to(:sky_id=)
|
315
|
+
end
|
316
|
+
|
305
317
|
it 'should load without the parent'
|
306
318
|
|
307
319
|
it 'should allow substituting the parent' do
|
@@ -331,7 +343,7 @@ if ADAPTER
|
|
331
343
|
end
|
332
344
|
end
|
333
345
|
|
334
|
-
FlightlessBirds::Ostrich.properties.slice(:sky_id).should_not be_empty
|
346
|
+
FlightlessBirds::Ostrich.properties(ADAPTER).slice(:sky_id).compact.should_not be_empty
|
335
347
|
end
|
336
348
|
end
|
337
349
|
|
@@ -571,6 +583,15 @@ if ADAPTER
|
|
571
583
|
machine.areas.size.should == 4
|
572
584
|
end
|
573
585
|
|
586
|
+
it "#build should add exactly one instance of the built record" do
|
587
|
+
machine = Machine.create(:name => 'my machine')
|
588
|
+
|
589
|
+
original_size = machine.areas.size
|
590
|
+
machine.areas.build(:name => "an area", :machine => machine)
|
591
|
+
|
592
|
+
machine.areas.size.should == original_size + 1
|
593
|
+
end
|
594
|
+
|
574
595
|
it '#<< should add default values for relationships that have conditions' do
|
575
596
|
# it should add default values
|
576
597
|
machine = Machine.new(:name => 'my machine')
|
@@ -1169,8 +1190,16 @@ if ADAPTER
|
|
1169
1190
|
#
|
1170
1191
|
|
1171
1192
|
it 'should join tables in the right order during has 1 => has n => has 1 queries' do
|
1172
|
-
child = Sweets::Shop.first.children(:name => 'Snotling nr 3').booger(:name.like => '
|
1193
|
+
child = Sweets::Shop.first.children(:name => 'Snotling nr 3').booger(:name.like => 'Nasty booger')
|
1173
1194
|
child.should_not be_nil
|
1195
|
+
child.size.should eql(1)
|
1196
|
+
child.first.name.should eql("Nasty booger")
|
1197
|
+
end
|
1198
|
+
|
1199
|
+
it 'should join tables in the right order for belongs_to relations' do
|
1200
|
+
wife = Sweets::Wife.first(Sweets::Wife.shop_owner.name => "Betsy", Sweets::Wife.shop_owner.shop.name => "Betsy's")
|
1201
|
+
wife.should_not be_nil
|
1202
|
+
wife.name.should eql("Barry")
|
1174
1203
|
end
|
1175
1204
|
|
1176
1205
|
it 'should raise exception if you try to change it' do
|
@@ -50,10 +50,12 @@ if ADAPTER
|
|
50
50
|
has n, :relationships
|
51
51
|
has n, :related_posts,
|
52
52
|
:through => :relationships,
|
53
|
+
:child_key => [:post_id],
|
53
54
|
:class_name => "Post"
|
54
55
|
|
55
56
|
has n, :void_tags,
|
56
57
|
:through => :taggings,
|
58
|
+
:child_key => [:post_id],
|
57
59
|
:class_name => "Tag",
|
58
60
|
:remote_relationship_name => :tag,
|
59
61
|
Post.taggings.tag.voided => true
|
@@ -294,4 +294,156 @@ describe DataMapper::Associations::ManyToMany::Proxy do
|
|
294
294
|
end
|
295
295
|
end
|
296
296
|
|
297
|
+
describe "with renamed associations" do
|
298
|
+
before :all do
|
299
|
+
class Singer
|
300
|
+
include DataMapper::Resource
|
301
|
+
|
302
|
+
def self.default_repository_name; ADAPTER end
|
303
|
+
|
304
|
+
property :id, Serial
|
305
|
+
property :name, String
|
306
|
+
|
307
|
+
has n, :tunes, :through => Resource, :class_name => 'Song'
|
308
|
+
end
|
309
|
+
|
310
|
+
class Song
|
311
|
+
include DataMapper::Resource
|
312
|
+
|
313
|
+
def self.default_repository_name; ADAPTER end
|
314
|
+
|
315
|
+
property :id, Serial
|
316
|
+
property :title, String
|
317
|
+
|
318
|
+
has n, :performers, :through => Resource, :class_name => 'Singer'
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
before do
|
323
|
+
[ Singer, Song, SingerSong ].each { |k| k.auto_migrate! }
|
324
|
+
|
325
|
+
song_1 = Song.create(:title => "Dubliners")
|
326
|
+
song_2 = Song.create(:title => "Portrait of the Artist as a Young Man")
|
327
|
+
song_3 = Song.create(:title => "Ulysses")
|
328
|
+
|
329
|
+
singer_1 = Singer.create(:name => "Jon Doe")
|
330
|
+
singer_2 = Singer.create(:name => "Jane Doe")
|
331
|
+
|
332
|
+
SingerSong.create(:song => song_1, :singer => singer_1)
|
333
|
+
SingerSong.create(:song => song_2, :singer => singer_1)
|
334
|
+
SingerSong.create(:song => song_1, :singer => singer_2)
|
335
|
+
|
336
|
+
@parent = song_3
|
337
|
+
@association = @parent.performers
|
338
|
+
@other = [ singer_1 ]
|
339
|
+
end
|
340
|
+
|
341
|
+
it "should provide #replace" do
|
342
|
+
@association.should respond_to(:replace)
|
343
|
+
end
|
344
|
+
|
345
|
+
it "should correctly link records" do
|
346
|
+
Song.get(1).should have(2).performers
|
347
|
+
Song.get(2).should have(1).performers
|
348
|
+
Song.get(3).should have(0).performers
|
349
|
+
Singer.get(1).should have(2).tunes
|
350
|
+
Singer.get(2).should have(1).tunes
|
351
|
+
end
|
352
|
+
|
353
|
+
it "should be able to have associated objects manually added" do
|
354
|
+
song = Song.get(3)
|
355
|
+
song.should have(0).performers
|
356
|
+
|
357
|
+
be = SingerSong.new(:song_id => song.id, :singer_id => 2)
|
358
|
+
song.singer_songs << be
|
359
|
+
song.save
|
360
|
+
|
361
|
+
song.reload.should have(1).performers
|
362
|
+
end
|
363
|
+
|
364
|
+
it "should automatically added necessary through class" do
|
365
|
+
song = Song.get(3)
|
366
|
+
song.should have(0).performers
|
367
|
+
|
368
|
+
song.performers << Singer.get(1)
|
369
|
+
song.performers << Singer.new(:name => "Jimmy John")
|
370
|
+
song.save
|
371
|
+
|
372
|
+
song.reload.should have(2).performers
|
373
|
+
end
|
374
|
+
|
375
|
+
it "should react correctly to a new record" do
|
376
|
+
song = Song.new(:title => "Finnegan's Wake")
|
377
|
+
singer = Singer.get(2)
|
378
|
+
song.should have(0).performers
|
379
|
+
singer.should have(1).tunes
|
380
|
+
|
381
|
+
song.performers << singer
|
382
|
+
song.save
|
383
|
+
|
384
|
+
song.reload.should have(1).performers
|
385
|
+
singer.reload.should have(2).tunes
|
386
|
+
end
|
387
|
+
|
388
|
+
it "should be able to delete intermediate model" do
|
389
|
+
song = Song.get(1)
|
390
|
+
song.should have(2).singer_songs
|
391
|
+
song.should have(2).performers
|
392
|
+
|
393
|
+
be = SingerSong.get(1,1)
|
394
|
+
song.singer_songs.delete(be)
|
395
|
+
song.save
|
396
|
+
|
397
|
+
song.reload
|
398
|
+
song.should have(1).singer_songs
|
399
|
+
song.should have(1).performers
|
400
|
+
end
|
401
|
+
|
402
|
+
it "should be clearable" do
|
403
|
+
repository(ADAPTER) do
|
404
|
+
song = Song.get(2)
|
405
|
+
song.should have(1).singer_songs
|
406
|
+
song.should have(1).performers
|
407
|
+
|
408
|
+
song.performers.clear
|
409
|
+
song.save
|
410
|
+
|
411
|
+
song.reload
|
412
|
+
song.should have(0).singer_songs
|
413
|
+
song.should have(0).performers
|
414
|
+
end
|
415
|
+
repository(ADAPTER) do
|
416
|
+
Song.get(2).should have(0).performers
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
it "should be able to delete one object" do
|
421
|
+
song = Song.get(1)
|
422
|
+
song.should have(2).singer_songs
|
423
|
+
song.should have(2).performers
|
424
|
+
|
425
|
+
editor = song.performers.first
|
426
|
+
song.performers.delete(editor)
|
427
|
+
song.save
|
428
|
+
|
429
|
+
song.reload
|
430
|
+
song.should have(1).singer_songs
|
431
|
+
song.should have(1).performers
|
432
|
+
editor.reload.tunes.should_not include(song)
|
433
|
+
end
|
434
|
+
|
435
|
+
it "should be destroyable" do
|
436
|
+
pending "cannot destroy a collection yet" do
|
437
|
+
song = Song.get(2)
|
438
|
+
song.should have(1).performers
|
439
|
+
|
440
|
+
song.performers.destroy
|
441
|
+
song.save
|
442
|
+
|
443
|
+
song.reload
|
444
|
+
song.should have(0).performers
|
445
|
+
end
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
297
449
|
end
|