sam-dm-core 0.9.6

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 (126) hide show
  1. data/.autotest +26 -0
  2. data/CONTRIBUTING +51 -0
  3. data/FAQ +92 -0
  4. data/History.txt +145 -0
  5. data/MIT-LICENSE +22 -0
  6. data/Manifest.txt +125 -0
  7. data/QUICKLINKS +12 -0
  8. data/README.txt +143 -0
  9. data/Rakefile +30 -0
  10. data/SPECS +63 -0
  11. data/TODO +1 -0
  12. data/lib/dm-core.rb +224 -0
  13. data/lib/dm-core/adapters.rb +4 -0
  14. data/lib/dm-core/adapters/abstract_adapter.rb +202 -0
  15. data/lib/dm-core/adapters/data_objects_adapter.rb +707 -0
  16. data/lib/dm-core/adapters/mysql_adapter.rb +136 -0
  17. data/lib/dm-core/adapters/postgres_adapter.rb +188 -0
  18. data/lib/dm-core/adapters/sqlite3_adapter.rb +105 -0
  19. data/lib/dm-core/associations.rb +199 -0
  20. data/lib/dm-core/associations/many_to_many.rb +147 -0
  21. data/lib/dm-core/associations/many_to_one.rb +107 -0
  22. data/lib/dm-core/associations/one_to_many.rb +309 -0
  23. data/lib/dm-core/associations/one_to_one.rb +61 -0
  24. data/lib/dm-core/associations/relationship.rb +218 -0
  25. data/lib/dm-core/associations/relationship_chain.rb +81 -0
  26. data/lib/dm-core/auto_migrations.rb +113 -0
  27. data/lib/dm-core/collection.rb +638 -0
  28. data/lib/dm-core/dependency_queue.rb +31 -0
  29. data/lib/dm-core/hook.rb +11 -0
  30. data/lib/dm-core/identity_map.rb +45 -0
  31. data/lib/dm-core/is.rb +16 -0
  32. data/lib/dm-core/logger.rb +232 -0
  33. data/lib/dm-core/migrations/destructive_migrations.rb +17 -0
  34. data/lib/dm-core/migrator.rb +29 -0
  35. data/lib/dm-core/model.rb +471 -0
  36. data/lib/dm-core/naming_conventions.rb +84 -0
  37. data/lib/dm-core/property.rb +673 -0
  38. data/lib/dm-core/property_set.rb +162 -0
  39. data/lib/dm-core/query.rb +625 -0
  40. data/lib/dm-core/repository.rb +159 -0
  41. data/lib/dm-core/resource.rb +637 -0
  42. data/lib/dm-core/scope.rb +58 -0
  43. data/lib/dm-core/support.rb +7 -0
  44. data/lib/dm-core/support/array.rb +13 -0
  45. data/lib/dm-core/support/assertions.rb +8 -0
  46. data/lib/dm-core/support/errors.rb +23 -0
  47. data/lib/dm-core/support/kernel.rb +7 -0
  48. data/lib/dm-core/support/symbol.rb +41 -0
  49. data/lib/dm-core/transaction.rb +267 -0
  50. data/lib/dm-core/type.rb +160 -0
  51. data/lib/dm-core/type_map.rb +80 -0
  52. data/lib/dm-core/types.rb +19 -0
  53. data/lib/dm-core/types/boolean.rb +7 -0
  54. data/lib/dm-core/types/discriminator.rb +34 -0
  55. data/lib/dm-core/types/object.rb +24 -0
  56. data/lib/dm-core/types/paranoid_boolean.rb +34 -0
  57. data/lib/dm-core/types/paranoid_datetime.rb +33 -0
  58. data/lib/dm-core/types/serial.rb +9 -0
  59. data/lib/dm-core/types/text.rb +10 -0
  60. data/lib/dm-core/version.rb +3 -0
  61. data/script/all +5 -0
  62. data/script/performance.rb +203 -0
  63. data/script/profile.rb +87 -0
  64. data/spec/integration/association_spec.rb +1371 -0
  65. data/spec/integration/association_through_spec.rb +203 -0
  66. data/spec/integration/associations/many_to_many_spec.rb +449 -0
  67. data/spec/integration/associations/many_to_one_spec.rb +163 -0
  68. data/spec/integration/associations/one_to_many_spec.rb +151 -0
  69. data/spec/integration/auto_migrations_spec.rb +398 -0
  70. data/spec/integration/collection_spec.rb +1069 -0
  71. data/spec/integration/data_objects_adapter_spec.rb +32 -0
  72. data/spec/integration/dependency_queue_spec.rb +58 -0
  73. data/spec/integration/model_spec.rb +127 -0
  74. data/spec/integration/mysql_adapter_spec.rb +85 -0
  75. data/spec/integration/postgres_adapter_spec.rb +731 -0
  76. data/spec/integration/property_spec.rb +233 -0
  77. data/spec/integration/query_spec.rb +506 -0
  78. data/spec/integration/repository_spec.rb +57 -0
  79. data/spec/integration/resource_spec.rb +475 -0
  80. data/spec/integration/sqlite3_adapter_spec.rb +352 -0
  81. data/spec/integration/sti_spec.rb +208 -0
  82. data/spec/integration/strategic_eager_loading_spec.rb +138 -0
  83. data/spec/integration/transaction_spec.rb +75 -0
  84. data/spec/integration/type_spec.rb +271 -0
  85. data/spec/lib/logging_helper.rb +18 -0
  86. data/spec/lib/mock_adapter.rb +27 -0
  87. data/spec/lib/model_loader.rb +91 -0
  88. data/spec/lib/publicize_methods.rb +28 -0
  89. data/spec/models/vehicles.rb +34 -0
  90. data/spec/models/zoo.rb +47 -0
  91. data/spec/spec.opts +3 -0
  92. data/spec/spec_helper.rb +86 -0
  93. data/spec/unit/adapters/abstract_adapter_spec.rb +133 -0
  94. data/spec/unit/adapters/adapter_shared_spec.rb +15 -0
  95. data/spec/unit/adapters/data_objects_adapter_spec.rb +628 -0
  96. data/spec/unit/adapters/postgres_adapter_spec.rb +133 -0
  97. data/spec/unit/associations/many_to_many_spec.rb +17 -0
  98. data/spec/unit/associations/many_to_one_spec.rb +152 -0
  99. data/spec/unit/associations/one_to_many_spec.rb +393 -0
  100. data/spec/unit/associations/one_to_one_spec.rb +7 -0
  101. data/spec/unit/associations/relationship_spec.rb +71 -0
  102. data/spec/unit/associations_spec.rb +242 -0
  103. data/spec/unit/auto_migrations_spec.rb +111 -0
  104. data/spec/unit/collection_spec.rb +182 -0
  105. data/spec/unit/data_mapper_spec.rb +35 -0
  106. data/spec/unit/identity_map_spec.rb +126 -0
  107. data/spec/unit/is_spec.rb +80 -0
  108. data/spec/unit/migrator_spec.rb +33 -0
  109. data/spec/unit/model_spec.rb +339 -0
  110. data/spec/unit/naming_conventions_spec.rb +36 -0
  111. data/spec/unit/property_set_spec.rb +83 -0
  112. data/spec/unit/property_spec.rb +753 -0
  113. data/spec/unit/query_spec.rb +530 -0
  114. data/spec/unit/repository_spec.rb +93 -0
  115. data/spec/unit/resource_spec.rb +626 -0
  116. data/spec/unit/scope_spec.rb +142 -0
  117. data/spec/unit/transaction_spec.rb +493 -0
  118. data/spec/unit/type_map_spec.rb +114 -0
  119. data/spec/unit/type_spec.rb +119 -0
  120. data/tasks/ci.rb +68 -0
  121. data/tasks/dm.rb +63 -0
  122. data/tasks/doc.rb +20 -0
  123. data/tasks/gemspec.rb +23 -0
  124. data/tasks/hoe.rb +46 -0
  125. data/tasks/install.rb +20 -0
  126. metadata +216 -0
@@ -0,0 +1,133 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', "..", 'spec_helper'))
2
+
3
+ if HAS_POSTGRES
4
+ describe DataMapper::Adapters::PostgresAdapter do
5
+ before :all do
6
+ @adapter = repository(:postgres).adapter
7
+ end
8
+
9
+ describe '#upgrade_model_storage' do
10
+ before do
11
+ @repository = mock('repository', :kind_of? => true, :name => :postgres)
12
+ @model = mock('model', :kind_of? => true, :storage_name => 'models')
13
+ @property = mock('property', :kind_of? => true, :model => @model, :serial? => true, :field => 'property')
14
+
15
+ @model.should_receive(:properties).with(:postgres).any_number_of_times.and_return([@property])
16
+
17
+ @command = mock('command')
18
+ @connection = mock('connection', :create_command => @command, :close => true)
19
+ @result = mock('result', :to_i => 0)
20
+
21
+ DataObjects::Connection.stub!(:new).and_return(@connection)
22
+
23
+ @adapter.stub!(:execute).and_return(@result)
24
+ @adapter.stub!(:storage_exists?).and_return(true)
25
+ @adapter.stub!(:query).and_return([ 0 ])
26
+
27
+ @original_method = @adapter.class.superclass.instance_method(:upgrade_model_storage)
28
+ @adapter.class.superclass.send(:define_method, :upgrade_model_storage) {}
29
+ end
30
+
31
+ after do
32
+ method = @original_method
33
+ @adapter.class.superclass.send(:define_method, :upgrade_model_storage) do |*args|
34
+ method.bind(self).call(*args)
35
+ end
36
+ end
37
+
38
+ it 'should check to make sure the sequences exist' do
39
+ statement = %q[SELECT COUNT(*) FROM "pg_class" WHERE "relkind" = 'S' AND "relname" = ?]
40
+ @adapter.should_receive(:query).with(statement, 'models_property_seq').and_return([ 0 ])
41
+ @adapter.upgrade_model_storage(@repository, @model)
42
+ end
43
+
44
+ it 'should add sequences' do
45
+ statement = %q[CREATE SEQUENCE "models_property_seq"]
46
+ @adapter.should_receive(:execute).with(statement)
47
+ @adapter.upgrade_model_storage(@repository, @model)
48
+ end
49
+
50
+ it 'should execute the superclass upgrade_model_storage' do
51
+ rv = mock('inside super')
52
+ @adapter.class.superclass.send(:define_method, :upgrade_model_storage) { rv }
53
+ @adapter.upgrade_model_storage(@repository, @model).should == rv
54
+ end
55
+ end
56
+
57
+ describe '#create_model_storage' do
58
+ before do
59
+ @repository = mock('repository', :kind_of? => true, :name => :postgres)
60
+ @model = mock('model', :kind_of? => true, :storage_name => 'models')
61
+ @property = mock('property', :kind_of? => true, :model => @model, :serial? => true, :field => 'property')
62
+
63
+ @model.should_receive(:properties).with(:postgres).any_number_of_times.and_return([@property])
64
+
65
+ @adapter.stub!(:execute).and_return(@result)
66
+ @adapter.stub!(:storage_exists?).and_return(true)
67
+ @adapter.stub!(:query).and_return([ 0 ])
68
+
69
+ @original_method = @adapter.class.superclass.instance_method(:create_table_statement)
70
+ @adapter.class.superclass.send(:define_method, :create_table_statement) {}
71
+ end
72
+
73
+ after do
74
+ method = @original_method
75
+ @adapter.class.superclass.send(:define_method, :create_table_statement) do |*args|
76
+ method.bind(self).call(*args)
77
+ end
78
+ end
79
+
80
+ it 'should check to make sure the sequences exist' do
81
+ statement = %q[SELECT COUNT(*) FROM "pg_class" WHERE "relkind" = 'S' AND "relname" = ?]
82
+ @adapter.should_receive(:query).with(statement, 'models_property_seq').and_return([ 0 ])
83
+ @adapter.create_model_storage(@repository, @model)
84
+ end
85
+
86
+ it 'should add sequences' do
87
+ statement = %q[CREATE SEQUENCE "models_property_seq"]
88
+ @adapter.should_receive(:execute).with(statement)
89
+ @adapter.create_model_storage(@repository, @model)
90
+ end
91
+
92
+ it 'should execute the superclass upgrade_model_storage' do
93
+ rv = mock('inside super')
94
+ @adapter.class.superclass.send(:define_method, :create_table_statement) { rv }
95
+ @adapter.create_table_statement(@repository, @model).should == rv
96
+ end
97
+ end
98
+
99
+ describe '#destroy_model_storage' do
100
+ before do
101
+ @repository = mock('repository', :kind_of? => true, :name => :postgres)
102
+ @model = mock('model', :kind_of? => true, :storage_name => 'models')
103
+ @property = mock('property', :kind_of? => true, :model => @model, :serial? => true, :field => 'property')
104
+
105
+ @model.should_receive(:properties).with(:postgres).any_number_of_times.and_return([@property])
106
+
107
+ @original_method = @adapter.class.superclass.instance_method(:destroy_model_storage)
108
+ @adapter.class.superclass.send(:define_method, :destroy_model_storage) {}
109
+ end
110
+
111
+ after do
112
+ method = @original_method
113
+ @adapter.class.superclass.send(:define_method, :destroy_model_storage) do |*args|
114
+ method.bind(self).call(*args)
115
+ end
116
+ end
117
+
118
+ it 'should not execute the superclass destroy_model_storage if the storage does not exist' do
119
+ rv = mock('inside super')
120
+ @adapter.class.superclass.send(:define_method, :destroy_model_storage) { rv }
121
+ @adapter.destroy_model_storage(@repository, @model).should_not == rv
122
+ end
123
+
124
+ it 'should execute the superclass destroy_model_storage if the storage exists' do
125
+ rv = mock('inside super')
126
+ @adapter.class.superclass.send(:define_method, :destroy_model_storage) { rv }
127
+ @adapter.stub!(:storage_exists?).and_return(true)
128
+
129
+ @adapter.destroy_model_storage(@repository, @model).should == rv
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,17 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'spec_helper'))
2
+
3
+ describe DataMapper::Associations::ManyToMany do
4
+
5
+ load_models_for_metaphor :vehicles
6
+
7
+ it 'should allow a declaration' do
8
+ lambda do
9
+ class Supplier
10
+ has n, :manufacturers, :through => Resource
11
+ end
12
+ end.should_not raise_error
13
+ end
14
+ end
15
+
16
+ describe DataMapper::Associations::ManyToMany::Proxy do
17
+ end
@@ -0,0 +1,152 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'spec_helper'))
2
+
3
+ describe DataMapper::Associations::ManyToOne do
4
+
5
+ load_models_for_metaphor :vehicles
6
+
7
+ it 'should allow a declaration' do
8
+ lambda do
9
+ class Vehicle
10
+ belongs_to :manufacturer
11
+ end
12
+ end.should_not raise_error
13
+ end
14
+ end
15
+
16
+ describe DataMapper::Associations::ManyToOne::Proxy do
17
+
18
+ load_models_for_metaphor :vehicles
19
+
20
+ before do
21
+ @child = mock('child', :kind_of? => true)
22
+ @parent = mock('parent')
23
+ @relationship = mock('relationship', :kind_of? => true, :get_parent => @parent, :attach_parent => nil)
24
+ @association = DataMapper::Associations::ManyToOne::Proxy.new(@relationship, @child)
25
+
26
+ @association.replace(@parent)
27
+ end
28
+
29
+ it 'should provide #replace' do
30
+ @association.should respond_to(:replace)
31
+ end
32
+
33
+ describe '#replace' do
34
+ before do
35
+ @other = mock('other parent')
36
+ end
37
+
38
+ before do
39
+ @relationship.should_receive(:attach_parent).with(@child, @other)
40
+ end
41
+
42
+ it 'should remove the resource from the collection' do
43
+ @association.should == @parent
44
+ @association.replace(@other)
45
+ @association.should == @other
46
+ end
47
+
48
+ it 'should not automatically save that the resource was removed from the association' do
49
+ @other.should_not_receive(:save)
50
+ @association.replace(@other)
51
+ end
52
+
53
+ it 'should return the association' do
54
+ @association.replace(@other).object_id.should == @association.object_id
55
+ end
56
+ end
57
+
58
+ it 'should provide #save' do
59
+ @association.should respond_to(:replace)
60
+ end
61
+
62
+ describe '#save' do
63
+ describe 'when the parent is nil' do
64
+ before do
65
+ @parent.should_receive(:nil?).with(no_args).and_return(true)
66
+ end
67
+
68
+ it 'should not save the parent' do
69
+ @association.save
70
+ end
71
+
72
+ it 'should return false' do
73
+ @association.save.should == false
74
+ end
75
+ end
76
+
77
+ describe 'when the parent is not a new record' do
78
+ before do
79
+ @parent.should_receive(:new_record?).with(no_args).and_return(false)
80
+ end
81
+
82
+ it 'should not save the parent' do
83
+ @parent.should_not_receive(:save)
84
+ @association.save
85
+ end
86
+
87
+ it 'should return true' do
88
+ @association.save.should == true
89
+ end
90
+ end
91
+
92
+ describe 'when the parent is a new record' do
93
+ before do
94
+ @parent.should_receive(:new_record?).with(no_args).and_return(true)
95
+ end
96
+
97
+ it 'should save the parent' do
98
+ @relationship.should_receive(:with_repository).and_yield(@repository)
99
+ @parent.should_receive(:save).with(no_args)
100
+ @association.save
101
+ end
102
+
103
+ it 'should return the result of the save' do
104
+ child_key = mock("child_key")
105
+ child_key.should_receive(:set).and_return(true)
106
+ parent_key = mock("parent_key")
107
+ parent_key.should_receive(:get).and_return(1)
108
+ @relationship.should_receive(:with_repository).and_yield(@repository)
109
+ @relationship.should_receive(:child_key).and_return(child_key)
110
+ @relationship.should_receive(:parent_key).and_return(parent_key)
111
+ save_results = mock('save results')
112
+ @parent.should_receive(:save).with(no_args).and_return(save_results)
113
+ @association.save.object_id.should == save_results.object_id
114
+ end
115
+ end
116
+ end
117
+
118
+ it 'should provide #reload' do
119
+ @association.should respond_to(:reload)
120
+ end
121
+
122
+ describe '#reload' do
123
+ before(:each) do
124
+ @mock_parent = mock('#reload test parent')
125
+ @association.replace(@mock_parent)
126
+ end
127
+
128
+ it 'should set the @parent ivar to nil' do
129
+ @association.__send__(:parent).should == @mock_parent # Sanity check.
130
+
131
+ # We can't test the value of the instance variable since
132
+ # #instance_variable_get will be run on the @parent (thanks to
133
+ # Proxy#method_missing). Instead, test that Relationship#get_parent is
134
+ # run -- if @parent wasn't set to nil, this expectation should fail.
135
+ @relationship.should_receive(:get_parent).once.and_return(@mock_parent)
136
+ @association.reload
137
+
138
+ # Trigger #get_parent on the relationship.
139
+ @association.__send__(:parent)
140
+ end
141
+
142
+ it 'should not change the foreign key in the child' do
143
+ @relationship.should_not_receive(:attach_parent)
144
+ @association.reload
145
+ end
146
+
147
+ it 'should return self' do
148
+ @association.reload.should be_kind_of(DataMapper::Associations::ManyToOne::Proxy)
149
+ @association.reload.object_id.should == @association.object_id
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,393 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'spec_helper'))
2
+
3
+ describe DataMapper::Associations::OneToMany do
4
+
5
+ load_models_for_metaphor :vehicles
6
+
7
+ before do
8
+ @class = Class.new do
9
+ def self.name
10
+ 'User'
11
+ end
12
+
13
+ include DataMapper::Resource
14
+
15
+ property :user_id, Integer, :key => true
16
+ end
17
+ end
18
+
19
+ it 'should provide #has' do
20
+ @class.should respond_to(:has)
21
+ end
22
+
23
+ describe '#has' do
24
+ it 'should return a Relationship' do
25
+ @class.has(@class.n, :orders).should be_kind_of(DataMapper::Associations::Relationship)
26
+ end
27
+
28
+ describe 'relationship' do
29
+ before do
30
+ @relationship = mock('relationship')
31
+ DataMapper::Associations::Relationship.stub!(:new).and_return(@relationship)
32
+ end
33
+
34
+ it 'should receive the name' do
35
+ DataMapper::Associations::Relationship.should_receive(:new) do |name,_,_,_,_|
36
+ name.should == :orders
37
+ end
38
+ @class.has(@class.n, :orders)
39
+ end
40
+
41
+ it 'should receive the repository name' do
42
+ DataMapper::Associations::Relationship.should_receive(:new) do |_,repository_name,_,_,_|
43
+ repository_name.should == :mock
44
+ end
45
+ repository(:mock) do
46
+ @class.has(@class.n, :orders)
47
+ end
48
+ end
49
+
50
+ it 'should receive the child model name when passed in as class_name' do
51
+ DataMapper::Associations::Relationship.should_receive(:new) do |_,_,child_model_name,_,_|
52
+ child_model_name.should == 'Company::Order'
53
+ end
54
+ @class.has(@class.n, :orders, :class_name => 'Company::Order')
55
+ end
56
+
57
+ it 'should receive the child model name when class_name not passed in' do
58
+ DataMapper::Associations::Relationship.should_receive(:new) do |_,_,child_model_name,_,_|
59
+ child_model_name.should == 'Order'
60
+ end
61
+ @class.has(@class.n, :orders)
62
+ end
63
+
64
+ it 'should receive the parent model name' do
65
+ DataMapper::Associations::Relationship.should_receive(:new) do |_,_,_,parent_model_name,_|
66
+ parent_model_name.should == @class
67
+ end
68
+ @class.has(@class.n, :orders)
69
+ end
70
+
71
+ it 'should receive the parent model name' do
72
+ options = { :min => 0, :max => 100 }
73
+ DataMapper::Associations::Relationship.should_receive(:new) do |_,_,_,parent_model_name,_|
74
+ options.object_id.should == options.object_id
75
+ end
76
+ @class.has(@class.n, :orders, options)
77
+ end
78
+ end
79
+
80
+ it 'should add an accessor for the proxy' do
81
+ @class.new.should_not respond_to(:orders)
82
+ @class.has(@class.n, :orders)
83
+ @class.new.should respond_to(:orders)
84
+ end
85
+
86
+ describe 'proxy accessor' do
87
+ before :all do
88
+ class User
89
+ include DataMapper::Resource
90
+ end
91
+
92
+ class Order
93
+ include DataMapper::Resource
94
+ end
95
+ end
96
+
97
+ it 'should return a OneToMany::Proxy' do
98
+ @class.has(@class.n, :orders)
99
+ @class.new.orders.should be_kind_of(DataMapper::Associations::OneToMany::Proxy)
100
+ end
101
+ end
102
+ end
103
+
104
+ it 'should work with classes inside modules'
105
+ end
106
+
107
+ describe DataMapper::Associations::OneToMany::Proxy do
108
+ before do
109
+ @parent = mock('parent', :new_record? => true, :kind_of? => true)
110
+ @resource = mock('resource', :null_object => true)
111
+ @collection = []
112
+ @parent_key = mock('parent key', :get => [])
113
+ @repository = mock('repository', :save => nil, :kind_of? => true)
114
+ @relationship = mock('relationship', :get_children => @collection, :query => {}, :kind_of? => true, :child_key => [], :parent_key => @parent_key)
115
+ @association = DataMapper::Associations::OneToMany::Proxy.new(@relationship, @parent)
116
+ end
117
+
118
+ describe 'a method that relates the resource', :shared => true do
119
+ it 'should add the resource to the collection' do
120
+ @association.should_not include(@resource)
121
+ do_add.should == return_value
122
+ @association.should include(@resource)
123
+ end
124
+
125
+ it 'should not automatically save that the resource was added to the association' do
126
+ @relationship.should_not_receive(:attach_parent)
127
+ do_add.should == return_value
128
+ end
129
+
130
+ it 'should persist the addition after saving the association' do
131
+ @relationship.should_receive(:with_repository).with(@resource).and_yield(@repository)
132
+ do_add.should == return_value
133
+ @relationship.should_receive(:attach_parent).with(@resource, @parent)
134
+ @association.save
135
+ end
136
+ end
137
+
138
+ describe 'a method that orphans the resource', :shared => true do
139
+ before do
140
+ @association << @resource
141
+ end
142
+
143
+ it 'should remove the resource from the collection' do
144
+ @association.should include(@resource)
145
+ do_remove.should == return_value
146
+ @association.should_not include(@resource)
147
+ end
148
+
149
+ it 'should not automatically save that the resource was removed from the association' do
150
+ @relationship.should_not_receive(:attach_parent)
151
+ do_remove.should == return_value
152
+ end
153
+
154
+ it 'should persist the removal after saving the association' do
155
+ @relationship.should_receive(:with_repository).with(@resource).and_yield(@repository)
156
+ do_remove.should == return_value
157
+ @relationship.should_receive(:attach_parent).with(@resource, nil)
158
+ @association.save
159
+ end
160
+ end
161
+
162
+ it 'should provide #<<' do
163
+ @association.should respond_to(:<<)
164
+ end
165
+
166
+ describe '#<<' do
167
+ def do_add
168
+ @association << @resource
169
+ end
170
+
171
+ def return_value
172
+ @association
173
+ end
174
+
175
+ it_should_behave_like 'a method that relates the resource'
176
+ end
177
+
178
+ it 'should provide #push' do
179
+ @association.should respond_to(:push)
180
+ end
181
+
182
+ describe '#push' do
183
+ def do_add
184
+ @association.push(@resource)
185
+ end
186
+
187
+ def return_value
188
+ @association
189
+ end
190
+
191
+ it_should_behave_like 'a method that relates the resource'
192
+ end
193
+
194
+ it 'should provide #unshift' do
195
+ @association.should respond_to(:unshift)
196
+ end
197
+
198
+ describe '#unshift' do
199
+ def do_add
200
+ @association.unshift(@resource)
201
+ end
202
+
203
+ def return_value
204
+ @association
205
+ end
206
+
207
+ it_should_behave_like 'a method that relates the resource'
208
+ end
209
+
210
+ it 'should provide #replace' do
211
+ @association.should respond_to(:replace)
212
+ end
213
+
214
+ describe '#replace' do
215
+ before do
216
+ @children = [
217
+ mock('child 1', :save => true),
218
+ mock('child 2', :save => true),
219
+ ]
220
+ @collection << @resource
221
+ @collection.stub!(:loaded?).and_return(true)
222
+ @relationship.stub!(:attach_parent)
223
+ end
224
+
225
+ def do_replace
226
+ @association.replace(@children)
227
+ end
228
+
229
+ def return_value
230
+ @association
231
+ end
232
+
233
+ it 'should remove the resource from the collection' do
234
+ @association.should include(@resource)
235
+ do_replace.should == return_value
236
+ @association.should_not include(@resource)
237
+ end
238
+
239
+ it 'should not automatically save that the resource was removed from the association' do
240
+ @relationship.should_not_receive(:attach_parent)
241
+ do_replace.should == return_value
242
+ end
243
+
244
+ it 'should persist the removal after saving the association' do
245
+ do_replace.should == return_value
246
+ @relationship.should_receive(:with_repository).exactly(3).times.and_yield(@repository)
247
+ @relationship.should_receive(:attach_parent).with(@resource, nil)
248
+ @association.save
249
+ end
250
+
251
+ it 'should not automatically save that the children were added to the association' do
252
+ @relationship.should_not_receive(:attach_parent)
253
+ do_replace.should == return_value
254
+ end
255
+
256
+ it 'should persist the addition after saving the association' do
257
+ do_replace.should == return_value
258
+ @relationship.should_receive(:with_repository).exactly(3).times.and_yield(@repository)
259
+ @relationship.should_receive(:attach_parent).with(@children[0], @parent)
260
+ @relationship.should_receive(:attach_parent).with(@children[1], @parent)
261
+ @association.save
262
+ end
263
+ end
264
+
265
+ it 'should provide #pop' do
266
+ @association.should respond_to(:pop)
267
+ end
268
+
269
+ describe '#pop' do
270
+ def do_remove
271
+ @association.pop
272
+ end
273
+
274
+ def return_value
275
+ @resource
276
+ end
277
+
278
+ it_should_behave_like 'a method that orphans the resource'
279
+ end
280
+
281
+ it 'should provide #shift' do
282
+ @association.should respond_to(:shift)
283
+ end
284
+
285
+ describe '#shift' do
286
+ def do_remove
287
+ @association.shift
288
+ end
289
+
290
+ def return_value
291
+ @resource
292
+ end
293
+
294
+ it_should_behave_like 'a method that orphans the resource'
295
+ end
296
+
297
+ it 'should provide #delete' do
298
+ @association.should respond_to(:delete)
299
+ end
300
+
301
+ describe '#delete' do
302
+ def do_remove
303
+ @association.delete(@resource)
304
+ end
305
+
306
+ def return_value
307
+ @resource
308
+ end
309
+
310
+ it_should_behave_like 'a method that orphans the resource'
311
+ end
312
+
313
+ it 'should provide #delete_at' do
314
+ @association.should respond_to(:delete_at)
315
+ end
316
+
317
+ describe '#delete_at' do
318
+ def do_remove
319
+ @association.delete_at(0)
320
+ end
321
+
322
+ def return_value
323
+ @resource
324
+ end
325
+
326
+ it_should_behave_like 'a method that orphans the resource'
327
+ end
328
+
329
+ it 'should provide #clear' do
330
+ @association.should respond_to(:clear)
331
+ end
332
+
333
+ describe '#clear' do
334
+ def do_remove
335
+ @association.clear
336
+ end
337
+
338
+ def return_value
339
+ @association
340
+ end
341
+
342
+ it_should_behave_like 'a method that orphans the resource'
343
+
344
+ it 'should empty the collection' do
345
+ @association << mock('other resource', :new_record? => false)
346
+ @association.should have(2).entries
347
+ do_remove
348
+ @association.should be_empty
349
+ end
350
+ end
351
+
352
+ it 'should provide #reload' do
353
+ @association.should respond_to(:reload)
354
+ end
355
+
356
+ describe '#reload' do
357
+ before do
358
+ @children = [ mock('child 1', :save => true), mock('child 2', :save => true) ]
359
+ @relationship.stub!(:get_children).and_return(@children)
360
+ end
361
+
362
+ it 'should set the @children ivar to nil' do
363
+ @association.__send__(:children).should == @children # Sanity check.
364
+
365
+ # We can't test the value of the @children instance variable since
366
+ # #instance_variable_get will be run on @children (thanks to
367
+ # Proxy#method_missing). Instead, test that Relationship#get_children is
368
+ # run -- if @children wasn't set to nil, this expectation should fail.
369
+ @relationship.should_receive(:get_children).once.and_return(@children)
370
+ @association.reload
371
+
372
+ # Trigger #get_children on the relationship.
373
+ @association.__send__(:children).should == @children
374
+ end
375
+
376
+ it 'should return self' do
377
+ @association.reload.should be_kind_of(DataMapper::Associations::OneToMany::Proxy)
378
+ @association.reload.object_id.should == @association.object_id
379
+ end
380
+ end
381
+
382
+ describe 'when deleting the parent' do
383
+ it 'should delete all the children without calling destroy if relationship :dependent is :delete_all'
384
+
385
+ it 'should destroy all the children if relationship :dependent is :destroy'
386
+
387
+ it 'should set the parent key for each child to nil if relationship :dependent is :nullify'
388
+
389
+ it 'should restrict the parent from being deleted if a child remains if relationship :dependent is restrict'
390
+
391
+ it 'should be restrict by default if relationship :dependent is not specified'
392
+ end
393
+ end