dm-constraints 0.9.9 → 0.9.10

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.
data/History.txt CHANGED
@@ -1,3 +1,9 @@
1
+ === 0.9.10 / 2009-01-19
2
+
3
+ * 1 major enhancement:
4
+
5
+ * Add :protect, :destroy, :set_nil and :skip constraints
6
+
1
7
  === 0.9.9 / 2009-01-04
2
8
 
3
9
  * No changes this version
data/Manifest.txt CHANGED
@@ -6,6 +6,7 @@ Rakefile
6
6
  TODO
7
7
  lib/dm-constraints.rb
8
8
  lib/dm-constraints/data_objects_adapter.rb
9
+ lib/dm-constraints/delete_constraint.rb
9
10
  lib/dm-constraints/mysql_adapter.rb
10
11
  lib/dm-constraints/postgres_adapter.rb
11
12
  lib/dm-constraints/version.rb
@@ -13,7 +13,18 @@ module DataMapper
13
13
  foreign_table = parent.storage_name(repository_name)
14
14
  foreign_keys = parent.key.map { |key| property_to_column_name(parent.repository(repository_name), key, false) }
15
15
 
16
- create_constraints_statement(table_name, constraint_name, keys, foreign_table, foreign_keys)
16
+ one_to_many_relationship = parent.relationships.values.select { |rel| rel.child_model == model }.first
17
+ delete_constraint_type = case one_to_many_relationship.delete_constraint
18
+ when :protect, nil
19
+ "NO ACTION"
20
+ when :destroy, :destroy!
21
+ "CASCADE"
22
+ when :set_nil
23
+ "SET NULL"
24
+ when :skip
25
+ nil
26
+ end
27
+ create_constraints_statement(table_name, constraint_name, keys, foreign_table, foreign_keys, delete_constraint_type) if delete_constraint_type
17
28
  end.compact
18
29
  end
19
30
 
@@ -29,14 +40,14 @@ module DataMapper
29
40
 
30
41
  private
31
42
 
32
- def create_constraints_statement(table_name, constraint_name, keys, foreign_table, foreign_keys)
43
+ def create_constraints_statement(table_name, constraint_name, keys, foreign_table, foreign_keys, delete_constraint_type)
33
44
  <<-EOS.compress_lines
34
45
  ALTER TABLE #{quote_table_name(table_name)}
35
46
  ADD CONSTRAINT #{quote_constraint_name(constraint_name)}
36
47
  FOREIGN KEY (#{keys * ', '})
37
48
  REFERENCES #{quote_table_name(foreign_table)} (#{foreign_keys * ', '})
38
- ON DELETE NO ACTION
39
- ON UPDATE NO ACTION
49
+ ON DELETE #{delete_constraint_type}
50
+ ON UPDATE #{delete_constraint_type}
40
51
  EOS
41
52
  end
42
53
 
@@ -0,0 +1,59 @@
1
+ module DataMapper
2
+ module Constraints
3
+ module DeleteConstraint
4
+
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ module ClassMethods
10
+ DELETE_CONSTRAINT_OPTIONS = [:protect, :destroy, :destroy!, :set_nil, :skip]
11
+ def check_delete_constraint_type(cardinality, name, options = {})
12
+ constraint_type = options[:constraint]
13
+ return if constraint_type.nil?
14
+ delete_constraint_options = DELETE_CONSTRAINT_OPTIONS.map { |o| ":#{o}" }
15
+ if !DELETE_CONSTRAINT_OPTIONS.include?(constraint_type)
16
+ raise ArgumentError, ":constraint option must be one of #{delete_constraint_options * ', '}"
17
+ end
18
+ end
19
+
20
+ # TODO: that should be moved to a 'util-like' module
21
+ def with_changed_method_visibility(method, from_visibility, to_visibility, &block)
22
+ send(to_visibility, method)
23
+ yield
24
+ send(from_visibility, method)
25
+ end
26
+
27
+ end
28
+
29
+ def add_delete_constraint_option(name, repository_name, child_model, parent_model, options = {})
30
+ @delete_constraint = options[:constraint]
31
+ end
32
+
33
+ def check_delete_constraints
34
+ model.relationships.each do |rel_name, rel|
35
+ children = self.send(rel_name)
36
+ case rel.delete_constraint
37
+ when nil, :protect
38
+ # only prevent deletion if the resource is a parent in a relationship and has children
39
+ throw(:halt, false) if children && children.respond_to?(:empty?) && !children.empty?
40
+ when :destroy
41
+ if children && children.respond_to?(:each)
42
+ children.each { |child| child.destroy }
43
+ end
44
+ when :set_nil
45
+ if children && children.respond_to?(:each)
46
+ children.each do |child|
47
+ child.class.many_to_one_relationships.each do |mto_rel|
48
+ child.send("#{mto_rel.name}=", nil) if child.send(mto_rel.name).eql?(self)
49
+ end
50
+ end
51
+ end
52
+ end # case
53
+ end # relationships
54
+ end # check_delete_constraints
55
+
56
+
57
+ end # DeleteConstraint
58
+ end # Constraints
59
+ end # DataMapper
@@ -1,5 +1,5 @@
1
1
  module DataMapper
2
2
  module Constraints
3
- VERSION = '0.9.9'
3
+ VERSION = '0.9.10'
4
4
  end
5
5
  end
@@ -3,15 +3,55 @@ require 'rubygems'
3
3
  require 'pathname'
4
4
 
5
5
  # Add all external dependencies for the plugin here
6
- gem 'dm-core', '~>0.9.9'
6
+ gem 'dm-core', '~>0.9.10'
7
7
  require 'dm-core'
8
8
 
9
9
  # Require plugin-files
10
10
  require Pathname(__FILE__).dirname.expand_path / 'dm-constraints' / 'data_objects_adapter'
11
11
  require Pathname(__FILE__).dirname.expand_path / 'dm-constraints' / 'postgres_adapter'
12
12
  require Pathname(__FILE__).dirname.expand_path / 'dm-constraints' / 'mysql_adapter'
13
+ require Pathname(__FILE__).dirname.expand_path / 'dm-constraints' / 'delete_constraint'
13
14
 
14
15
  module DataMapper
16
+ module Associations
17
+ class Relationship
18
+ include Extlib::Hook
19
+ include DataMapper::Constraints::DeleteConstraint
20
+
21
+ attr_reader :delete_constraint
22
+ OPTIONS << :constraint
23
+
24
+ # initialize is a private method in Relationship
25
+ # and private methods can not be "advised" (hooked into)
26
+ # in extlib.
27
+ with_changed_method_visibility(:initialize, :private, :public) do
28
+ before :initialize, :add_delete_constraint_option
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ module DataMapper
35
+ module Constraints
36
+
37
+ include DeleteConstraint
38
+
39
+ module ClassMethods
40
+ include DeleteConstraint::ClassMethods
41
+ end
42
+
43
+ def self.included(model)
44
+ model.extend(ClassMethods)
45
+ model.class_eval do
46
+ before_class_method :has, :check_delete_constraint_type
47
+ if method_defined?(:destroy)
48
+ before :destroy, :check_delete_constraints
49
+ end
50
+ end
51
+ end
52
+
53
+ end
54
+
15
55
  class AutoMigrator
16
56
  include Extlib::Hook
17
57
  include DataMapper::Constraints::DataObjectsAdapter::Migration
@@ -5,11 +5,14 @@ ADAPTERS.each do |adapter|
5
5
 
6
6
  describe 'DataMapper::Constraints' do
7
7
 
8
+ # load_models_for_metaphor :stable, :farmer, :cow
9
+
8
10
  before do
9
11
  DataMapper::Repository.adapters[:default] = DataMapper::Repository.adapters[adapter]
10
12
 
11
- class Stable
13
+ class ::Stable
12
14
  include DataMapper::Resource
15
+ include DataMapper::Constraints
13
16
 
14
17
  property :id, Serial
15
18
  property :location, String
@@ -18,8 +21,9 @@ ADAPTERS.each do |adapter|
18
21
  has n, :cows
19
22
  end
20
23
 
21
- class Farmer
24
+ class ::Farmer
22
25
  include DataMapper::Resource
26
+ include DataMapper::Constraints
23
27
 
24
28
  property :first_name, String, :key => true
25
29
  property :last_name, String, :key => true
@@ -27,7 +31,7 @@ ADAPTERS.each do |adapter|
27
31
  has n, :cows
28
32
  end
29
33
 
30
- class Cow
34
+ class ::Cow
31
35
  include DataMapper::Resource
32
36
  include DataMapper::Constraints
33
37
 
@@ -61,5 +65,169 @@ ADAPTERS.each do |adapter|
61
65
  lambda { @c1 = Cow.create(:name => "Bea", :stable_id => s.id + 1) }.should raise_error
62
66
  end
63
67
 
64
- end
65
- end
68
+ # :constraint associations
69
+ # value | on deletion of parent...
70
+ # ---------------------------------
71
+ # :protect | raises exception if there are child records
72
+ # :destroy | deletes children
73
+ # :destroy! | deletes children directly without instantiating the resource, bypassing any hooks
74
+ # :set_nil | sets parent id to nil in child associations
75
+ # :skip | does not do anything with children (they'll become orphan records)
76
+
77
+ describe "constraint options" do
78
+ describe "when no constraint options are given" do
79
+
80
+ it "should destroy the parent if there are no children in the association" do
81
+ @f1 = Farmer.create(:first_name => "John", :last_name => "Doe")
82
+ @f2 = Farmer.create(:first_name => "Some", :last_name => "Body")
83
+ @c1 = Cow.create(:name => "Bea", :farmer => @f2)
84
+ @f1.destroy.should == true
85
+ end
86
+
87
+ it "should not destroy the parent if there are children in the association" do
88
+ @f = Farmer.create(:first_name => "John", :last_name => "Doe")
89
+ @c1 = Cow.create(:name => "Bea", :farmer => @f)
90
+ @f.destroy.should == false
91
+ end
92
+
93
+ end
94
+
95
+ describe "when :constraint => :protect is given" do
96
+ before do
97
+ class ::Farmer
98
+ has n, :cows, :constraint => :protect
99
+ end
100
+ class ::Cow
101
+ belongs_to :farmer
102
+ end
103
+ end
104
+
105
+ it "should destroy the parent if there are no children in the association" do
106
+ @f1 = Farmer.create(:first_name => "John", :last_name => "Doe")
107
+ @f2 = Farmer.create(:first_name => "Some", :last_name => "Body")
108
+ @c1 = Cow.create(:name => "Bea", :farmer => @f2)
109
+ @f1.destroy.should == true
110
+ end
111
+
112
+ it "should not destroy the parent if there are children in the association" do
113
+ @f = Farmer.create(:first_name => "John", :last_name => "Doe")
114
+ @c1 = Cow.create(:name => "Bea", :farmer => @f)
115
+ @f.destroy.should == false
116
+ end
117
+
118
+ it "the child should be destroyable" do
119
+ @f = Farmer.create(:first_name => "John", :last_name => "Doe")
120
+ @c = Cow.create(:name => "Bea", :farmer => @f)
121
+ @c.destroy.should == true
122
+ end
123
+
124
+ end
125
+
126
+ describe "when :constraint => :destroy is given" do
127
+ before do
128
+ class ::Farmer
129
+ has n, :cows, :constraint => :destroy
130
+ end
131
+ class ::Cow
132
+ belongs_to :farmer
133
+ end
134
+ DataMapper.auto_migrate!
135
+ end
136
+
137
+ it "should destroy the parent and the children, too" do
138
+ #NOTE: the repository wrapper is needed in order for
139
+ # the identity map to work (otherwise @c1 in the below two calls
140
+ # would refer to different instances)
141
+ repository do
142
+ @f = Farmer.create(:first_name => "John", :last_name => "Doe")
143
+ @c1 = Cow.create(:name => "Bea", :farmer => @f)
144
+ @c2 = Cow.create(:name => "Riksa", :farmer => @f)
145
+ @f.destroy.should == true
146
+ @f.should be_new_record
147
+ @c1.should be_new_record
148
+ @c2.should be_new_record
149
+ end
150
+ end
151
+
152
+ it "the child should be destroyable" do
153
+ @f = Farmer.create(:first_name => "John", :last_name => "Doe")
154
+ @c = Cow.create(:name => "Bea", :farmer => @f)
155
+ @c.destroy.should == true
156
+ end
157
+
158
+ end
159
+
160
+ describe "when :constraint => :set_nil is given" do
161
+ before do
162
+ class ::Farmer
163
+ has n, :cows, :constraint => :set_nil
164
+ end
165
+ class ::Cow
166
+ belongs_to :farmer
167
+ end
168
+ DataMapper.auto_migrate!
169
+ end
170
+
171
+ it "destroying the parent should set children foreign keys to nil" do
172
+ @f = Farmer.create(:first_name => "John", :last_name => "Doe")
173
+ @c1 = Cow.create(:name => "Bea", :farmer => @f)
174
+ @c2 = Cow.create(:name => "Riksa", :farmer => @f)
175
+ cows = @f.cows
176
+ @f.destroy.should == true
177
+ cows.all? { |cow| cow.farmer.should be_nil }
178
+ end
179
+
180
+ it "the child should be destroyable" do
181
+ @f = Farmer.create(:first_name => "John", :last_name => "Doe")
182
+ @c = Cow.create(:name => "Bea", :farmer => @f)
183
+ @c.destroy.should == true
184
+ end
185
+
186
+ end # describe
187
+
188
+ describe "when :constraint => :skip is given" do
189
+ before do
190
+ class ::Farmer
191
+ has n, :cows, :constraint => :skip
192
+ end
193
+ class ::Cow
194
+ belongs_to :farmer
195
+ end
196
+ DataMapper.auto_migrate!
197
+ end
198
+
199
+ it "destroying the parent should be allowed, children should become orphan records" do
200
+ @f = Farmer.create(:first_name => "John", :last_name => "Doe")
201
+ @c1 = Cow.create(:name => "Bea", :farmer => @f)
202
+ @c2 = Cow.create(:name => "Riksa", :farmer => @f)
203
+ @f.destroy.should == true
204
+ @c1.farmer.should be_new_record
205
+ @c2.farmer.should be_new_record
206
+ end
207
+
208
+ it "the child should be destroyable" do
209
+ @f = Farmer.create(:first_name => "John", :last_name => "Doe")
210
+ @c = Cow.create(:name => "Bea", :farmer => @f)
211
+ @c.destroy.should == true
212
+ end
213
+
214
+ end # describe
215
+
216
+ describe "when an invalid option is given" do
217
+ before do
218
+ end
219
+
220
+ it "should raise an error" do
221
+ lambda do
222
+ class ::Farmer
223
+ has n, :cows, :constraint => :chocolate
224
+ end
225
+ end.should raise_error(ArgumentError)
226
+ end
227
+
228
+ end
229
+
230
+ end # describe 'constraint options'
231
+
232
+ end # DataMapper::Constraints
233
+ end # ADAPTERS.each
data/spec/spec_helper.rb CHANGED
@@ -4,7 +4,7 @@ require 'rubygems'
4
4
  gem 'rspec', '~>1.1.11'
5
5
  require 'spec'
6
6
 
7
- gem 'dm-core', '~>0.9.9'
7
+ gem 'dm-core', '~>0.9.10'
8
8
  require 'dm-core'
9
9
 
10
10
  ADAPTERS = []
data/tasks/spec.rb CHANGED
@@ -8,7 +8,7 @@ begin
8
8
  desc 'Run specifications'
9
9
  Spec::Rake::SpecTask.new(:spec) do |t|
10
10
  t.spec_opts << '--options' << 'spec/spec.opts' if File.exists?('spec/spec.opts')
11
- t.spec_files = Pathname.glob((ROOT + 'spec/**/*_spec.rb').to_s)
11
+ t.spec_files = Pathname.glob((ROOT + 'spec/**/*_spec.rb').to_s).map { |f| f.to_s }
12
12
 
13
13
  begin
14
14
  gem 'rcov', '~>0.8'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dm-constraints
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.9
4
+ version: 0.9.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dirkjan Bussink
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-01-04 00:00:00 -08:00
12
+ date: 2009-01-19 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -20,7 +20,7 @@ dependencies:
20
20
  requirements:
21
21
  - - ~>
22
22
  - !ruby/object:Gem::Version
23
- version: 0.9.9
23
+ version: 0.9.10
24
24
  version:
25
25
  description: DataMapper plugin constraining relationships
26
26
  email:
@@ -43,6 +43,7 @@ files:
43
43
  - TODO
44
44
  - lib/dm-constraints.rb
45
45
  - lib/dm-constraints/data_objects_adapter.rb
46
+ - lib/dm-constraints/delete_constraint.rb
46
47
  - lib/dm-constraints/mysql_adapter.rb
47
48
  - lib/dm-constraints/postgres_adapter.rb
48
49
  - lib/dm-constraints/version.rb