datamapper 0.2.4 → 0.2.5

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.
Files changed (65) hide show
  1. data/CHANGELOG +16 -1
  2. data/README +10 -8
  3. data/environment.rb +1 -1
  4. data/example.rb +13 -5
  5. data/lib/data_mapper.rb +2 -0
  6. data/lib/data_mapper/adapters/abstract_adapter.rb +61 -0
  7. data/lib/data_mapper/adapters/data_object_adapter.rb +33 -6
  8. data/lib/data_mapper/adapters/mysql_adapter.rb +5 -0
  9. data/lib/data_mapper/adapters/postgresql_adapter.rb +12 -0
  10. data/lib/data_mapper/adapters/sql/commands/load_command.rb +6 -14
  11. data/lib/data_mapper/adapters/sql/mappings/column.rb +37 -26
  12. data/lib/data_mapper/adapters/sql/mappings/table.rb +50 -8
  13. data/lib/data_mapper/associations.rb +4 -5
  14. data/lib/data_mapper/associations/has_and_belongs_to_many_association.rb +2 -2
  15. data/lib/data_mapper/associations/has_many_association.rb +40 -13
  16. data/lib/data_mapper/associations/has_n_association.rb +1 -1
  17. data/lib/data_mapper/base.rb +5 -448
  18. data/lib/data_mapper/callbacks.rb +12 -2
  19. data/lib/data_mapper/context.rb +4 -0
  20. data/lib/data_mapper/database.rb +1 -1
  21. data/lib/data_mapper/identity_map.rb +2 -2
  22. data/lib/data_mapper/persistence.rb +538 -0
  23. data/lib/data_mapper/support/active_record_impersonation.rb +21 -3
  24. data/lib/data_mapper/support/errors.rb +2 -0
  25. data/lib/data_mapper/support/serialization.rb +7 -10
  26. data/lib/data_mapper/support/struct.rb +7 -0
  27. data/lib/data_mapper/validatable_extensions/validations/validates_uniqueness_of.rb +11 -4
  28. data/performance.rb +23 -10
  29. data/rakefile.rb +1 -1
  30. data/spec/active_record_impersonation_spec.rb +2 -6
  31. data/spec/acts_as_tree_spec.rb +3 -1
  32. data/spec/associations_spec.rb +40 -160
  33. data/spec/attributes_spec.rb +1 -1
  34. data/spec/base_spec.rb +41 -13
  35. data/spec/callbacks_spec.rb +32 -0
  36. data/spec/coersion_spec.rb +1 -1
  37. data/spec/column_spec.rb +22 -12
  38. data/spec/dependency_spec.rb +5 -3
  39. data/spec/embedded_value_spec.rb +33 -17
  40. data/spec/has_many_association_spec.rb +173 -0
  41. data/spec/legacy_spec.rb +2 -2
  42. data/spec/load_command_spec.rb +59 -7
  43. data/spec/models/animal.rb +6 -2
  44. data/spec/models/animals_exhibit.rb +3 -1
  45. data/spec/models/career.rb +2 -1
  46. data/spec/models/comment.rb +3 -1
  47. data/spec/models/exhibit.rb +3 -1
  48. data/spec/models/fruit.rb +3 -1
  49. data/spec/models/person.rb +10 -1
  50. data/spec/models/post.rb +3 -1
  51. data/spec/models/project.rb +3 -1
  52. data/spec/models/section.rb +3 -1
  53. data/spec/models/serializer.rb +3 -1
  54. data/spec/models/user.rb +3 -1
  55. data/spec/models/zoo.rb +3 -1
  56. data/spec/paranoia_spec.rb +3 -1
  57. data/spec/postgres_spec.rb +54 -0
  58. data/spec/save_command_spec.rb +9 -5
  59. data/spec/schema_spec.rb +0 -91
  60. data/spec/single_table_inheritance_spec.rb +8 -0
  61. data/spec/table_spec.rb +46 -0
  62. data/spec/validates_uniqueness_of_spec.rb +19 -1
  63. metadata +8 -10
  64. data/lib/data_mapper/associations/has_one_association.rb +0 -77
  65. data/plugins/dataobjects/do_rb +0 -0
@@ -1,6 +1,6 @@
1
1
  require File.dirname(__FILE__) + "/spec_helper"
2
2
 
3
- describe DataMapper::Base do
3
+ describe DataMapper::Persistence do
4
4
 
5
5
  it 'should allow mass-assignment of attributes' do
6
6
  zoo = Zoo.new(:name => 'MassAssignment', :notes => 'This is a test.')
data/spec/base_spec.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  require File.dirname(__FILE__) + "/spec_helper"
2
2
 
3
- describe DataMapper::Base do
3
+ describe DataMapper::Persistence do
4
4
 
5
5
  it "attributes method should load all lazy-loaded values" do
6
6
  Animal.first(:name => 'Cup').attributes[:notes].should == 'I am a Cup!'
@@ -137,7 +137,7 @@ describe 'A new record' do
137
137
 
138
138
  it "should have original values when loaded from the database" do
139
139
  Person.create(:name => 'a')
140
- x = Person[:name => 'a']
140
+ x = Person.first(:name => 'a')
141
141
  x.original_values.should_not be_empty
142
142
  x.original_values.keys.should include(:name)
143
143
  x.original_values[:name].should == "a"
@@ -165,7 +165,9 @@ end
165
165
  describe 'Properties' do
166
166
 
167
167
  it 'should default to public method visibility' do
168
- class SoftwareEngineer < DataMapper::Base
168
+ class SoftwareEngineer
169
+ include DataMapper::Persistence
170
+
169
171
  set_table_name 'people'
170
172
  property :name, :string
171
173
  end
@@ -175,7 +177,9 @@ describe 'Properties' do
175
177
  end
176
178
 
177
179
  it 'should respect protected property options' do
178
- class SanitationEngineer < DataMapper::Base
180
+ class SanitationEngineer
181
+ include DataMapper::Persistence
182
+
179
183
  set_table_name 'people'
180
184
  property :name, :string, :reader => :protected
181
185
  property :age, :integer, :writer => :protected
@@ -186,7 +190,9 @@ describe 'Properties' do
186
190
  end
187
191
 
188
192
  it 'should respect private property options' do
189
- class ElectricalEngineer < DataMapper::Base
193
+ class ElectricalEngineer
194
+ include DataMapper::Persistence
195
+
190
196
  set_table_name 'people'
191
197
  property :name, :string, :reader => :private
192
198
  property :age, :integer, :writer => :private
@@ -197,7 +203,9 @@ describe 'Properties' do
197
203
  end
198
204
 
199
205
  it 'should set both reader and writer visibiliy when accessor option is passed' do
200
- class TrainEngineer < DataMapper::Base
206
+ class TrainEngineer
207
+ include DataMapper::Persistence
208
+
201
209
  property :name, :string, :accessor => :private
202
210
  end
203
211
 
@@ -206,32 +214,38 @@ describe 'Properties' do
206
214
  end
207
215
 
208
216
  it 'should only be listed in attributes if they have public getters' do
209
- class SalesEngineer < DataMapper::Base
217
+ class SalesEngineer
218
+ include DataMapper::Persistence
219
+
210
220
  set_table_name 'people'
211
221
  property :name, :string
212
222
  property :age, :integer, :reader => :private
213
223
  end
214
224
 
215
- @sam = SalesEngineer[:name => 'Sam']
225
+ @sam = SalesEngineer.first(:name => 'Sam')
216
226
  # note: id default key gets a public reader by default (but writer is protected)
217
227
  @sam.attributes.should == {:id => @sam.id, :name => @sam.name}
218
228
  end
219
229
 
220
230
  it 'should not allow mass assignment if private or protected' do
221
- class ChemicalEngineer < DataMapper::Base
231
+ class ChemicalEngineer
232
+ include DataMapper::Persistence
233
+
222
234
  set_table_name 'people'
223
235
  property :name, :string, :writer => :private
224
236
  property :age, :integer
225
237
  end
226
238
 
227
- @sam = ChemicalEngineer[:name => 'Sam']
239
+ @sam = ChemicalEngineer.first(:name => 'Sam')
228
240
  @sam.attributes = {:name => 'frank', :age => 101}
229
241
  @sam.age.should == 101
230
242
  @sam.name.should == 'Sam'
231
243
  end
232
244
 
233
245
  it 'should allow :protected to be passed as an alias for a public reader, protected writer' do
234
- class CivilEngineer < DataMapper::Base
246
+ class CivilEngineer
247
+ include DataMapper::Persistence
248
+
235
249
  set_table_name 'people'
236
250
  property :name, :string, :protected => true
237
251
  end
@@ -241,7 +255,9 @@ describe 'Properties' do
241
255
  end
242
256
 
243
257
  it 'should allow :private to be passed as an alias for a public reader, private writer' do
244
- class AudioEngineer < DataMapper::Base
258
+ class AudioEngineer
259
+ include DataMapper::Persistence
260
+
245
261
  set_table_name 'people'
246
262
  property :name, :string, :private => true
247
263
  end
@@ -252,11 +268,23 @@ describe 'Properties' do
252
268
 
253
269
  it 'should raise an error when invalid options are passsed' do
254
270
  lambda do
255
- class JumpyCow < DataMapper::Base
271
+ class JumpyCow
272
+ include DataMapper::Persistence
273
+
256
274
  set_table_name 'animals'
257
275
  property :name, :string, :laze => true
258
276
  end
259
277
  end.should raise_error(ArgumentError)
260
278
  end
261
279
 
280
+ it 'should raise an error when the first argument to index isnt an array' do
281
+ lambda do
282
+ class JumpyCow
283
+ include DataMapper::Persistence
284
+
285
+ set_table_name 'animals'
286
+ index :name, :parent
287
+ end
288
+ end.should raise_error(ArgumentError)
289
+ end
262
290
  end
@@ -44,4 +44,36 @@ describe DataMapper::Callbacks do
44
44
 
45
45
  end
46
46
 
47
+ it "should execute before_save regardless of dirty state" do
48
+
49
+ Post.before_save do |post|
50
+ post.instance_variable_set("@one", 'moo')
51
+ end
52
+
53
+ Post.before_save do |post|
54
+ post.instance_variable_set("@two", 'cow')
55
+ end
56
+
57
+ Post.before_save :red_cow
58
+
59
+ class Post
60
+ def red_cow
61
+ @three = "blue_cow"
62
+ end
63
+ end
64
+
65
+ post = Post.new(:title => 'bob')
66
+ post.save
67
+
68
+ post = Post.first(:title => 'bob')
69
+ post.instance_variable_get("@one").should be_nil
70
+ post.instance_variable_get("@two").should be_nil
71
+ post.instance_variable_get("@three").should be_nil
72
+
73
+ post.save
74
+ post.instance_variable_get("@one").should eql('moo')
75
+ post.instance_variable_get("@two").should eql('cow')
76
+ post.instance_variable_get("@three").should eql('blue_cow')
77
+ end
78
+
47
79
  end
@@ -18,7 +18,7 @@ describe DataMapper::Adapters::Sql::Coersion do
18
18
  dob = Date::today
19
19
  bob = Person.create(:name => 'DateCoersionTest', :date_of_birth => dob)
20
20
 
21
- bob2 = Person[:name => 'DateCoersionTest']
21
+ bob2 = Person.first(:name => 'DateCoersionTest')
22
22
 
23
23
  bob.date_of_birth.should eql(dob)
24
24
  bob2.date_of_birth.should eql(dob)
data/spec/column_spec.rb CHANGED
@@ -3,8 +3,13 @@ require File.dirname(__FILE__) + "/spec_helper"
3
3
  describe DataMapper::Adapters::Sql::Mappings::Column do
4
4
 
5
5
  before(:all) do
6
+ @mappings = DataMapper::Adapters::Sql::Mappings
6
7
  fixtures(:zoos)
7
8
  end
9
+
10
+ def table
11
+ @table ||= @mappings::Table.new(database(:mock).adapter, "Cow")
12
+ end
8
13
 
9
14
  it "should only lazy loading text columns by default" do
10
15
  table = database.table(Zoo)
@@ -18,21 +23,17 @@ describe DataMapper::Adapters::Sql::Mappings::Column do
18
23
  end
19
24
 
20
25
  it "should be unique within a set" do
21
-
22
- mappings = DataMapper::Adapters::Sql::Mappings
23
-
24
26
  columns = SortedSet.new
25
27
 
26
- table = mappings::Table.new(database(:mock).adapter, "Cow")
27
- columns << mappings::Column.new(database(:mock).adapter, table, :one, :string, 1)
28
- columns << mappings::Column.new(database(:mock).adapter, table, :two, :string, 2)
29
- columns << mappings::Column.new(database(:mock).adapter, table, :three, :string, 3)
28
+ columns << @mappings::Column.new(database(:mock).adapter, table, :one, :string, 1)
29
+ columns << @mappings::Column.new(database(:mock).adapter, table, :two, :string, 2)
30
+ columns << @mappings::Column.new(database(:mock).adapter, table, :three, :string, 3)
30
31
  columns.should have(3).entries
31
32
 
32
- columns << mappings::Column.new(database(:mock).adapter, table, :two, :integer, 3)
33
+ columns << @mappings::Column.new(database(:mock).adapter, table, :two, :integer, 3)
33
34
  columns.should have(3).entries
34
35
 
35
- columns << mappings::Column.new(database(:mock).adapter, table, :id, :integer, -1)
36
+ columns << @mappings::Column.new(database(:mock).adapter, table, :id, :integer, -1)
36
37
  columns.should have(4).entries
37
38
  end
38
39
 
@@ -92,10 +93,19 @@ describe DataMapper::Adapters::Sql::Mappings::Column do
92
93
  end
93
94
 
94
95
  it "should default the size of an integer column to 11" do
95
- mappings = DataMapper::Adapters::Sql::Mappings
96
- table = mappings::Table.new(database(:mock).adapter, "Zebu")
97
- integer = mappings::Column.new(database(:mock).adapter, table, :age, :integer, 1)
96
+ integer = @mappings::Column.new(database(:mock).adapter, table, :age, :integer, 1)
98
97
  integer.size.should == 11
99
98
  end
100
99
 
100
+ it "should be able to create a column with unique index" do
101
+ column = table.add_column("name", :string, :index => :unique)
102
+ column.unique?.should be_true
103
+ table.to_create_sql.should match(/UNIQUE/)
104
+ end
105
+
106
+ it "should be able to create an indexed column" do
107
+ column = table.add_column("age", :integer, :index => true)
108
+ column.index?.should be_true
109
+ table.to_create_index_sql[0].should match(/CREATE INDEX cow_age_index/)
110
+ end
101
111
  end
@@ -1,18 +1,20 @@
1
1
  require File.dirname(__FILE__) + "/spec_helper"
2
2
 
3
- describe DataMapper::Base do
3
+ describe DataMapper::Persistence do
4
4
 
5
5
  it "should be able to add a dependency for a class not yet defined" do
6
6
 
7
7
  $happy_cow_defined = false
8
8
 
9
- DataMapper::Base.dependencies.add('HappyCow') do |klass|
9
+ DataMapper::Persistence.dependencies.add('HappyCow') do |klass|
10
10
  klass.should eql(Object.const_get('HappyCow'))
11
11
  database.table(klass).key.name.should eql(:name)
12
12
  $happy_cow_defined = true
13
13
  end
14
14
 
15
- class HappyCow < DataMapper::Base
15
+ class HappyCow
16
+ include DataMapper::Persistence
17
+
16
18
  property :name, :string, :key => true
17
19
  end
18
20
 
@@ -3,7 +3,7 @@ require File.dirname(__FILE__) + "/spec_helper"
3
3
  describe DataMapper::EmbeddedValue do
4
4
 
5
5
  before(:all) do
6
- @bob = Person[:name => 'Bob']
6
+ @bob = Person.first(:name => 'Bob')
7
7
  end
8
8
 
9
9
  it 'should proxy getting values for you' do
@@ -19,7 +19,9 @@ describe DataMapper::EmbeddedValue do
19
19
  end
20
20
 
21
21
  it 'should not require prefix' do
22
- class PointyHeadedBoss < DataMapper::Base
22
+ class PointyHeadedBoss
23
+ include DataMapper::Persistence
24
+
23
25
  set_table_name 'people'
24
26
  property :name, :string
25
27
 
@@ -31,12 +33,14 @@ describe DataMapper::EmbeddedValue do
31
33
  end
32
34
  end
33
35
 
34
- @sam = PointyHeadedBoss[:name => 'Sam']
36
+ @sam = PointyHeadedBoss.first(:name => 'Sam')
35
37
  @sam.address.address_street.should == '1337 Duck Way'
36
38
  end
37
39
 
38
40
  it 'should add convenience methods to the non-embedded base' do
39
- class Employee < DataMapper::Base
41
+ class Employee
42
+ include DataMapper::Persistence
43
+
40
44
  set_table_name 'people'
41
45
  property :name, :string
42
46
 
@@ -46,12 +50,14 @@ describe DataMapper::EmbeddedValue do
46
50
  end
47
51
  end
48
52
 
49
- @sam = Employee[:name => 'Sam']
53
+ @sam = Employee.first(:name => 'Sam')
50
54
  @sam.address_street.should == '1337 Duck Way'
51
55
  end
52
56
 
53
57
  it 'should support lazy loading of embedded properties' do
54
- class Human < DataMapper::Base
58
+ class Human
59
+ include DataMapper::Persistence
60
+
55
61
  set_table_name 'people'
56
62
  property :name, :string
57
63
 
@@ -61,12 +67,14 @@ describe DataMapper::EmbeddedValue do
61
67
  end
62
68
  end
63
69
 
64
- @sam = Human[:name => 'Sam']
70
+ @sam = Human.first(:name => 'Sam')
65
71
  @sam.address.street.should == '1337 Duck Way'
66
72
  end
67
73
 
68
74
  it 'should default to public method visibility for all' do
69
- class SoftwareEngineer < DataMapper::Base
75
+ class SoftwareEngineer
76
+ include DataMapper::Persistence
77
+
70
78
  set_table_name 'people'
71
79
  property :name, :string
72
80
 
@@ -75,13 +83,15 @@ describe DataMapper::EmbeddedValue do
75
83
  end
76
84
  end
77
85
 
78
- @sam = SoftwareEngineer[:name => 'Sam']
86
+ @sam = SoftwareEngineer.first(:name => 'Sam')
79
87
  public_properties = @sam.address.class.public_instance_methods.select { |m| ["city", "city="].include?(m) }
80
88
  public_properties.length.should == 2
81
89
  end
82
90
 
83
91
  it 'should respect protected property options for all' do
84
- class SanitationEngineer < DataMapper::Base
92
+ class SanitationEngineer
93
+ include DataMapper::Persistence
94
+
85
95
  set_table_name 'people'
86
96
  property :name, :string
87
97
 
@@ -91,13 +101,15 @@ describe DataMapper::EmbeddedValue do
91
101
  end
92
102
  end
93
103
 
94
- @sam = SanitationEngineer[:name => 'Sam']
104
+ @sam = SanitationEngineer.first(:name => 'Sam')
95
105
  protected_properties = @sam.address.class.protected_instance_methods.select { |m| ["city", "street"].include?(m) }
96
106
  protected_properties.length.should == 2
97
107
  end
98
108
 
99
109
  it 'should respect private property options for all' do
100
- class ElectricalEngineer < DataMapper::Base
110
+ class ElectricalEngineer
111
+ include DataMapper::Persistence
112
+
101
113
  set_table_name 'people'
102
114
  property :name, :string, :reader => :private
103
115
 
@@ -107,13 +119,15 @@ describe DataMapper::EmbeddedValue do
107
119
  end
108
120
  end
109
121
 
110
- @sam = ElectricalEngineer[:name => 'Sam']
122
+ @sam = ElectricalEngineer.first(:name => 'Sam')
111
123
  private_properties = @sam.address.class.private_instance_methods.select { |m| ["city=", "street="].include?(m) }
112
124
  private_properties.length.should == 2
113
125
  end
114
126
 
115
127
  it 'should set both reader and writer visibiliy for all when accessor option is passed' do
116
- class TrainEngineer < DataMapper::Base
128
+ class TrainEngineer
129
+ include DataMapper::Persistence
130
+
117
131
  set_table_name 'people'
118
132
  property :name, :string, :reader => :private
119
133
 
@@ -122,13 +136,15 @@ describe DataMapper::EmbeddedValue do
122
136
  end
123
137
  end
124
138
 
125
- @sam = TrainEngineer[:name => 'Sam']
139
+ @sam = TrainEngineer.first(:name => 'Sam')
126
140
  private_properties = @sam.address.class.private_instance_methods.select { |m| ["city", "city="].include?(m) }
127
141
  private_properties.length.should == 2
128
142
  end
129
143
 
130
144
  it 'should allow individual properties to override method visibility options passed on the block' do
131
- class ChemicalEngineer < DataMapper::Base
145
+ class ChemicalEngineer
146
+ include DataMapper::Persistence
147
+
132
148
  set_table_name 'people'
133
149
  property :name, :string
134
150
 
@@ -138,7 +154,7 @@ describe DataMapper::EmbeddedValue do
138
154
  end
139
155
  end
140
156
 
141
- @sam = ChemicalEngineer[:name => 'Sam']
157
+ @sam = ChemicalEngineer.first(:name => 'Sam')
142
158
  public_properties = @sam.address.class.public_instance_methods.select { |m| ["street", "street="].include?(m) }
143
159
  public_properties.length.should == 2
144
160
  end
@@ -0,0 +1,173 @@
1
+ require File.dirname(__FILE__) + "/spec_helper"
2
+
3
+ describe DataMapper::Associations::HasManyAssociation do
4
+
5
+ before(:all) do
6
+ fixtures(:zoos)
7
+ fixtures(:exhibits)
8
+ fixtures(:fruit)
9
+ fixtures(:animals)
10
+ end
11
+
12
+ after(:all) do
13
+ fixtures(:fruit)
14
+ fixtures(:animals)
15
+ end
16
+
17
+ before(:each) do
18
+ @zoo = Zoo.new(:name => "ZOO")
19
+ @zoo.save
20
+ end
21
+
22
+ after(:each) do
23
+ @zoo.destroy!
24
+ end
25
+
26
+ it "should return an empty Enumerable for new objects" do
27
+ project = Project.new
28
+ project.sections.should be_a_kind_of(Enumerable)
29
+ project.sections.should be_empty
30
+ end
31
+
32
+ it "should display correctly when inspected" do
33
+ Zoo.first(:name => 'Dallas').exhibits.inspect.should match(/\#\<Exhibit\:0x.{7}/)
34
+ end
35
+
36
+ it 'should lazily-load the association when Enumerable methods are called' do
37
+ database do |db|
38
+ san_diego = Zoo.first(:name => 'San Diego')
39
+ san_diego.exhibits.size.should == 2
40
+ san_diego.exhibits.should include(Exhibit.first(:name => 'Monkey Mayhem'))
41
+ end
42
+ end
43
+
44
+ it 'should eager-load associations for an entire set' do
45
+ database do
46
+ zoos = Zoo.all
47
+ zoos.each do |zoo|
48
+ zoo.exhibits.each do |exhibit|
49
+ exhibit.zoo.should == zoo
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ it "should be dirty even when clean objects are associated" do
56
+ zoo = Zoo.first(:name => 'New York')
57
+ zoo.exhibits << Exhibit.first
58
+ zoo.should be_dirty
59
+ end
60
+
61
+ it "should proxy associations on the associated type" do
62
+ Zoo.first(:name => 'Miami').exhibits.animals.size.should == 1
63
+ end
64
+
65
+ it "should have a valid zoo setup for testing" do
66
+ @zoo.should be_valid
67
+ @zoo.should_not be_a_new_record
68
+ @zoo.id.should_not be_nil
69
+ end
70
+
71
+ it "should generate the SQL for a join statement" do
72
+ exhibits_association = database(:mock).schema[Zoo].associations.find { |a| a.name == :exhibits }
73
+
74
+ exhibits_association.to_sql.should == <<-EOS.compress_lines
75
+ JOIN `exhibits` ON `exhibits`.`zoo_id` = `zoos`.`id`
76
+ EOS
77
+ end
78
+
79
+ it "should add an item to an association" do
80
+ bear = Exhibit.new( :name => "Bear")
81
+ @zoo.exhibits << bear
82
+ @zoo.exhibits.should include(bear)
83
+ end
84
+
85
+ it "should build a new item" do
86
+ bear = @zoo.exhibits.build( :name => "Bear" )
87
+ bear.should be_kind_of(Exhibit)
88
+ @zoo.exhibits.should include(bear)
89
+ end
90
+
91
+ it "should not save the item when building" do
92
+ bear = @zoo.exhibits.build( :name => "Bear" )
93
+ bear.should be_new_record
94
+ end
95
+
96
+ it "should create a new item" do
97
+ bear = @zoo.exhibits.create( :name => "Bear" )
98
+ bear.should be_kind_of(Exhibit)
99
+ @zoo.exhibits.should include(bear)
100
+ end
101
+
102
+ it "should save the item when creating" do
103
+ bear = @zoo.exhibits.create( :name => "Bear" )
104
+ bear.should_not be_new_record
105
+ end
106
+
107
+ it "should set the association to a saved target when added with <<" do
108
+ pirahna = Exhibit.new(:name => "Pirahna")
109
+ pirahna.zoo_id.should be_nil
110
+
111
+ @zoo.exhibits << pirahna
112
+ pirahna.zoo.should == @zoo
113
+ end
114
+
115
+ it "should set the association to a non-saved target when added with <<" do
116
+ zoo = Zoo.new(:name => "My Zoo")
117
+ kangaroo = Exhibit.new(:name => "Kangaroo")
118
+ zoo.exhibits << kangaroo
119
+ kangaroo.zoo.should == zoo
120
+ end
121
+
122
+ it "should set the id of the exhibit when the associated zoo is saved" do
123
+ snake = Exhibit.new(:name => "Snake")
124
+ @zoo.exhibits << snake
125
+ @zoo.save
126
+ @zoo.id.should == snake.zoo_id
127
+ end
128
+
129
+ it "should set the id of an already saved exibit if it's added to a different zoo" do
130
+ beaver = Exhibit.new(:name => "Beaver")
131
+ beaver.save
132
+ beaver.should_not be_a_new_record
133
+ @zoo.exhibits << beaver
134
+ @zoo.save
135
+ beaver.zoo.should == @zoo
136
+ beaver.zoo_id.should == @zoo.id
137
+ end
138
+
139
+ it "should set the size of the assocation" do
140
+ @zoo.exhibits << Exhibit.new(:name => "anonymous")
141
+ @zoo.exhibits.size.should == 1
142
+ end
143
+
144
+ it "should give the association when an inspect is done on it" do
145
+ whale = Exhibit.new(:name => "Whale")
146
+ @zoo.exhibits << whale
147
+ @zoo.exhibits.should_not == "nil"
148
+ @zoo.exhibits.inspect.should_not be_nil
149
+ end
150
+
151
+ it "should generate the SQL for a join statement" do
152
+ fruit_association = database(:mock).schema[Animal].associations.find { |a| a.name == :favourite_fruit }
153
+
154
+ fruit_association.to_sql.should == <<-EOS.compress_lines
155
+ JOIN `fruit` ON `fruit`.`devourer_id` = `animals`.`id`
156
+ EOS
157
+ end
158
+
159
+ it "is assigned a devourer_id" do
160
+ bob = Animal.new(:name => 'bob')
161
+ fruit = Fruit.first
162
+ bob.favourite_fruit = fruit
163
+
164
+ bob.save
165
+
166
+ bob.reload!
167
+ fruit.devourer_id.should eql(bob.id)
168
+ bob.favourite_fruit.should == fruit
169
+
170
+ fruit.reload!
171
+ fruit.devourer_of_souls.should == bob
172
+ end
173
+ end