datamapper 0.2.5 → 0.3.0

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 (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