datamapper 0.2.5 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. data/CHANGELOG +5 -1
  2. data/FAQ +96 -0
  3. data/QUICKLINKS +12 -0
  4. data/README +57 -155
  5. data/environment.rb +61 -43
  6. data/example.rb +30 -12
  7. data/lib/data_mapper.rb +6 -1
  8. data/lib/data_mapper/adapters/abstract_adapter.rb +0 -57
  9. data/lib/data_mapper/adapters/data_object_adapter.rb +203 -97
  10. data/lib/data_mapper/adapters/mysql_adapter.rb +4 -0
  11. data/lib/data_mapper/adapters/postgresql_adapter.rb +7 -1
  12. data/lib/data_mapper/adapters/sql/coersion.rb +3 -2
  13. data/lib/data_mapper/adapters/sql/commands/load_command.rb +29 -10
  14. data/lib/data_mapper/adapters/sql/mappings/associations_set.rb +4 -0
  15. data/lib/data_mapper/adapters/sql/mappings/column.rb +13 -9
  16. data/lib/data_mapper/adapters/sql/mappings/conditions.rb +172 -0
  17. data/lib/data_mapper/adapters/sql/mappings/table.rb +43 -17
  18. data/lib/data_mapper/adapters/sqlite3_adapter.rb +9 -2
  19. data/lib/data_mapper/associations.rb +75 -3
  20. data/lib/data_mapper/associations/belongs_to_association.rb +70 -36
  21. data/lib/data_mapper/associations/has_and_belongs_to_many_association.rb +195 -86
  22. data/lib/data_mapper/associations/has_many_association.rb +168 -61
  23. data/lib/data_mapper/associations/has_n_association.rb +23 -3
  24. data/lib/data_mapper/attributes.rb +73 -0
  25. data/lib/data_mapper/auto_migrations.rb +2 -6
  26. data/lib/data_mapper/base.rb +5 -9
  27. data/lib/data_mapper/database.rb +4 -3
  28. data/lib/data_mapper/embedded_value.rb +66 -30
  29. data/lib/data_mapper/identity_map.rb +1 -3
  30. data/lib/data_mapper/is/tree.rb +121 -0
  31. data/lib/data_mapper/migration.rb +155 -0
  32. data/lib/data_mapper/persistence.rb +532 -218
  33. data/lib/data_mapper/property.rb +306 -0
  34. data/lib/data_mapper/query.rb +164 -0
  35. data/lib/data_mapper/support/blank.rb +2 -2
  36. data/lib/data_mapper/support/connection_pool.rb +5 -6
  37. data/lib/data_mapper/support/enumerable.rb +3 -3
  38. data/lib/data_mapper/support/errors.rb +10 -1
  39. data/lib/data_mapper/support/inflector.rb +174 -238
  40. data/lib/data_mapper/support/object.rb +54 -0
  41. data/lib/data_mapper/support/serialization.rb +19 -1
  42. data/lib/data_mapper/support/string.rb +7 -16
  43. data/lib/data_mapper/support/symbol.rb +3 -15
  44. data/lib/data_mapper/support/typed_set.rb +68 -0
  45. data/lib/data_mapper/types/base.rb +44 -0
  46. data/lib/data_mapper/types/string.rb +34 -0
  47. data/lib/data_mapper/validations/number_validator.rb +40 -0
  48. data/lib/data_mapper/validations/string_validator.rb +20 -0
  49. data/lib/data_mapper/validations/validator.rb +13 -0
  50. data/performance.rb +26 -1
  51. data/profile_data_mapper.rb +1 -1
  52. data/rakefile.rb +42 -2
  53. data/spec/acts_as_tree_spec.rb +11 -3
  54. data/spec/adapters/data_object_adapter_spec.rb +31 -0
  55. data/spec/associations/belongs_to_association_spec.rb +98 -0
  56. data/spec/associations/has_and_belongs_to_many_association_spec.rb +377 -0
  57. data/spec/associations/has_many_association_spec.rb +337 -0
  58. data/spec/attributes_spec.rb +23 -1
  59. data/spec/auto_migrations_spec.rb +86 -29
  60. data/spec/callbacks_spec.rb +107 -0
  61. data/spec/column_spec.rb +5 -2
  62. data/spec/count_command_spec.rb +33 -1
  63. data/spec/database_spec.rb +18 -0
  64. data/spec/dependency_spec.rb +4 -2
  65. data/spec/embedded_value_spec.rb +8 -8
  66. data/spec/fixtures/people.yaml +1 -1
  67. data/spec/fixtures/projects.yaml +10 -1
  68. data/spec/fixtures/tasks.yaml +6 -0
  69. data/spec/fixtures/tasks_tasks.yaml +2 -0
  70. data/spec/fixtures/tomatoes.yaml +1 -0
  71. data/spec/is_a_tree_spec.rb +149 -0
  72. data/spec/load_command_spec.rb +71 -9
  73. data/spec/magic_columns_spec.rb +17 -2
  74. data/spec/migration_spec.rb +267 -0
  75. data/spec/models/animal.rb +1 -1
  76. data/spec/models/candidate.rb +8 -0
  77. data/spec/models/career.rb +1 -1
  78. data/spec/models/chain.rb +8 -0
  79. data/spec/models/comment.rb +1 -1
  80. data/spec/models/exhibit.rb +1 -1
  81. data/spec/models/fence.rb +7 -0
  82. data/spec/models/fruit.rb +2 -2
  83. data/spec/models/job.rb +8 -0
  84. data/spec/models/person.rb +2 -3
  85. data/spec/models/post.rb +1 -1
  86. data/spec/models/project.rb +21 -1
  87. data/spec/models/section.rb +1 -1
  88. data/spec/models/serializer.rb +1 -1
  89. data/spec/models/task.rb +9 -0
  90. data/spec/models/tomato.rb +27 -0
  91. data/spec/models/user.rb +8 -2
  92. data/spec/models/zoo.rb +2 -7
  93. data/spec/paranoia_spec.rb +1 -1
  94. data/spec/{base_spec.rb → persistence_spec.rb} +207 -18
  95. data/spec/postgres_spec.rb +48 -6
  96. data/spec/property_spec.rb +90 -9
  97. data/spec/query_spec.rb +71 -5
  98. data/spec/save_command_spec.rb +11 -0
  99. data/spec/spec_helper.rb +14 -11
  100. data/spec/support/blank_spec.rb +8 -0
  101. data/spec/support/inflector_spec.rb +41 -0
  102. data/spec/support/object_spec.rb +9 -0
  103. data/spec/{serialization_spec.rb → support/serialization_spec.rb} +1 -1
  104. data/spec/support/silence_spec.rb +15 -0
  105. data/spec/{support_spec.rb → support/string_spec.rb} +3 -3
  106. data/spec/support/struct_spec.rb +12 -0
  107. data/spec/support/typed_set_spec.rb +66 -0
  108. data/spec/table_spec.rb +3 -3
  109. data/spec/types/string.rb +81 -0
  110. data/spec/validates_uniqueness_of_spec.rb +17 -0
  111. data/spec/validations/number_validator.rb +59 -0
  112. data/spec/validations/string_validator.rb +14 -0
  113. metadata +59 -17
  114. data/do_performance.rb +0 -153
  115. data/lib/data_mapper/support/active_record_impersonation.rb +0 -103
  116. data/lib/data_mapper/support/weak_hash.rb +0 -46
  117. data/spec/active_record_impersonation_spec.rb +0 -129
  118. data/spec/associations_spec.rb +0 -232
  119. data/spec/conditions_spec.rb +0 -49
  120. data/spec/has_many_association_spec.rb +0 -173
  121. data/spec/models/animals_exhibit.rb +0 -8
@@ -13,7 +13,7 @@ task :default => 'dm:spec'
13
13
 
14
14
  task :environment => 'dm:environment'
15
15
 
16
- namespace :dm do
16
+ dm = namespace :dm do
17
17
 
18
18
  desc "Setup Environment"
19
19
  task :environment do
@@ -24,6 +24,10 @@ namespace :dm do
24
24
  Spec::Rake::SpecTask.new('spec') do |t|
25
25
  t.spec_opts = ["--format", "specdoc", "--colour"]
26
26
  t.spec_files = FileList[(ENV['FILES'] || 'spec/**/*_spec.rb')]
27
+ unless ENV['NO_RCOV']
28
+ t.rcov = true
29
+ t.rcov_opts = ['--exclude', 'examples,spec,environment.rb']
30
+ end
27
31
  end
28
32
 
29
33
  desc "Run comparison with ActiveRecord"
@@ -36,12 +40,48 @@ namespace :dm do
36
40
  load 'profile_data_mapper.rb'
37
41
  end
38
42
 
43
+ namespace :spec do
44
+ def set_model_mode(fl, mode)
45
+ fl.each do |fname|
46
+ contents = File.open(fname, 'r') { |f| f.read }
47
+
48
+ if mode == :compat
49
+ contents.gsub!(/#< DataMapper::Base #/, '< DataMapper::Base #')
50
+ contents.gsub!(/include DataMapper::Persistence/, '#include DataMapper::Persistence')
51
+ elsif mode == :normal
52
+ contents.gsub!(/< DataMapper::Base #/, '#< DataMapper::Base #')
53
+ contents.gsub!(/#include DataMapper::Persistence/, 'include DataMapper::Persistence')
54
+ else
55
+ raise "Unknown mode #{mode}."
56
+ end
57
+
58
+ File.open(fname, 'w') do |f|
59
+ f.write(contents)
60
+ end
61
+ end
62
+ end
63
+
64
+ desc "Run specifications with DataMapper::Base compatibilty"
65
+ task :compat do
66
+ fl = FileList['spec/**/*.rb'].exclude(/\b\.svn/)
67
+
68
+ set_model_mode(fl, :compat)
69
+
70
+ begin
71
+ dm[:spec].execute
72
+ ensure
73
+ set_model_mode(fl, :normal)
74
+ end
75
+ end
76
+ end
39
77
  end
40
78
 
41
- PACKAGE_VERSION = '0.2.5'
79
+ PACKAGE_VERSION = '0.3.0'
42
80
 
43
81
  PACKAGE_FILES = FileList[
44
82
  'README',
83
+ 'FAQ',
84
+ 'QUICKLINKS',
45
85
  'CHANGELOG',
46
86
  'MIT-LICENSE',
47
87
  '*.rb',
@@ -3,13 +3,17 @@ require File.dirname(__FILE__) + "/spec_helper"
3
3
  describe('A tree') do
4
4
 
5
5
  before(:all) do
6
- class Node
6
+ class Node #< DataMapper::Base # please do not remove this
7
7
  include DataMapper::Persistence
8
8
 
9
9
  property :name, :string
10
10
 
11
11
  belongs_to :parent, :class => 'Node'
12
12
  has_many :children, :class => 'Node', :foreign_key => 'parent_id'
13
+
14
+ def <=>(other)
15
+ name <=> other.name
16
+ end
13
17
  end
14
18
 
15
19
  Node.auto_migrate!
@@ -51,9 +55,13 @@ describe('A tree') do
51
55
  grand.should have(2).children
52
56
 
53
57
  root.should == grand # true since +root+ and +grand+ are objects with identical types and attributes.
54
- root.should_not eql(grand) # false since +root+ and +grand+ are in different sessions.
58
+ root.object_id.should_not eql(grand.object_id) # false since +root+ and +grand+ are in different sessions.
55
59
 
56
- grand.children[0].children[0].name.should == 'one_one'
60
+ grand.children.should include(one)
61
+ one.reload
62
+ one.children.should include(one_one)
63
+
64
+ grand.children.first.children.first.should eql(one_one)
57
65
  end
58
66
 
59
67
  end
@@ -0,0 +1,31 @@
1
+ require File.dirname(__FILE__) + "/../spec_helper"
2
+
3
+ describe DataMapper::Adapters::DataObjectAdapter do
4
+
5
+ before(:all) do
6
+ fixtures(:zoos)
7
+ end
8
+
9
+ if ENV['ADAPTER'] == 'postgresql'
10
+ it "should be able to be given a port" do
11
+ options = {
12
+ :adapter => 'postgresql',
13
+ :database => 'data_mapper_1',
14
+ :port => 4001
15
+ }
16
+ db = DataMapper::Database.setup(:my_postgres, options)
17
+ db.port.should == 4001
18
+ end
19
+ end
20
+
21
+ it "should use DB defaults when creating an empty record" do
22
+ comment = Comment.create({})
23
+ comment.new_record?.should be_false
24
+ end
25
+
26
+ it "should raise an argument error on create if an attribute value is not a primitive" do
27
+ lambda { Zoo.create(:name => [nil, 'bob']) }.should raise_error(ArgumentError)
28
+ end
29
+
30
+ it "should accept a subclass as a valid type if the parent is a valid type"
31
+ end
@@ -0,0 +1,98 @@
1
+ require File.dirname(__FILE__) + "/../spec_helper"
2
+
3
+ describe DataMapper::Associations::BelongsToAssociation do
4
+ before(:all) do
5
+ fixtures(:zoos)
6
+ end
7
+
8
+ before(:each) do
9
+ @aviary = Exhibit.first(:name => 'Monkey Mayhem')
10
+ end
11
+
12
+ it "should provide a shallow_append method that doesn't impact the complementary association" do
13
+ project = Project.new
14
+ section = Section.new
15
+ section.send(:project_association).shallow_append(project)
16
+ project.sections.should be_empty
17
+ end
18
+
19
+ it 'has a zoo association' do
20
+ @aviary.zoo.class.should == Zoo
21
+ Exhibit.new.zoo.should == nil
22
+ end
23
+
24
+ it 'belongs to a zoo' do
25
+ @aviary.zoo.should == @aviary.database_context.first(Zoo, :name => 'San Diego')
26
+ end
27
+
28
+ it "is assigned a zoo_id" do
29
+ zoo = Zoo.first
30
+ exhibit = Exhibit.new(:name => 'bob')
31
+ exhibit.zoo = zoo
32
+ exhibit.instance_variable_get("@zoo_id").should == zoo.id
33
+
34
+ exhibit.save.should eql(true)
35
+
36
+ zoo2 = Zoo.first
37
+ zoo2.exhibits.should include(exhibit)
38
+
39
+ exhibit.destroy!
40
+
41
+ zoo = Zoo.new(:name => 'bob')
42
+ bob = Exhibit.new(:name => 'bob')
43
+ zoo.exhibits << bob
44
+ zoo.save.should eql(true)
45
+
46
+ zoo.exhibits.first.should_not be_a_new_record
47
+
48
+ bob.destroy!
49
+ zoo.destroy!
50
+ end
51
+
52
+ it "should not assign zoo_id when passed nil" do
53
+ # pending "http://wm.lighthouseapp.com/projects/4819-datamapper/tickets/147"
54
+ exhibit = Exhibit.first
55
+ exhibit.zoo_id = nil
56
+ exhibit.zoo_id.should be_nil
57
+ exhibit.save.should == true
58
+
59
+ exhibit.reload
60
+ exhibit.zoo_id.should be_nil
61
+ exhibit.zoo.should be_nil
62
+ end
63
+
64
+ it "should be marked dirty if the complementary association is a new_record and the instance is already saved" do
65
+ zoo = Zoo.new(:name => "My Zoo")
66
+ tiger = Exhibit.create(:name => "Tiger")
67
+ zoo.exhibits << tiger
68
+ tiger.should be_dirty
69
+ end
70
+
71
+ it 'can build its zoo' do
72
+ database do |db|
73
+ e = Exhibit.new({:name => 'Super Extra Crazy Monkey Cage'})
74
+ e.zoo.should == nil
75
+ e.build_zoo({:name => 'Monkey Zoo'})
76
+ e.zoo.class == Zoo
77
+ e.zoo.new_record?.should == true
78
+
79
+ e.save
80
+ end
81
+ end
82
+
83
+ it 'can build its zoo' do
84
+ database do |db|
85
+ e = Exhibit.new({:name => 'Super Extra Crazy Monkey Cage'})
86
+ e.zoo.should == nil
87
+ e.create_zoo({:name => 'Monkey Zoo'})
88
+ e.zoo.class == Zoo
89
+ e.zoo.new_record?.should == false
90
+ e.save
91
+ end
92
+ end
93
+
94
+ after(:all) do
95
+ fixtures('zoos')
96
+ fixtures('exhibits')
97
+ end
98
+ end
@@ -0,0 +1,377 @@
1
+ require File.dirname(__FILE__) + "/../spec_helper"
2
+
3
+ describe DataMapper::Associations::HasAndBelongsToManyAssociation do
4
+
5
+ before(:all) do
6
+ fixtures(:animals)
7
+ fixtures(:exhibits)
8
+ end
9
+
10
+ before(:each) do
11
+ @amazonia = Exhibit.first :name => 'Amazonia'
12
+ end
13
+
14
+ it "should generate the SQL for a join statement" do
15
+ animals_association = database(:mock).schema[Exhibit].associations.find { |a| a.name == :animals }
16
+
17
+ animals_association.to_sql.should == <<-EOS.compress_lines
18
+ JOIN `animals_exhibits` ON `animals_exhibits`.`exhibit_id` = `exhibits`.`id`
19
+ JOIN `animals` ON `animals`.`id` = `animals_exhibits`.`animal_id`
20
+ EOS
21
+ end
22
+
23
+ it "should load associations" do
24
+ database do
25
+ froggy = Animal.first(:name => 'Frog')
26
+ froggy.exhibits.size.should == 1
27
+ froggy.exhibits.first.should == Exhibit.first(:name => 'Amazonia')
28
+ end
29
+ end
30
+
31
+ it 'has an animals association' do
32
+ [@amazonia, Exhibit.new].each do |exhibit|
33
+ exhibit.animals.class.should == DataMapper::Associations::HasAndBelongsToManyAssociation::Set
34
+ end
35
+ end
36
+
37
+ it 'has many animals' do
38
+ @amazonia.animals.size.should == 1
39
+ end
40
+
41
+ it 'should load associations magically' do
42
+ Exhibit.all.each do |exhibit|
43
+ exhibit.animals.each do |animal|
44
+ animal.exhibits.should include(exhibit)
45
+ end
46
+ end
47
+ end
48
+
49
+ it 'should allow association of additional objects' do
50
+ buffalo = Animal.create(:name => "Buffalo")
51
+ @amazonia.animals << buffalo
52
+ @amazonia.animals.size.should == 2
53
+ @amazonia.save!
54
+ @amazonia.reload!
55
+ @amazonia.animals.should have(2).entries
56
+
57
+ other = Exhibit[@amazonia.id]
58
+ other.animals.should have(2).entries
59
+
60
+ @amazonia.animals.delete(buffalo).should_not be_nil
61
+ @amazonia.animals.should be_dirty
62
+ @amazonia.save!
63
+
64
+ other = Exhibit[@amazonia.id]
65
+ other.animals.should have(1).entries
66
+ end
67
+
68
+ it "should allow association of additional objects (CLEAN)" do
69
+ # pending "http://wm.lighthouseapp.com/projects/4819-datamapper/tickets/92"
70
+
71
+ ted = Exhibit.create(:name => 'Ted')
72
+ ted.should_not be_dirty
73
+
74
+ zest = Animal.create(:name => 'Zest')
75
+ zest.should_not be_dirty
76
+
77
+ ted.animals << zest
78
+ ted.should be_dirty
79
+ ted.save
80
+
81
+ ted.reload!
82
+ ted.should_not be_dirty
83
+ ted.should have(1).animals
84
+
85
+ ted2 = Exhibit[ted.key]
86
+ ted2.should_not be_dirty
87
+ ted2.should have(1).animals
88
+
89
+ ted2.destroy!
90
+ zest.destroy!
91
+ end
92
+
93
+ it 'should allow you to fill and clear an association' do
94
+ marcy = Exhibit.create(:name => 'marcy')
95
+
96
+ Animal.all.each do |animal|
97
+ marcy.animals << animal
98
+ end
99
+
100
+ marcy.save.should eql(true)
101
+ marcy.should have(Animal.count).animals
102
+
103
+ marcy.animals.clear
104
+ marcy.should have(0).animals
105
+
106
+ marcy.save.should eql(true)
107
+
108
+ marcys_stand_in = Exhibit[marcy.id]
109
+ marcys_stand_in.should have(0).animals
110
+
111
+ marcy.destroy!
112
+ end
113
+
114
+ it 'should allow you to delete a specific association member' do
115
+ walter = Exhibit.create(:name => 'walter')
116
+
117
+ Animal.all.each do |animal|
118
+ walter.animals << animal
119
+ end
120
+
121
+ walter.save.should eql(true)
122
+ walter.should have(Animal.count).animals
123
+
124
+ delete_me = walter.animals.first
125
+ walter.animals.delete(delete_me).should eql(delete_me)
126
+ walter.animals.delete(delete_me).should eql(nil)
127
+
128
+ walter.should have(Animal.count - 1).animals
129
+ walter.save.should eql(true)
130
+
131
+ walters_stand_in = Exhibit[walter.id]
132
+ walters_stand_in.animals.size.should eql(walter.animals.size)
133
+
134
+ walter.destroy!
135
+ end
136
+
137
+ it "should allow updates to associations using association_keys=" do
138
+ # pending "http://wm.lighthouseapp.com/projects/4819-datamapper/tickets/109-associations-should-support-association_keys-methods"
139
+ database(:default) do
140
+ meerkat = Animal.create(:name => "Meerkat")
141
+ dunes = Exhibit.create(:name => "Dunes")
142
+
143
+
144
+ dunes.animals.should be_empty
145
+ dunes.send(:animals_keys=, meerkat.key)
146
+ dunes.save.should be_true
147
+
148
+ dunes.should have(1).animals
149
+ dunes.animals.should include(meerkat)
150
+
151
+ dunes.reload!
152
+ dunes.should have(1).animals
153
+
154
+ dunes.destroy!
155
+ meerkat.destroy!
156
+ end
157
+ end
158
+
159
+ it "should allow you to 'append' multiple associated objects at once" do
160
+ dunes = Exhibit.create(:name => 'Dunes')
161
+
162
+ lambda { dunes.animals << @amazonia.animals }.should_not raise_error(ArgumentError)
163
+ lambda { dunes.animals << Animal.all }.should_not raise_error(ArgumentError)
164
+
165
+ dunes.destroy!
166
+ end
167
+
168
+ it "should raise an error when attempting to associate an object not of the correct type (assuming added model doesn't inherit from the correct type)" do
169
+ # pending("see: http://wm.lighthouseapp.com/projects/4819-datamapper/tickets/91")
170
+ @amazonia.animals.should_not be_empty
171
+ chuck = Person.new(:name => "Chuck")
172
+
173
+ ## InvalidRecord isn't the error we should use here....needs to be changed
174
+ lambda { @amazonia.animals << chuck }.should raise_error(ArgumentError)
175
+
176
+ end
177
+
178
+ it "should associate an object which has inherited from the correct type into an association" do
179
+ # pending("see: http://wm.lighthouseapp.com/projects/4819-datamapper/tickets/91")
180
+ programmer = Career.first(:name => 'Programmer')
181
+ programmer.followers.should_not be_empty
182
+
183
+ sales_person = SalesPerson.new(:name => 'Chuck')
184
+
185
+ lambda { programmer.followers << sales_person }.should_not raise_error(ArgumentError)
186
+
187
+ end
188
+
189
+ it "should correctly handle dependent associations ~cascading destroy~ (:destroy)" do
190
+ class Chain
191
+ has_and_belongs_to_many :chains, :dependent => :destroy
192
+ end
193
+ Chain.auto_migrate!
194
+
195
+ chain = Chain.create(:name => "1")
196
+ chain.chains << Chain.create(:name => "2")
197
+ chain.chains << Chain.create(:name => "3")
198
+ chain.chains << Chain.create(:name => "4")
199
+ chain.save
200
+ chain4 = Chain.create(:name => "5")
201
+ chain.chains.first.chains << chain4
202
+ chain.chains.first.save
203
+ chain4.chains << Chain.first(:name => "3")
204
+ chain4.save
205
+ chain = Chain[chain.key]
206
+
207
+ chain.destroy!
208
+ Chain.first(:name => "2").should be_nil
209
+ Chain.first(:name => "3").should be_nil
210
+ Chain.first(:name => "4").should be_nil
211
+ Chain.first(:name => "5").should be_nil
212
+
213
+ Chain.delete_all
214
+ end
215
+
216
+ it "should correctly handle dependent associations ~no cascade~ (:delete)" do
217
+ class Chain
218
+ has_and_belongs_to_many :chains, :dependent => :delete
219
+ end
220
+ Chain.auto_migrate!
221
+
222
+ chain = Chain.create(:name => "1")
223
+ chain.chains << Chain.create(:name => "2")
224
+ chain.chains << Chain.create(:name => "3")
225
+ chain.chains << Chain.create(:name => "4")
226
+ chain.save
227
+ chain5 = Chain.create(:name => "5")
228
+ chain.chains.first.chains << chain5
229
+ chain.chains.first.save
230
+ chain = Chain[chain.key]
231
+
232
+ chain.destroy!
233
+ Chain.first(:name => "2").should be_nil
234
+ Chain.first(:name => "3").should be_nil
235
+ Chain.first(:name => "4").should be_nil
236
+ Chain.first(:name => "5").should_not be_nil
237
+
238
+ Chain.delete_all
239
+ end
240
+
241
+ it "should correctly handle dependent associations (:protect)" do
242
+ class Chain
243
+ has_and_belongs_to_many :chains, :dependent => :protect
244
+ end
245
+ Chain.auto_migrate!
246
+
247
+ chain = Chain.create(:name => "1")
248
+ chain.chains << Chain.create(:name => "2")
249
+ chain.chains << Chain.create(:name => "3")
250
+ chain.chains << Chain.create(:name => "4")
251
+ chain.save
252
+ chain4 = Chain.create(:name => "5")
253
+ chain.chains.first.chains << chain4
254
+ chain.chains.first.save
255
+ chain = Chain[chain.key]
256
+
257
+ lambda { chain.destroy! }.should raise_error(DataMapper::AssociationProtectedError)
258
+
259
+ Chain.delete_all
260
+ end
261
+
262
+ it "should throw AssociationProtectedError even when @items have not been loaded yet (:protect)" do
263
+ class Chain
264
+ has_and_belongs_to_many :chains, :dependent => :protect
265
+ end
266
+ Chain.auto_migrate!
267
+
268
+ chain = Chain.create(:name => "1")
269
+ chain.chains << Chain.create(:name => "2")
270
+ chain.chains << Chain.create(:name => "3")
271
+ chain.chains << Chain.create(:name => "4")
272
+ chain.save
273
+ chain4 = Chain.create(:name => "5")
274
+ chain.chains.first.chains << chain4
275
+ chain.chains.first.save
276
+ chain = Chain[chain.key]
277
+
278
+ lambda { chain.destroy! }.should raise_error(DataMapper::AssociationProtectedError)
279
+
280
+ Chain.delete_all
281
+ end
282
+
283
+ it "should correctly handle dependent associations (:nullify)" do
284
+ class Chain
285
+ has_and_belongs_to_many :chains, :dependent => :nullify
286
+ end
287
+ Chain.auto_migrate!
288
+
289
+ chain = Chain.create(:name => "1")
290
+ chain.chains << Chain.create(:name => "2")
291
+ chain.chains << Chain.create(:name => "3")
292
+ chain.chains << Chain.create(:name => "4")
293
+ chain.save
294
+ chain4 = Chain.create(:name => "5")
295
+ chain.chains.first.chains << chain4
296
+ chain.chains.first.save
297
+ chain = Chain[chain.key]
298
+
299
+ chain.destroy!
300
+ Chain.first(:name => "2").should_not be_nil
301
+ Chain.first(:name => "3").should_not be_nil
302
+ Chain.first(:name => "4").should_not be_nil
303
+
304
+ Chain.delete_all
305
+ end
306
+
307
+ end
308
+
309
+ describe DataMapper::Associations::HasAndBelongsToManyAssociation, "self-referential relationship" do
310
+
311
+ before(:all) do
312
+ fixtures(:tasks)
313
+ end
314
+
315
+ before(:each) do
316
+ @task_relax = Task.first(:name => "task_relax")
317
+ end
318
+
319
+ it "should allow a self-referential habtm by creating a related_* column for the right foreign key" do
320
+ tasks_assocation = database(:mock).schema[Task].associations.find { |a| a.name == :tasks }
321
+
322
+ tasks_assocation.right_foreign_key.name.should == :related_task_id
323
+ end
324
+
325
+ it "should load the self-referential association" do
326
+ database do
327
+ task_relax = Task.first(:name => "task_relax")
328
+ task_relax.tasks.size.should == 1
329
+ task_relax.tasks.first.should == Task.first(:name => "task_drink_heartily")
330
+ end
331
+ end
332
+
333
+ it "should allow a mirrored relationship between two rows (no infinite recursion)" do
334
+ task_vacation = Task.first(:name => "task_vacation")
335
+
336
+ @task_relax.tasks << task_vacation
337
+ @task_relax.save
338
+
339
+ task_vacation.tasks << @task_relax
340
+ task_vacation.save
341
+
342
+ @task_relax.reload!
343
+ @task_relax.tasks.should include(task_vacation)
344
+
345
+ task_vacation.reload!
346
+ task_vacation.tasks.should include(@task_relax)
347
+ end
348
+
349
+ end
350
+
351
+ describe DataMapper::Associations::HasAndBelongsToManyAssociation, "compatibility with belongs_to" do
352
+
353
+ it "should be able to save a job without interferring with applications" do
354
+ programmer = Job.create(:name => 'Programmer')
355
+ manager = Job.create(:name => 'Manager')
356
+
357
+ bob = Candidate.create(:name => 'Bob')
358
+
359
+ bob.applications << programmer << manager
360
+
361
+ bob.applications.should have(2).entries
362
+ bob.job.should be_nil
363
+
364
+ bob.job = programmer
365
+
366
+ bob.should be_dirty
367
+ bob.applications.should be_dirty
368
+ bob.job.should_not be_dirty # Because no property is changed on the has_many side.
369
+
370
+ bob.save!.should == true
371
+
372
+ impostor = Candidate[bob.id]
373
+
374
+ impostor.applications.should have(2).entries
375
+ impostor.job.should == programmer
376
+ end
377
+ end