dm-constraints 0.9.10 → 0.9.11
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +10 -0
- data/README.txt +48 -0
- data/Rakefile +1 -1
- data/lib/dm-constraints/data_objects_adapter.rb +96 -2
- data/lib/dm-constraints/delete_constraint.rb +147 -20
- data/lib/dm-constraints/mysql_adapter.rb +22 -2
- data/lib/dm-constraints/postgres_adapter.rb +12 -2
- data/lib/dm-constraints/version.rb +1 -1
- data/lib/dm-constraints.rb +24 -1
- data/spec/integration/constraints_spec.rb +400 -67
- data/spec/spec_helper.rb +2 -2
- data/tasks/spec.rb +1 -1
- metadata +4 -4
data/History.txt
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
=== 0.9.11 / 2009-03-29
|
2
|
+
|
3
|
+
* 5 major enhancements:
|
4
|
+
|
5
|
+
* Added :destroy! constraints
|
6
|
+
* Added support for 1:1 constraints
|
7
|
+
* Added support for M:M constraints
|
8
|
+
* Add'l rspecs
|
9
|
+
* Updated readme.txt
|
10
|
+
|
1
11
|
=== 0.9.10 / 2009-01-19
|
2
12
|
|
3
13
|
* 1 major enhancement:
|
data/README.txt
CHANGED
@@ -2,3 +2,51 @@
|
|
2
2
|
|
3
3
|
Plugin that adds foreign key constraints to associations.
|
4
4
|
Currently supports only PostgreSQL and MySQL
|
5
|
+
|
6
|
+
All constraints are added to the underlying database, but constraining is implemented in
|
7
|
+
pure ruby.
|
8
|
+
|
9
|
+
|
10
|
+
=== Constraints
|
11
|
+
|
12
|
+
- :protect returns false on destroy if there are child records
|
13
|
+
- :destroy deletes children if present
|
14
|
+
- :destroy! deletes children directly without instantiating the resource, bypassing any hooks
|
15
|
+
Does not support 1:1 Relationships as #destroy! is not supported on Resource in dm-master
|
16
|
+
- :set_nil sets parent id to nil in child associations
|
17
|
+
Not valid for M:M relationships as duplicate records could be created (see explanation in specs)
|
18
|
+
- :skip Does nothing with children, results in orphaned records
|
19
|
+
|
20
|
+
By default a relationship will PROTECT its children.
|
21
|
+
|
22
|
+
|
23
|
+
=== Cardinality Notes
|
24
|
+
* 1:1
|
25
|
+
* Applicable constraints: [:set_nil, :skip, :protect, :destroy]
|
26
|
+
|
27
|
+
* 1:M
|
28
|
+
* Applicable constraints: [:set_nil, :skip, :protect, :destroy, :destroy!]
|
29
|
+
|
30
|
+
* M:M
|
31
|
+
* Applicable constraints: [:skip, :protect, :destroy, :destroy!]
|
32
|
+
|
33
|
+
|
34
|
+
=== Examples
|
35
|
+
|
36
|
+
# 1:M Example
|
37
|
+
class Farmer
|
38
|
+
has n, :pigs #equivalent to: has n, :pigs, :constraint => :protect
|
39
|
+
end
|
40
|
+
|
41
|
+
# M:M Example
|
42
|
+
class Articles
|
43
|
+
has n, :tags, :through => Resource, :constraint => :destroy
|
44
|
+
end
|
45
|
+
class Tags
|
46
|
+
has n, :articles, :through => Resource, :constraint => :destroy
|
47
|
+
end
|
48
|
+
|
49
|
+
# 1:1 Example
|
50
|
+
class Farmer
|
51
|
+
has 1, :beloved_sheep, :constraint => :protect
|
52
|
+
end
|
data/Rakefile
CHANGED
@@ -12,7 +12,7 @@ AUTHOR = 'Dirkjan Bussink'
|
|
12
12
|
EMAIL = 'd.bussink [a] gmail [d] com'
|
13
13
|
GEM_NAME = 'dm-constraints'
|
14
14
|
GEM_VERSION = DataMapper::Constraints::VERSION
|
15
|
-
GEM_DEPENDENCIES = [['dm-core',
|
15
|
+
GEM_DEPENDENCIES = [['dm-core', GEM_VERSION]]
|
16
16
|
GEM_CLEAN = %w[ log pkg coverage ]
|
17
17
|
GEM_EXTRAS = { :has_rdoc => true, :extra_rdoc_files => %w[ README.txt LICENSE TODO History.txt ] }
|
18
18
|
|
@@ -2,8 +2,26 @@ module DataMapper
|
|
2
2
|
module Constraints
|
3
3
|
module DataObjectsAdapter
|
4
4
|
module SQL
|
5
|
+
|
6
|
+
##
|
7
|
+
# generates all foreign key create constraint statements for valid relationships
|
8
|
+
# given repository and a model
|
9
|
+
#
|
10
|
+
# This wraps calls to create_constraints_statement
|
11
|
+
#
|
12
|
+
# @see #create_constraints_statement
|
13
|
+
#
|
14
|
+
# @param repository_name [Symbol] Name of the repository to constrain
|
15
|
+
#
|
16
|
+
# @param model [DataMapper::Model] Model to constrain
|
17
|
+
#
|
18
|
+
# @return [Array[String]] List of statements to create constraints
|
19
|
+
#
|
20
|
+
#
|
21
|
+
# @api public
|
5
22
|
def create_constraints_statements(repository_name, model)
|
6
23
|
model.many_to_one_relationships.map do |relationship|
|
24
|
+
|
7
25
|
table_name = model.storage_name(repository_name)
|
8
26
|
constraint_name = constraint_name(table_name, relationship.name)
|
9
27
|
next if constraint_exists?(table_name, constraint_name)
|
@@ -13,8 +31,16 @@ module DataMapper
|
|
13
31
|
foreign_table = parent.storage_name(repository_name)
|
14
32
|
foreign_keys = parent.key.map { |key| property_to_column_name(parent.repository(repository_name), key, false) }
|
15
33
|
|
16
|
-
|
17
|
-
|
34
|
+
#Anonymous relationshps for :through => Resource
|
35
|
+
one_to_many_relationship = parent.relationships.values.select { |rel|
|
36
|
+
rel.options[:near_relationship_name] == Extlib::Inflection.tableize(model.name).to_sym
|
37
|
+
}.first
|
38
|
+
|
39
|
+
one_to_many_relationship ||= parent.relationships.values.select { |rel|
|
40
|
+
rel.child_model == model
|
41
|
+
}.first
|
42
|
+
|
43
|
+
delete_constraint_type = case one_to_many_relationship.nil? ? :protect : one_to_many_relationship.delete_constraint
|
18
44
|
when :protect, nil
|
19
45
|
"NO ACTION"
|
20
46
|
when :destroy, :destroy!
|
@@ -24,10 +50,27 @@ module DataMapper
|
|
24
50
|
when :skip
|
25
51
|
nil
|
26
52
|
end
|
53
|
+
|
27
54
|
create_constraints_statement(table_name, constraint_name, keys, foreign_table, foreign_keys, delete_constraint_type) if delete_constraint_type
|
28
55
|
end.compact
|
29
56
|
end
|
30
57
|
|
58
|
+
##
|
59
|
+
# generates all foreign key destroy constraint statements for valid relationships
|
60
|
+
# given repository and a model
|
61
|
+
#
|
62
|
+
# This wraps calls to destroy_constraints_statement
|
63
|
+
#
|
64
|
+
# @see #destroy_constraints_statement
|
65
|
+
#
|
66
|
+
# @param repository_name [Symbol] Name of the repository to constrain
|
67
|
+
#
|
68
|
+
# @param model [DataMapper::Model] Model to constrain
|
69
|
+
#
|
70
|
+
# @return [Array[String]] List of statements to destroy constraints
|
71
|
+
#
|
72
|
+
#
|
73
|
+
# @api public
|
31
74
|
def destroy_constraints_statements(repository_name, model)
|
32
75
|
model.many_to_one_relationships.map do |relationship|
|
33
76
|
table_name = model.storage_name(repository_name)
|
@@ -35,11 +78,30 @@ module DataMapper
|
|
35
78
|
next unless constraint_exists?(table_name, constraint_name)
|
36
79
|
|
37
80
|
destroy_constraints_statement(table_name, constraint_name)
|
81
|
+
|
38
82
|
end.compact
|
39
83
|
end
|
40
84
|
|
41
85
|
private
|
42
86
|
|
87
|
+
##
|
88
|
+
# Generates the SQL statement to create a constraint
|
89
|
+
#
|
90
|
+
# @param table_name [String] name of table to constrain
|
91
|
+
#
|
92
|
+
# @param constraint_name [String] name of foreign key constraint
|
93
|
+
#
|
94
|
+
# @param keys [Array[String]] keys that refer to another table
|
95
|
+
#
|
96
|
+
# @param foreign_table [String] table fk refers to
|
97
|
+
#
|
98
|
+
# @param foreign_keys [Array[String]] keys on foreign table that constraint refers to
|
99
|
+
#
|
100
|
+
# @param delete_constraint_type [String] the constraint to add to the table
|
101
|
+
#
|
102
|
+
# @return [String] SQL DDL Statement to create a constraint
|
103
|
+
#
|
104
|
+
# @api private
|
43
105
|
def create_constraints_statement(table_name, constraint_name, keys, foreign_table, foreign_keys, delete_constraint_type)
|
44
106
|
<<-EOS.compress_lines
|
45
107
|
ALTER TABLE #{quote_table_name(table_name)}
|
@@ -51,6 +113,16 @@ module DataMapper
|
|
51
113
|
EOS
|
52
114
|
end
|
53
115
|
|
116
|
+
##
|
117
|
+
# Generates the SQL statement to destroy a constraint
|
118
|
+
#
|
119
|
+
# @param table_name [String] name of table to constrain
|
120
|
+
#
|
121
|
+
# @param constraint_name [String] name of foreign key constraint
|
122
|
+
#
|
123
|
+
# @return [String] SQL DDL Statement to destroy a constraint
|
124
|
+
#
|
125
|
+
# @api private
|
54
126
|
def destroy_constraints_statement(table_name, constraint_name)
|
55
127
|
<<-EOS.compress_lines
|
56
128
|
ALTER TABLE #{quote_table_name(table_name)}
|
@@ -58,10 +130,30 @@ module DataMapper
|
|
58
130
|
EOS
|
59
131
|
end
|
60
132
|
|
133
|
+
##
|
134
|
+
# generates a unique constraint name given a table and a relationships
|
135
|
+
#
|
136
|
+
# @param table_name [String] name of table to constrain
|
137
|
+
#
|
138
|
+
# @param relationships_name [String] name of the relationship to constrain
|
139
|
+
#
|
140
|
+
# @return [String] name of the constraint
|
141
|
+
#
|
142
|
+
# @api private
|
61
143
|
def constraint_name(table_name, relationship_name)
|
62
144
|
"#{table_name}_#{relationship_name}_fk"
|
63
145
|
end
|
64
146
|
|
147
|
+
##
|
148
|
+
# SQL quotes a foreign key constraint name
|
149
|
+
#
|
150
|
+
# @see #quote_table_name
|
151
|
+
#
|
152
|
+
# @param foreign_key [String] SQL quotes a foreign key name
|
153
|
+
#
|
154
|
+
# @return [String] quoted constraint name
|
155
|
+
#
|
156
|
+
# @api private
|
65
157
|
def quote_constraint_name(foreign_key)
|
66
158
|
quote_table_name(foreign_key)
|
67
159
|
end
|
@@ -78,6 +170,7 @@ module DataMapper
|
|
78
170
|
def auto_migrate_constraints_down(repository_name, *descendants)
|
79
171
|
descendants = DataMapper::Resource.descendants.to_a if descendants.empty?
|
80
172
|
descendants.each do |model|
|
173
|
+
repository_name ||= model.repository(repository_name).name
|
81
174
|
if model.storage_exists?(repository_name)
|
82
175
|
adapter = model.repository(repository_name).adapter
|
83
176
|
next unless adapter.respond_to?(:destroy_constraints_statements)
|
@@ -90,6 +183,7 @@ module DataMapper
|
|
90
183
|
def auto_migrate_constraints_up(retval, repository_name, *descendants)
|
91
184
|
descendants = DataMapper::Resource.descendants.to_a if descendants.empty?
|
92
185
|
descendants.each do |model|
|
186
|
+
repository_name ||= model.repository(repository_name).name
|
93
187
|
adapter = model.repository(repository_name).adapter
|
94
188
|
next unless adapter.respond_to?(:create_constraints_statements)
|
95
189
|
statements = adapter.create_constraints_statements(repository_name, model)
|
@@ -8,16 +8,57 @@ module DataMapper
|
|
8
8
|
|
9
9
|
module ClassMethods
|
10
10
|
DELETE_CONSTRAINT_OPTIONS = [:protect, :destroy, :destroy!, :set_nil, :skip]
|
11
|
+
|
12
|
+
##
|
13
|
+
# Checks that the constraint type is appropriate to the relationship
|
14
|
+
#
|
15
|
+
# @param cardinality [Fixnum] cardinality of relationship
|
16
|
+
#
|
17
|
+
# @param name [Symbol] name of relationship to evaluate constraint of
|
18
|
+
#
|
19
|
+
# @param options [Hash] options hash
|
20
|
+
#
|
21
|
+
# @raises ArgumentError
|
22
|
+
#
|
23
|
+
# @return [nil]
|
24
|
+
#
|
25
|
+
# @api semi-public
|
11
26
|
def check_delete_constraint_type(cardinality, name, options = {})
|
27
|
+
#Make sure options contains :constraint key, whether nil or not
|
28
|
+
options[:constraint] ||= nil
|
12
29
|
constraint_type = options[:constraint]
|
13
30
|
return if constraint_type.nil?
|
31
|
+
|
14
32
|
delete_constraint_options = DELETE_CONSTRAINT_OPTIONS.map { |o| ":#{o}" }
|
15
33
|
if !DELETE_CONSTRAINT_OPTIONS.include?(constraint_type)
|
16
34
|
raise ArgumentError, ":constraint option must be one of #{delete_constraint_options * ', '}"
|
17
35
|
end
|
36
|
+
|
37
|
+
if constraint_type == :set_nil && self.relationships[name].is_a?(DataMapper::Associations::RelationshipChain)
|
38
|
+
raise ArgumentError, "Constraint type :set_nil is not valid for M:M relationships"
|
39
|
+
end
|
40
|
+
|
41
|
+
if cardinality == 1 && constraint_type == :destroy!
|
42
|
+
raise ArgumentError, "Constraint type :destroy! is not valid for 1:1 relationships"
|
43
|
+
end
|
18
44
|
end
|
19
45
|
|
20
|
-
|
46
|
+
##
|
47
|
+
# Temporarily changes the visibility of a method so a block can be evaluated against it
|
48
|
+
#
|
49
|
+
# @param method [Symobl] method to change visibility of
|
50
|
+
#
|
51
|
+
# @param from_visibility [Symbol] original visibility
|
52
|
+
#
|
53
|
+
# @param to_visibility [Symbol] temporary visibility
|
54
|
+
#
|
55
|
+
# @param block [Proc] proc to run
|
56
|
+
#
|
57
|
+
# @notes TODO: this should be moved to a 'util-like' module
|
58
|
+
#
|
59
|
+
# @return [nil]
|
60
|
+
#
|
61
|
+
# @api semi-public
|
21
62
|
def with_changed_method_visibility(method, from_visibility, to_visibility, &block)
|
22
63
|
send(to_visibility, method)
|
23
64
|
yield
|
@@ -26,33 +67,119 @@ module DataMapper
|
|
26
67
|
|
27
68
|
end
|
28
69
|
|
29
|
-
|
30
|
-
|
70
|
+
##
|
71
|
+
# Addes the delete constraint options to a relationship
|
72
|
+
#
|
73
|
+
# @param params [*ARGS] Arguments passed to Relationship#initialize or RelationshipChain#initialize
|
74
|
+
#
|
75
|
+
# @notes This takes *params because it runs before the initializer for Relationships and RelationshipChains
|
76
|
+
# which have different method signatures
|
77
|
+
#
|
78
|
+
# @return [nil]
|
79
|
+
#
|
80
|
+
# @api semi-public
|
81
|
+
def add_delete_constraint_option(*params)
|
82
|
+
opts = params.last
|
83
|
+
|
84
|
+
if opts.is_a?(Hash)
|
85
|
+
#if it is a chain, set the constraint on the 1:M near relationship(anonymous)
|
86
|
+
if self.is_a?(DataMapper::Associations::RelationshipChain)
|
87
|
+
opts = params.last
|
88
|
+
near_rel = opts[:parent_model].relationships[opts[:near_relationship_name]]
|
89
|
+
near_rel.options[:constraint] = opts[:constraint]
|
90
|
+
near_rel.instance_variable_set "@delete_constraint", opts[:constraint]
|
91
|
+
end
|
92
|
+
|
93
|
+
@delete_constraint = params.last[:constraint]
|
94
|
+
end
|
31
95
|
end
|
32
96
|
|
97
|
+
##
|
98
|
+
# Checks delete constraints prior to destroying a dm resource or collection
|
99
|
+
#
|
100
|
+
# @throws :halt
|
101
|
+
#
|
102
|
+
# @notes
|
103
|
+
# - It only considers a relationship's constraints if this is the parent model (ie a child shouldn't delete a parent)
|
104
|
+
# - RelationshipChains are skipped, as they are evaluated by their underlying 1:M relationships
|
105
|
+
#
|
106
|
+
# @returns [nil]
|
107
|
+
#
|
108
|
+
# @api semi-public
|
33
109
|
def check_delete_constraints
|
34
110
|
model.relationships.each do |rel_name, rel|
|
111
|
+
#Only look at relationships where this model is the parent
|
112
|
+
next if rel.parent_model != model
|
113
|
+
|
114
|
+
#Don't delete across M:M relationships, instead use their anonymous 1:M Relationships
|
115
|
+
next if rel.is_a?(DataMapper::Associations::RelationshipChain)
|
116
|
+
|
35
117
|
children = self.send(rel_name)
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
118
|
+
if children.kind_of?(DataMapper::Collection)
|
119
|
+
check_collection_delete_constraints(rel,children)
|
120
|
+
elsif children
|
121
|
+
check_resource_delete_constraints(rel,children)
|
122
|
+
end
|
53
123
|
end # relationships
|
54
124
|
end # check_delete_constraints
|
55
125
|
|
126
|
+
##
|
127
|
+
# Performs the meat of the check_delete_constraints method for a collection of resources
|
128
|
+
#
|
129
|
+
# @param rel [DataMapper::Associations::Relationship] relationship being evaluated
|
130
|
+
#
|
131
|
+
# @param children [~DataMapper::Collection] child records to constrain
|
132
|
+
#
|
133
|
+
# @see #check_delete_constraints
|
134
|
+
#
|
135
|
+
# @api semi-public
|
136
|
+
def check_collection_delete_constraints(rel, children)
|
137
|
+
case rel.delete_constraint
|
138
|
+
when nil, :protect
|
139
|
+
unless children.empty?
|
140
|
+
DataMapper.logger.error("Could not delete #{self.class} a child #{children.first.class} exists")
|
141
|
+
throw(:halt,false)
|
142
|
+
end
|
143
|
+
when :destroy
|
144
|
+
children.each{|child| child.destroy}
|
145
|
+
when :destroy!
|
146
|
+
children.destroy!
|
147
|
+
when :set_nil
|
148
|
+
children.each do |child|
|
149
|
+
child.class.many_to_one_relationships.each do |mto_rel|
|
150
|
+
child.send("#{mto_rel.name}=", nil) if child.send(mto_rel.name).eql?(self)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
##
|
157
|
+
# Performs the meat of check_delete_constraints method for a single resource
|
158
|
+
#
|
159
|
+
# @param rel [DataMapper::Associations::Relationship] the relationship to evaluate
|
160
|
+
#
|
161
|
+
# @param child [~DataMapper::Model] the model to constrain
|
162
|
+
#
|
163
|
+
# @see #check_delete_constraints
|
164
|
+
#
|
165
|
+
# @api semi-public
|
166
|
+
def check_resource_delete_constraints(rel, child)
|
167
|
+
case rel.delete_constraint
|
168
|
+
when nil, :protect
|
169
|
+
unless child.nil?
|
170
|
+
DataMapper.logger.error("Could not delete #{self.class} a child #{child.class} exists")
|
171
|
+
throw(:halt,false)
|
172
|
+
end
|
173
|
+
when :destroy
|
174
|
+
child.destroy
|
175
|
+
when :destroy!
|
176
|
+
#not supported in dm-master, an exception should have been raised on class load
|
177
|
+
when :set_nil
|
178
|
+
child.class.many_to_one_relationships.each do |mto_rel|
|
179
|
+
child.send("#{mto_rel.name}=", nil) if child.send(mto_rel.name).eql?(self)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
56
183
|
|
57
184
|
end # DeleteConstraint
|
58
185
|
end # Constraints
|
@@ -6,6 +6,16 @@ module DataMapper
|
|
6
6
|
|
7
7
|
private
|
8
8
|
|
9
|
+
##
|
10
|
+
# MySQL specific query to determine to drop a foreign key
|
11
|
+
#
|
12
|
+
# @param table_name [Symbol] name of table to check constraint on
|
13
|
+
#
|
14
|
+
# @param constraint_name [~String] name of constraint to check for
|
15
|
+
#
|
16
|
+
# @return [String] SQL DDL to destroy a constraint
|
17
|
+
#
|
18
|
+
# @api private
|
9
19
|
def destroy_constraints_statement(table_name, constraint_name)
|
10
20
|
<<-EOS.compress_lines
|
11
21
|
ALTER TABLE #{quote_table_name(table_name)}
|
@@ -13,7 +23,17 @@ module DataMapper
|
|
13
23
|
EOS
|
14
24
|
end
|
15
25
|
|
16
|
-
|
26
|
+
##
|
27
|
+
# MySQL specific query to determine if a constraint exists
|
28
|
+
#
|
29
|
+
# @param table_name [Symbol] name of table to check constraint on
|
30
|
+
#
|
31
|
+
# @param constraint_name [~String] name of constraint to check for
|
32
|
+
#
|
33
|
+
# @return [Boolean]
|
34
|
+
#
|
35
|
+
# @api private
|
36
|
+
def constraint_exists?(table_name, constraint_name)
|
17
37
|
statement = <<-EOS.compress_lines
|
18
38
|
SELECT COUNT(*)
|
19
39
|
FROM `information_schema`.`table_constraints`
|
@@ -22,7 +42,7 @@ module DataMapper
|
|
22
42
|
AND `table_name` = ?
|
23
43
|
AND `constraint_name` = ?
|
24
44
|
EOS
|
25
|
-
query(statement, db_name,
|
45
|
+
query(statement, db_name, table_name, constraint_name).first > 0
|
26
46
|
end
|
27
47
|
end
|
28
48
|
end
|
@@ -6,7 +6,17 @@ module DataMapper
|
|
6
6
|
|
7
7
|
private
|
8
8
|
|
9
|
-
|
9
|
+
##
|
10
|
+
# Postgres specific query to determine if a constraint exists
|
11
|
+
#
|
12
|
+
# @param table_name [Symbol] name of table to check constraint on
|
13
|
+
#
|
14
|
+
# @param constraint_name [~String] name of constraint to check for
|
15
|
+
#
|
16
|
+
# @return [Boolean]
|
17
|
+
#
|
18
|
+
# @api private
|
19
|
+
def constraint_exists?(table_name, constraint_name)
|
10
20
|
statement = <<-EOS.compress_lines
|
11
21
|
SELECT COUNT(*)
|
12
22
|
FROM "information_schema"."table_constraints"
|
@@ -14,7 +24,7 @@ module DataMapper
|
|
14
24
|
AND "table_name" = ?
|
15
25
|
AND "constraint_name" = ?
|
16
26
|
EOS
|
17
|
-
query(statement,
|
27
|
+
query(statement, table_name, constraint_name).first > 0
|
18
28
|
end
|
19
29
|
end
|
20
30
|
end
|
data/lib/dm-constraints.rb
CHANGED
@@ -3,7 +3,7 @@ require 'rubygems'
|
|
3
3
|
require 'pathname'
|
4
4
|
|
5
5
|
# Add all external dependencies for the plugin here
|
6
|
-
gem 'dm-core', '
|
6
|
+
gem 'dm-core', '0.9.11'
|
7
7
|
require 'dm-core'
|
8
8
|
|
9
9
|
# Require plugin-files
|
@@ -12,6 +12,25 @@ require Pathname(__FILE__).dirname.expand_path / 'dm-constraints' / 'postgres_ad
|
|
12
12
|
require Pathname(__FILE__).dirname.expand_path / 'dm-constraints' / 'mysql_adapter'
|
13
13
|
require Pathname(__FILE__).dirname.expand_path / 'dm-constraints' / 'delete_constraint'
|
14
14
|
|
15
|
+
module DataMapper
|
16
|
+
module Associations
|
17
|
+
class RelationshipChain
|
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
|
+
|
15
34
|
module DataMapper
|
16
35
|
module Associations
|
17
36
|
class Relationship
|
@@ -40,6 +59,10 @@ module DataMapper
|
|
40
59
|
include DeleteConstraint::ClassMethods
|
41
60
|
end
|
42
61
|
|
62
|
+
##
|
63
|
+
# Add before hooks to #has to check for proper constraint definitions
|
64
|
+
# Add before hooks to #destroy to properly constrain children
|
65
|
+
#
|
43
66
|
def self.included(model)
|
44
67
|
model.extend(ClassMethods)
|
45
68
|
model.class_eval do
|
@@ -2,11 +2,7 @@ require 'pathname'
|
|
2
2
|
require Pathname(__FILE__).dirname.expand_path.parent + 'spec_helper'
|
3
3
|
|
4
4
|
ADAPTERS.each do |adapter|
|
5
|
-
|
6
5
|
describe 'DataMapper::Constraints' do
|
7
|
-
|
8
|
-
# load_models_for_metaphor :stable, :farmer, :cow
|
9
|
-
|
10
6
|
before do
|
11
7
|
DataMapper::Repository.adapters[:default] = DataMapper::Repository.adapters[adapter]
|
12
8
|
|
@@ -43,10 +39,43 @@ ADAPTERS.each do |adapter|
|
|
43
39
|
belongs_to :farmer
|
44
40
|
end
|
45
41
|
|
42
|
+
#Used to test a belongs_to association with no has() association
|
43
|
+
#on the other end
|
44
|
+
class ::Pig
|
45
|
+
include DataMapper::Resource
|
46
|
+
include DataMapper::Constraints
|
47
|
+
|
48
|
+
property :id, Serial
|
49
|
+
property :name, String
|
50
|
+
|
51
|
+
belongs_to :farmer
|
52
|
+
end
|
53
|
+
|
54
|
+
#Used to test M:M :through => Resource relationships
|
55
|
+
class ::Chicken
|
56
|
+
include DataMapper::Resource
|
57
|
+
include DataMapper::Constraints
|
58
|
+
|
59
|
+
property :id, Serial
|
60
|
+
property :name, String
|
61
|
+
|
62
|
+
has n, :tags, :through => Resource
|
63
|
+
end
|
64
|
+
|
65
|
+
class ::Tag
|
66
|
+
include DataMapper::Resource
|
67
|
+
include DataMapper::Constraints
|
68
|
+
|
69
|
+
property :id, Serial
|
70
|
+
property :phrase, String
|
71
|
+
|
72
|
+
has n, :chickens, :through => Resource
|
73
|
+
end
|
74
|
+
|
46
75
|
DataMapper.auto_migrate!
|
47
|
-
end
|
76
|
+
end # before
|
48
77
|
|
49
|
-
it "is included when DataMapper::
|
78
|
+
it "is included when DataMapper::Constraints is loaded" do
|
50
79
|
Cow.new.should be_kind_of(DataMapper::Constraints)
|
51
80
|
end
|
52
81
|
|
@@ -65,16 +94,24 @@ ADAPTERS.each do |adapter|
|
|
65
94
|
lambda { @c1 = Cow.create(:name => "Bea", :stable_id => s.id + 1) }.should raise_error
|
66
95
|
end
|
67
96
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
97
|
+
describe "belongs_to without matching has association" do
|
98
|
+
before do
|
99
|
+
@f1 = Farmer.create(:first_name => "John", :last_name => "Doe")
|
100
|
+
@f2 = Farmer.create(:first_name => "Some", :last_name => "Body")
|
101
|
+
@p = Pig.create(:name => "Bea", :farmer => @f2)
|
102
|
+
end
|
103
|
+
it "should destroy the parent if there are no children in the association" do
|
104
|
+
@f1.destroy.should == true
|
105
|
+
end
|
106
|
+
|
107
|
+
it "the child should be destroyable" do
|
108
|
+
@p.destroy.should == true
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
76
112
|
|
77
113
|
describe "constraint options" do
|
114
|
+
|
78
115
|
describe "when no constraint options are given" do
|
79
116
|
|
80
117
|
it "should destroy the parent if there are no children in the association" do
|
@@ -96,128 +133,424 @@ ADAPTERS.each do |adapter|
|
|
96
133
|
before do
|
97
134
|
class ::Farmer
|
98
135
|
has n, :cows, :constraint => :protect
|
136
|
+
has 1, :pig, :constraint => :protect
|
137
|
+
end
|
138
|
+
class ::Pig
|
139
|
+
belongs_to :farmer
|
99
140
|
end
|
100
141
|
class ::Cow
|
101
142
|
belongs_to :farmer
|
102
143
|
end
|
144
|
+
class ::Chicken
|
145
|
+
has n, :tags, :through => Resource, :constraint => :protect
|
146
|
+
end
|
147
|
+
class ::Tag
|
148
|
+
has n, :chickens, :through => Resource, :constraint => :protect
|
149
|
+
end
|
103
150
|
end
|
104
151
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
152
|
+
describe "one-to-one associations" do
|
153
|
+
before do
|
154
|
+
@f1 = Farmer.create(:first_name => "Mary", :last_name => "Smith")
|
155
|
+
@p1 = Pig.create(:name => "Morton",:farmer => @f1)
|
156
|
+
end
|
157
|
+
|
158
|
+
it "should not destroy the parent if there are children in the association" do
|
159
|
+
@f1.destroy.should == false
|
160
|
+
end
|
161
|
+
|
162
|
+
it "the child should be destroyable" do
|
163
|
+
@p1.destroy.should == true
|
164
|
+
end
|
110
165
|
end
|
111
166
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
167
|
+
describe "one-to-many associations" do
|
168
|
+
before do
|
169
|
+
@f1 = Farmer.create(:first_name => "John", :last_name => "Doe")
|
170
|
+
@f2 = Farmer.create(:first_name => "Some", :last_name => "Body")
|
171
|
+
@c1 = Cow.create(:name => "Bea", :farmer => @f2)
|
172
|
+
end
|
173
|
+
|
174
|
+
it "should destroy the parent if there are no children in the association" do
|
175
|
+
@f1.destroy.should == true
|
176
|
+
end
|
177
|
+
|
178
|
+
it "should not destroy the parent if there are children in the association" do
|
179
|
+
@f2.destroy.should == false
|
180
|
+
end
|
181
|
+
|
182
|
+
it "the child should be destroyable" do
|
183
|
+
@c1.destroy.should == true
|
184
|
+
end
|
116
185
|
end
|
117
186
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
187
|
+
describe "many-to-many associations" do
|
188
|
+
before do
|
189
|
+
@t1 = Tag.create(:phrase => "silly chicken")
|
190
|
+
@t2 = Tag.create(:phrase => "serious chicken")
|
191
|
+
@chk1 = Chicken.create(:name =>"Frank the Chicken", :tags => [@t2])
|
192
|
+
end
|
193
|
+
|
194
|
+
it "should destroy the parent if there are no children in the association" do
|
195
|
+
@t1.destroy.should == true
|
196
|
+
end
|
197
|
+
|
198
|
+
it "should not destroy the parent if there are children in the association" do
|
199
|
+
@t2.destroy.should == false
|
200
|
+
end
|
201
|
+
|
202
|
+
it "the child should be destroyable" do
|
203
|
+
@chk1.tags.clear
|
204
|
+
@chk1.save.should == true
|
205
|
+
@chk1.tags.should be_empty
|
206
|
+
end
|
122
207
|
end
|
123
208
|
|
124
|
-
end
|
209
|
+
end # when constraint protect is given
|
210
|
+
|
211
|
+
describe "when :constraint => :destroy! is given" do
|
212
|
+
before do
|
213
|
+
class ::Farmer
|
214
|
+
has n, :cows, :constraint => :destroy!
|
215
|
+
end
|
216
|
+
class ::Cow
|
217
|
+
belongs_to :farmer
|
218
|
+
end
|
219
|
+
class ::Chicken
|
220
|
+
has n, :tags, :through => Resource, :constraint => :destroy!
|
221
|
+
end
|
222
|
+
class ::Tag
|
223
|
+
has n, :chickens, :through => Resource, :constraint => :destroy!
|
224
|
+
end
|
225
|
+
|
226
|
+
DataMapper.auto_migrate!
|
227
|
+
end
|
228
|
+
|
229
|
+
describe "one-to-many associations" do
|
230
|
+
before(:each) do
|
231
|
+
@f = Farmer.create(:first_name => "John", :last_name => "Doe")
|
232
|
+
@c1 = Cow.create(:name => "Bea", :farmer => @f)
|
233
|
+
@c2 = Cow.create(:name => "Riksa", :farmer => @f)
|
234
|
+
end
|
235
|
+
|
236
|
+
it "should let the parent to be destroyed" do
|
237
|
+
@f.destroy.should == true
|
238
|
+
@f.should be_new_record
|
239
|
+
end
|
240
|
+
|
241
|
+
it "should destroy the children" do
|
242
|
+
@f.destroy
|
243
|
+
@f.cows.all? { |c| c.should be_new_record }
|
244
|
+
end
|
245
|
+
|
246
|
+
it "the child should be destroyable" do
|
247
|
+
@c1.destroy.should == true
|
248
|
+
end
|
249
|
+
|
250
|
+
end
|
251
|
+
|
252
|
+
describe "many-to-many associations" do
|
253
|
+
before do
|
254
|
+
@t1 = Tag.create(:phrase => "floozy")
|
255
|
+
@t2 = Tag.create(:phrase => "dirty")
|
256
|
+
@chk1 = Chicken.create(:name => "Nancy Chicken", :tags => [@t1, @t2])
|
257
|
+
end
|
258
|
+
|
259
|
+
it "should destroy! the parent and the children, too" do
|
260
|
+
@chk1.destroy.should == true
|
261
|
+
@chk1.should be_new_record
|
262
|
+
|
263
|
+
# @t1 & @t2 should still exist, the chicken_tags should have been deleted
|
264
|
+
ChickenTag.all.should be_empty
|
265
|
+
@t1.should_not be_new_record
|
266
|
+
@t2.should_not be_new_record
|
267
|
+
end
|
268
|
+
|
269
|
+
it "the child should be destroyable" do
|
270
|
+
@chk1.destroy.should == true
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
end # when :constraint => :destroy! is given
|
125
275
|
|
126
276
|
describe "when :constraint => :destroy is given" do
|
127
277
|
before do
|
128
278
|
class ::Farmer
|
129
279
|
has n, :cows, :constraint => :destroy
|
280
|
+
has 1, :pig, :constraint => :destroy
|
130
281
|
end
|
131
282
|
class ::Cow
|
132
283
|
belongs_to :farmer
|
133
284
|
end
|
285
|
+
class ::Chicken
|
286
|
+
has n, :tags, :through => Resource, :constraint => :destroy
|
287
|
+
end
|
288
|
+
class ::Tag
|
289
|
+
has n, :chickens, :through => Resource, :constraint => :destroy
|
290
|
+
end
|
291
|
+
|
134
292
|
DataMapper.auto_migrate!
|
135
293
|
end
|
136
294
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
295
|
+
describe "one-to-one associations" do
|
296
|
+
before do
|
297
|
+
@f = Farmer.create(:first_name => "Ted", :last_name => "Cornhusker")
|
298
|
+
@p = Pig.create(:name => "BaconBits", :farmer => @f)
|
299
|
+
end
|
300
|
+
|
301
|
+
it "should let the parent to be destroyed" do
|
302
|
+
@f.destroy.should == true
|
303
|
+
@f.should be_new_record
|
304
|
+
end
|
305
|
+
|
306
|
+
it "should destroy the children" do
|
307
|
+
pig = @f.pig
|
308
|
+
@f.destroy
|
309
|
+
pig.should be_new_record
|
310
|
+
end
|
311
|
+
|
312
|
+
it "the child should be destroyable" do
|
313
|
+
@p.destroy.should == true
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
describe "one-to-many associations" do
|
318
|
+
before(:each) do
|
142
319
|
@f = Farmer.create(:first_name => "John", :last_name => "Doe")
|
143
320
|
@c1 = Cow.create(:name => "Bea", :farmer => @f)
|
144
321
|
@c2 = Cow.create(:name => "Riksa", :farmer => @f)
|
322
|
+
end
|
323
|
+
|
324
|
+
it "should let the parent to be destroyed" do
|
145
325
|
@f.destroy.should == true
|
146
326
|
@f.should be_new_record
|
147
|
-
@c1.should be_new_record
|
148
|
-
@c2.should be_new_record
|
149
327
|
end
|
328
|
+
|
329
|
+
it "should destroy the children" do
|
330
|
+
@f.destroy
|
331
|
+
@f.cows.all? { |c| c.should be_new_record }
|
332
|
+
@f.should be_new_record
|
333
|
+
end
|
334
|
+
|
335
|
+
it "the child should be destroyable" do
|
336
|
+
@c1.destroy.should == true
|
337
|
+
end
|
338
|
+
|
150
339
|
end
|
151
340
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
341
|
+
describe "many-to-many associations" do
|
342
|
+
before do
|
343
|
+
@t1 = Tag.create :phrase => "floozy"
|
344
|
+
@t2 = Tag.create :phrase => "dirty"
|
345
|
+
@chk1 = Chicken.create :name => "Nancy Chicken", :tags => [@t1,@t2]
|
346
|
+
end
|
347
|
+
|
348
|
+
it "should destroy the parent and the children, too" do
|
349
|
+
@chk1.destroy.should == true
|
350
|
+
@chk1.should be_new_record
|
351
|
+
|
352
|
+
#@t1 & @t2 should still exist, the chicken_tags should have been deleted
|
353
|
+
ChickenTag.all.should be_empty
|
354
|
+
@t1.should_not be_new_record
|
355
|
+
@t2.should_not be_new_record
|
356
|
+
end
|
357
|
+
|
358
|
+
it "the child should be destroyable" do
|
359
|
+
@chk1.destroy.should == true
|
360
|
+
end
|
156
361
|
end
|
157
362
|
|
158
|
-
end
|
363
|
+
end # when :constraint => :destroy is given
|
159
364
|
|
160
365
|
describe "when :constraint => :set_nil is given" do
|
161
366
|
before do
|
162
367
|
class ::Farmer
|
163
368
|
has n, :cows, :constraint => :set_nil
|
369
|
+
has 1, :pig, :constraint => :set_nil
|
164
370
|
end
|
165
371
|
class ::Cow
|
166
372
|
belongs_to :farmer
|
167
373
|
end
|
374
|
+
# NOTE: M:M Relationships are not supported,
|
375
|
+
# see "when checking constraint types" tests at bottom
|
168
376
|
DataMapper.auto_migrate!
|
169
377
|
end
|
170
378
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
379
|
+
describe "one-to-one associations" do
|
380
|
+
before do
|
381
|
+
@f = Farmer.create(:first_name => "Mr", :last_name => "Hands")
|
382
|
+
@p = Pig.create(:name => "Greasy", :farmer => @f)
|
383
|
+
end
|
384
|
+
|
385
|
+
it "should let the parent to be destroyed" do
|
386
|
+
@f.destroy.should == true
|
387
|
+
end
|
388
|
+
|
389
|
+
it "should set the child's foreign_key id to nil" do
|
390
|
+
pig = @f.pig
|
391
|
+
@f.destroy.should == true
|
392
|
+
pig.farmer.should be_nil
|
393
|
+
end
|
394
|
+
|
395
|
+
it "the child should be destroyable" do
|
396
|
+
@p.destroy.should == true
|
397
|
+
end
|
398
|
+
|
178
399
|
end
|
179
400
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
401
|
+
describe "one-to-many associations" do
|
402
|
+
before(:each) do
|
403
|
+
@f = Farmer.create(:first_name => "John", :last_name => "Doe")
|
404
|
+
@c1 = Cow.create(:name => "Bea", :farmer => @f)
|
405
|
+
@c2 = Cow.create(:name => "Riksa", :farmer => @f)
|
406
|
+
end
|
407
|
+
|
408
|
+
it "should let the parent to be destroyed" do
|
409
|
+
@f.destroy.should == true
|
410
|
+
@f.should be_new_record
|
411
|
+
end
|
412
|
+
|
413
|
+
it "should set the foreign_key ids of children to nil" do
|
414
|
+
@f.destroy
|
415
|
+
@f.cows.all? { |c| c.farmer.should be_nil }
|
416
|
+
end
|
417
|
+
|
418
|
+
it "the children should be destroyable" do
|
419
|
+
@c1.destroy.should == true
|
420
|
+
@c2.destroy.should == true
|
421
|
+
end
|
422
|
+
|
184
423
|
end
|
185
424
|
|
186
|
-
end # describe
|
425
|
+
end # describe "when :constraint => :set_nil is given" do
|
187
426
|
|
188
427
|
describe "when :constraint => :skip is given" do
|
189
428
|
before do
|
190
429
|
class ::Farmer
|
191
430
|
has n, :cows, :constraint => :skip
|
431
|
+
has 1, :pig, :constraint => :skip
|
192
432
|
end
|
193
433
|
class ::Cow
|
194
434
|
belongs_to :farmer
|
195
435
|
end
|
436
|
+
class ::Chicken
|
437
|
+
has n, :tags, :through => Resource, :constraint => :skip
|
438
|
+
end
|
439
|
+
class ::Tag
|
440
|
+
has n, :chickens, :through => Resource, :constraint => :skip
|
441
|
+
end
|
196
442
|
DataMapper.auto_migrate!
|
197
443
|
end
|
198
444
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
445
|
+
describe "one-to-one associations" do
|
446
|
+
before do
|
447
|
+
@f = Farmer.create(:first_name => "William", :last_name => "Shepard")
|
448
|
+
@p = Pig.create(:name => "Jiggles The Pig", :farmer => @f)
|
449
|
+
end
|
450
|
+
|
451
|
+
it "should let the parent be destroyed" do
|
452
|
+
@f.destroy.should == true
|
453
|
+
@f.should be_new_record
|
454
|
+
# @p.farmer.should be_new_record
|
455
|
+
end
|
456
|
+
|
457
|
+
it "should let the children become orphan records" do
|
458
|
+
@f.destroy
|
459
|
+
@p.farmer.should be_new_record
|
460
|
+
end
|
461
|
+
|
462
|
+
it "the child should be destroyable" do
|
463
|
+
@p.destroy.should == true
|
464
|
+
end
|
465
|
+
|
206
466
|
end
|
207
467
|
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
468
|
+
describe "one-to-many associations" do
|
469
|
+
before do
|
470
|
+
@f = Farmer.create(:first_name => "John", :last_name => "Doe")
|
471
|
+
@c1 = Cow.create(:name => "Bea", :farmer => @f)
|
472
|
+
@c2 = Cow.create(:name => "Riksa", :farmer => @f)
|
473
|
+
end
|
474
|
+
|
475
|
+
it "should let the parent to be destroyed" do
|
476
|
+
@f.destroy.should == true
|
477
|
+
@f.should be_new_record
|
478
|
+
end
|
479
|
+
|
480
|
+
it "should let the children become orphan records" do
|
481
|
+
@f.destroy
|
482
|
+
@c1.farmer.should be_new_record
|
483
|
+
@c2.farmer.should be_new_record
|
484
|
+
end
|
485
|
+
|
486
|
+
it "the children should be destroyable" do
|
487
|
+
@c1.destroy.should == true
|
488
|
+
@c2.destroy.should == true
|
489
|
+
end
|
490
|
+
|
491
|
+
end
|
492
|
+
|
493
|
+
describe "many-to-many associations" do
|
494
|
+
before do
|
495
|
+
@t = Tag.create(:phrase => "Richard Pryor's Chicken")
|
496
|
+
@chk = Chicken.create(:name => "Delicious", :tags => [@t])
|
497
|
+
end
|
498
|
+
|
499
|
+
it "the children should be destroyable" do
|
500
|
+
@chk.destroy.should == true
|
501
|
+
end
|
212
502
|
end
|
213
503
|
|
214
|
-
end # describe
|
504
|
+
end # describe "when :constraint => :skip is given"
|
505
|
+
|
506
|
+
describe "when checking constraint types" do
|
507
|
+
|
508
|
+
#M:M relationships results in a join table composed of a two part primary key
|
509
|
+
# setting a portion of the primary key is not possible for two reasons:
|
510
|
+
# 1. the columns are defined as :nullable => false
|
511
|
+
# 2. there could be duplicate rows if more than one of either of the types
|
512
|
+
# was deleted while being associated to the same type on the other side of the relationshp
|
513
|
+
# Given
|
514
|
+
# Turkey(Name: Ted, ID: 1) =>
|
515
|
+
# Tags[Tag(Phrase: awesome, ID: 1), Tag(Phrase: fat, ID: 2)]
|
516
|
+
# Turkey(Name: Steve, ID: 2) =>
|
517
|
+
# Tags[Tag(Phrase: awesome, ID: 1), Tag(Phrase: flamboyant, ID: 3)]
|
518
|
+
#
|
519
|
+
# Table turkeys_tags would look like (turkey_id, tag_id)
|
520
|
+
# (1, 1)
|
521
|
+
# (1, 2)
|
522
|
+
# (2, 1)
|
523
|
+
# (2, 3)
|
524
|
+
#
|
525
|
+
# If both turkeys were deleted and pk was set null
|
526
|
+
# (null, 1)
|
527
|
+
# (null, 2)
|
528
|
+
# (null, 1) #at this time there would be a duplicate row error
|
529
|
+
# (null, 3)
|
530
|
+
#
|
531
|
+
# I would suggest setting :constraint to :skip in this scenario which will leave
|
532
|
+
# you with orphaned rows.
|
533
|
+
it "should raise an error if :set_nil is given for a M:M relationship" do
|
534
|
+
lambda{
|
535
|
+
class ::Chicken
|
536
|
+
has n, :tags, :through => Resource, :constraint => :set_nil
|
537
|
+
end
|
538
|
+
class ::Tag
|
539
|
+
has n, :chickens, :through => Resource, :constraint => :set_nil
|
540
|
+
end
|
541
|
+
}.should raise_error(ArgumentError)
|
542
|
+
end
|
215
543
|
|
216
|
-
|
217
|
-
|
544
|
+
# Resource#destroy! is not suppored in dm-core
|
545
|
+
it "should raise an error if :destroy! is given for a 1:1 relationship" do
|
546
|
+
lambda do
|
547
|
+
class ::Farmer
|
548
|
+
has 1, :pig, :constraint => :destroy!
|
549
|
+
end
|
550
|
+
end.should raise_error(ArgumentError)
|
218
551
|
end
|
219
552
|
|
220
|
-
it "should raise an error" do
|
553
|
+
it "should raise an error if an unknown type is given" do
|
221
554
|
lambda do
|
222
555
|
class ::Farmer
|
223
556
|
has n, :cows, :constraint => :chocolate
|
data/spec/spec_helper.rb
CHANGED
data/tasks/spec.rb
CHANGED
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.
|
4
|
+
version: 0.9.11
|
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-
|
12
|
+
date: 2009-03-29 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -18,9 +18,9 @@ dependencies:
|
|
18
18
|
version_requirement:
|
19
19
|
version_requirements: !ruby/object:Gem::Requirement
|
20
20
|
requirements:
|
21
|
-
- -
|
21
|
+
- - "="
|
22
22
|
- !ruby/object:Gem::Version
|
23
|
-
version: 0.9.
|
23
|
+
version: 0.9.11
|
24
24
|
version:
|
25
25
|
description: DataMapper plugin constraining relationships
|
26
26
|
email:
|