sbf-dm-constraints 1.3.0.beta

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 (35) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +39 -0
  3. data/.rspec +5 -0
  4. data/.rubocop.yml +468 -0
  5. data/Gemfile +70 -0
  6. data/LICENSE +20 -0
  7. data/README.rdoc +58 -0
  8. data/Rakefile +4 -0
  9. data/dm-constraints.gemspec +21 -0
  10. data/lib/data_mapper/constraints/adapters/abstract_adapter.rb +33 -0
  11. data/lib/data_mapper/constraints/adapters/do_adapter.rb +192 -0
  12. data/lib/data_mapper/constraints/adapters/extension.rb +45 -0
  13. data/lib/data_mapper/constraints/adapters/mysql_adapter.rb +26 -0
  14. data/lib/data_mapper/constraints/adapters/oracle_adapter.rb +53 -0
  15. data/lib/data_mapper/constraints/adapters/postgres_adapter.rb +13 -0
  16. data/lib/data_mapper/constraints/adapters/sqlite_adapter.rb +22 -0
  17. data/lib/data_mapper/constraints/adapters/sqlserver_adapter.rb +11 -0
  18. data/lib/data_mapper/constraints/migrations/model.rb +34 -0
  19. data/lib/data_mapper/constraints/migrations/relationship.rb +41 -0
  20. data/lib/data_mapper/constraints/migrations/singleton_methods.rb +44 -0
  21. data/lib/data_mapper/constraints/relationship/many_to_many.rb +48 -0
  22. data/lib/data_mapper/constraints/relationship/one_to_many.rb +79 -0
  23. data/lib/data_mapper/constraints/resource.rb +30 -0
  24. data/lib/data_mapper/constraints/version.rb +5 -0
  25. data/lib/dm-constraints.rb +19 -0
  26. data/spec/integration/constraints_spec.rb +630 -0
  27. data/spec/isolated/require_after_setup_spec.rb +36 -0
  28. data/spec/isolated/require_before_setup_spec.rb +35 -0
  29. data/spec/isolated/require_spec.rb +15 -0
  30. data/spec/rcov.opts +6 -0
  31. data/spec/spec_helper.rb +26 -0
  32. data/tasks/spec.rake +21 -0
  33. data/tasks/yard.rake +9 -0
  34. data/tasks/yardstick.rake +19 -0
  35. metadata +94 -0
@@ -0,0 +1,33 @@
1
+ require 'data_mapper/constraints/adapters/extension'
2
+
3
+ module DataMapper
4
+ module Constraints
5
+ module Adapters
6
+ module AbstractAdapter
7
+ # @api private
8
+ def constraint_exists?(*)
9
+ false
10
+ end
11
+
12
+ # @api private
13
+ def create_relationship_constraint(*)
14
+ false
15
+ end
16
+
17
+ # @api private
18
+ def destroy_relationship_constraint(*)
19
+ false
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ Adapters::AbstractAdapter.class_eval do
26
+ include Constraints::Adapters::AbstractAdapter
27
+ end
28
+
29
+ Adapters::AbstractAdapter.descendants.each do |adapter_class|
30
+ const_name = DataMapper::Inflector.demodulize(adapter_class.name)
31
+ Adapters.include_constraint_api(const_name)
32
+ end
33
+ end
@@ -0,0 +1,192 @@
1
+ module DataMapper
2
+ module Constraints
3
+ module Adapters
4
+ module DataObjectsAdapter
5
+ ##
6
+ # Determine if a constraint exists for a table
7
+ #
8
+ # @param storage_name [Symbol]
9
+ # name of table to check constraint on
10
+ # @param constraint_name [~String]
11
+ # name of constraint to check for
12
+ #
13
+ # @return [Boolean]
14
+ #
15
+ # @api private
16
+ def constraint_exists?(storage_name, constraint_name)
17
+ statement = DataMapper::Ext::String.compress_lines(<<-SQL)
18
+ SELECT COUNT(*)
19
+ FROM #{quote_name('information_schema')}.#{quote_name('table_constraints')}
20
+ WHERE #{quote_name('constraint_type')} = 'FOREIGN KEY'
21
+ AND #{quote_name('table_schema')} = ?
22
+ AND #{quote_name('table_name')} = ?
23
+ AND #{quote_name('constraint_name')} = ?
24
+ SQL
25
+
26
+ select(statement, schema_name, storage_name, constraint_name).first > 0
27
+ end
28
+
29
+ ##
30
+ # Create the constraint for a relationship
31
+ #
32
+ # @param relationship [Relationship]
33
+ # the relationship to create the constraint for
34
+ #
35
+ # @return [true, false]
36
+ # true if creating the constraints was successful
37
+ #
38
+ # @api semipublic
39
+ def create_relationship_constraint(relationship)
40
+ return false unless valid_relationship_for_constraint?(relationship)
41
+
42
+ source_storage_name = relationship.source_model.storage_name(name)
43
+ target_storage_name = relationship.target_model.storage_name(name)
44
+ constraint_name = constraint_name(source_storage_name, relationship.name)
45
+
46
+ return false if constraint_exists?(source_storage_name, constraint_name)
47
+
48
+ constraint_type =
49
+ case relationship.inverse.constraint
50
+ when :protect then 'NO ACTION'
51
+ # TODO: support :cascade as an option:
52
+ # (destroy doesn't communicate the UPDATE constraint)
53
+ when :destroy, :destroy! then 'CASCADE'
54
+ when :set_nil then 'SET NULL'
55
+ end
56
+
57
+ return false if constraint_type.nil?
58
+
59
+ source_keys = relationship.source_key.map { |p| property_to_column_name(p, false) }
60
+ target_keys = relationship.target_key.map { |p| property_to_column_name(p, false) }
61
+
62
+ create_constraints_statement = create_constraints_statement(
63
+ constraint_name,
64
+ constraint_type,
65
+ source_storage_name,
66
+ source_keys,
67
+ target_storage_name,
68
+ target_keys
69
+ )
70
+
71
+ execute(create_constraints_statement)
72
+ end
73
+
74
+ ##
75
+ # Remove the constraint for a relationship
76
+ #
77
+ # @param relationship [Relationship]
78
+ # the relationship to remove the constraint for
79
+ #
80
+ # @return [true, false]
81
+ # true if destroying the constraint was successful
82
+ #
83
+ # @api semipublic
84
+ def destroy_relationship_constraint(relationship)
85
+ return false unless valid_relationship_for_constraint?(relationship)
86
+
87
+ storage_name = relationship.source_model.storage_name(name)
88
+ constraint_name = constraint_name(storage_name, relationship.name)
89
+
90
+ return false unless constraint_exists?(storage_name, constraint_name)
91
+
92
+ destroy_constraints_statement =
93
+ destroy_constraints_statement(storage_name, constraint_name)
94
+
95
+ execute(destroy_constraints_statement)
96
+ end
97
+
98
+ ##
99
+ # Check to see if the relationship's constraints can be used
100
+ #
101
+ # Only one-to-one, one-to-many, and many-to-many relationships
102
+ # can be used for constraints. They must also be in the same
103
+ # repository as the adapter is connected to.
104
+ #
105
+ # @param relationship [Relationship]
106
+ # the relationship to check
107
+ #
108
+ # @return [true, false]
109
+ # true if a constraint can be established for relationship
110
+ #
111
+ # @api private
112
+ private def valid_relationship_for_constraint?(relationship)
113
+ return false unless relationship.source_repository_name == name || relationship.source_repository_name.nil?
114
+ return false unless relationship.target_repository_name == name || relationship.target_repository_name.nil?
115
+ return false unless relationship.is_a?(Associations::ManyToOne::Relationship)
116
+
117
+ true
118
+ end
119
+
120
+ module SQL
121
+ # Generates the SQL statement to create a constraint
122
+ #
123
+ # @param [String] constraint_name
124
+ # name of the foreign key constraint
125
+ # @param [String] constraint_type
126
+ # type of constraint to ALTER source_storage_name with
127
+ # @param [String] source_storage_name
128
+ # name of table to ALTER with constraint
129
+ # @param [Array(String)] source_keys
130
+ # columns in source_storage_name that refer to foreign table
131
+ # @param [String] target_storage_name
132
+ # target table of the constraint
133
+ # @param [Array(String)] target_keys
134
+ # columns the target table that are referred to
135
+ #
136
+ # @return [String]
137
+ # SQL DDL Statement to create a constraint
138
+ #
139
+ # @api private
140
+ private def create_constraints_statement(constraint_name, constraint_type, source_storage_name, source_keys, target_storage_name, target_keys)
141
+ DataMapper::Ext::String.compress_lines(<<-SQL)
142
+ ALTER TABLE #{quote_name(source_storage_name)}
143
+ ADD CONSTRAINT #{quote_name(constraint_name)}
144
+ FOREIGN KEY (#{source_keys.join(', ')})
145
+ REFERENCES #{quote_name(target_storage_name)} (#{target_keys.join(', ')})
146
+ ON DELETE #{constraint_type}
147
+ ON UPDATE #{constraint_type}
148
+ SQL
149
+ end
150
+
151
+ ##
152
+ # Generates the SQL statement to destroy a constraint
153
+ #
154
+ # @param [String] storage_name
155
+ # name of table to constrain
156
+ # @param [String] constraint_name
157
+ # name of foreign key constraint
158
+ #
159
+ # @return [String]
160
+ # SQL DDL Statement to destroy a constraint
161
+ #
162
+ # @api private
163
+ private def destroy_constraints_statement(storage_name, constraint_name)
164
+ DataMapper::Ext::String.compress_lines(<<-SQL)
165
+ ALTER TABLE #{quote_name(storage_name)}
166
+ DROP CONSTRAINT #{quote_name(constraint_name)}
167
+ SQL
168
+ end
169
+
170
+ ##
171
+ # generates a unique constraint name given a table and a relationships
172
+ #
173
+ # @param [String] storage_name
174
+ # name of table to constrain
175
+ # @param [String] relationship_name
176
+ # name of the relationship to constrain
177
+ #
178
+ # @return [String]
179
+ # name of the constraint
180
+ #
181
+ # @api private
182
+ private def constraint_name(storage_name, relationship_name)
183
+ identifier = "#{storage_name}_#{relationship_name}"[0, self.class::IDENTIFIER_MAX_LENGTH - 3]
184
+ "#{identifier}_fk"
185
+ end
186
+ end
187
+
188
+ include SQL
189
+ end
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,45 @@
1
+ module DataMapper
2
+ module Constraints
3
+ module Adapters
4
+ module Extension
5
+ # Include the corresponding Constraints module into a adapter class
6
+ #
7
+ # @param [Symbol] const_name
8
+ # demodulized name of the adapter class to include corresponding
9
+ # constraints module into
10
+ #
11
+ # TODO: come up with a better way to include modules
12
+ # into all currently loaded and subsequently loaded Adapters
13
+ #
14
+ # @api private
15
+ def include_constraint_api(const_name)
16
+ require constraint_extensions(const_name)
17
+
18
+ if Constraints::Adapters.const_defined?(const_name)
19
+ adapter = const_get(const_name)
20
+ constraint_module = Constraints::Adapters.const_get(const_name)
21
+ adapter.class_eval { include constraint_module }
22
+ end
23
+ rescue LoadError
24
+ # Silently ignore the fact that no adapter extensions could be required
25
+ # This means that the adapter in use doesn't support constraints
26
+ end
27
+
28
+ # @api private
29
+ private def constraint_extensions(const_name)
30
+ name = adapter_name(const_name)
31
+ name = 'do' if name == 'dataobjects'
32
+ "data_mapper/constraints/adapters/#{name}_adapter"
33
+ end
34
+
35
+ # @api private
36
+ private def const_added(const_name)
37
+ include_constraint_api(const_name)
38
+ super
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ Adapters.extend Constraints::Adapters::Extension
45
+ end
@@ -0,0 +1,26 @@
1
+ require 'data_mapper/constraints/adapters/do_adapter'
2
+
3
+ module DataMapper
4
+ module Constraints
5
+ module Adapters
6
+ module MysqlAdapter
7
+ include SQL, DataObjectsAdapter
8
+
9
+ module SQL
10
+ ##
11
+ # MySQL specific query to drop a foreign key
12
+ #
13
+ # @see DataMapper::Constraints::Adapters::DataObjectsAdapter#destroy_constraints_statement
14
+ #
15
+ # @api private
16
+ private def destroy_constraints_statement(storage_name, constraint_name)
17
+ DataMapper::Ext::String.compress_lines(<<-SQL)
18
+ ALTER TABLE #{quote_name(storage_name)}
19
+ DROP FOREIGN KEY #{quote_name(constraint_name)}
20
+ SQL
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,53 @@
1
+ require 'data_mapper/constraints/adapters/do_adapter'
2
+
3
+ module DataMapper
4
+ module Constraints
5
+ module Adapters
6
+ module OracleAdapter
7
+ include DataObjectsAdapter
8
+
9
+ # oracle does not provide the information_schema table
10
+ # To question internal state like postgres or mysql
11
+ #
12
+ # @see DataMapper::Constraints::Adapters::DataObjectsAdapter
13
+ #
14
+ # @api private
15
+ def constraint_exists?(storage_name, constraint_name)
16
+ statement = DataMapper::Ext::String.compress_lines(<<-SQL)
17
+ SELECT COUNT(*)
18
+ FROM USER_CONSTRAINTS
19
+ WHERE table_name = ?
20
+ AND constraint_name = ?
21
+ SQL
22
+
23
+ select(statement, oracle_upcase(storage_name)[0, self.class::IDENTIFIER_MAX_LENGTH].gsub('"', '_'), oracle_upcase(constraint_name)[0, self.class::IDENTIFIER_MAX_LENGTH].gsub('"', '_')).first > 0
24
+ end
25
+
26
+ # @see DataMapper::Constraints::Adapters::DataObjectsAdapter#create_constraints_statement
27
+ #
28
+ # @api private
29
+ #
30
+ # TODO: is it desirable to always set `INITIALLY DEFERRED DEFERRABLE`?
31
+ def create_constraints_statement(constraint_name, constraint_type, source_storage_name, source_keys, target_storage_name, target_keys)
32
+ DataMapper::Ext::String.compress_lines(<<-SQL)
33
+ ALTER TABLE #{quote_name(source_storage_name)}
34
+ ADD CONSTRAINT #{quote_name(constraint_name)}
35
+ FOREIGN KEY (#{source_keys.join(', ')})
36
+ REFERENCES #{quote_name(target_storage_name)} (#{target_keys.join(', ')})
37
+ #{"ON DELETE #{constraint_type}" if constraint_type && constraint_type != 'NO ACTION'}
38
+ INITIALLY DEFERRED DEFERRABLE
39
+ SQL
40
+ end
41
+
42
+ # @api private
43
+ def destroy_constraints_statement(storage_name, constraint_name)
44
+ DataMapper::Ext::String.compress_lines(<<-SQL)
45
+ ALTER TABLE #{quote_name(storage_name)}
46
+ DROP CONSTRAINT #{quote_name(constraint_name)}
47
+ CASCADE
48
+ SQL
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,13 @@
1
+ require 'data_mapper/constraints/adapters/do_adapter'
2
+
3
+ module DataMapper
4
+ module Constraints
5
+ module Adapters
6
+
7
+ module PostgresAdapter
8
+ include DataObjectsAdapter
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,22 @@
1
+ module DataMapper
2
+ module Constraints
3
+ module Adapters
4
+ module SqliteAdapter
5
+ # @api private
6
+ def constraint_exists?(*)
7
+ false
8
+ end
9
+
10
+ # @api private
11
+ def create_relationship_constraint(*)
12
+ false
13
+ end
14
+
15
+ # @api private
16
+ def destroy_relationship_constraint(*)
17
+ false
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,11 @@
1
+ require 'data_mapper/constraints/adapters/do_adapter'
2
+
3
+ module DataMapper
4
+ module Constraints
5
+ module Adapters
6
+ module SqlserverAdapter
7
+ include DataObjectsAdapter
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,34 @@
1
+ # TODO: figure out some other (less tightly coupled) way to ensure that
2
+ # dm-migrations' method implementations are loaded before this file
3
+ require 'dm-migrations/auto_migration'
4
+
5
+ module DataMapper
6
+ module Constraints
7
+ module Migrations
8
+ module Model
9
+ # @api private
10
+ def auto_migrate_constraints_up(repository_name = self.repository_name)
11
+ # TODO: this check should not be here
12
+ return if respond_to?(:is_remixable?) && is_remixable?
13
+
14
+ relationships(repository_name).each do |relationship|
15
+ relationship.auto_migrate_constraints_up(repository_name)
16
+ end
17
+ end
18
+
19
+ # @api private
20
+ def auto_migrate_constraints_down(repository_name = self.repository_name)
21
+ return unless storage_exists?(repository_name)
22
+ # TODO: this check should not be here
23
+ return if respond_to?(:is_remixable?) && is_remixable?
24
+
25
+ relationships(repository_name).each do |relationship|
26
+ relationship.auto_migrate_constraints_down(repository_name)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ Model.append_extensions Constraints::Migrations::Model
34
+ end
@@ -0,0 +1,41 @@
1
+ module DataMapper
2
+ module Constraints
3
+ module Migrations
4
+ module Relationship
5
+ # @api private
6
+ def auto_migrate_constraints_up(_repository_name)
7
+ # no-op
8
+ end
9
+
10
+ # @api private
11
+ def auto_migrate_constraints_down(_repository_name)
12
+ # no-op
13
+ end
14
+
15
+ module ManyToOne
16
+ # @api private
17
+ def auto_migrate_constraints_up(repository_name)
18
+ adapter = DataMapper.repository(repository_name)&.adapter
19
+ adapter&.create_relationship_constraint(self)
20
+ self
21
+ end
22
+
23
+ # @api private
24
+ def auto_migrate_constraints_down(repository_name)
25
+ adapter = DataMapper.repository(repository_name)&.adapter
26
+ adapter&.destroy_relationship_constraint(self)
27
+ self
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ Associations::Relationship.class_eval do
35
+ include Constraints::Migrations::Relationship
36
+ end
37
+
38
+ Associations::ManyToOne::Relationship.class_eval do
39
+ include Constraints::Migrations::Relationship::ManyToOne
40
+ end
41
+ end
@@ -0,0 +1,44 @@
1
+ module DataMapper
2
+ module Constraints
3
+ module Migrations
4
+ module SingletonMethods
5
+ def auto_migrate!(repository_name = nil)
6
+ auto_migrate_constraints_down(repository_name)
7
+ # TODO: Model#auto_migrate! drops and adds constraints, as well.
8
+ # is that an avoidable duplication?
9
+ super
10
+ auto_migrate_constraints_up(repository_name)
11
+ self
12
+ end
13
+
14
+ private def auto_migrate_down!(repository_name = nil)
15
+ auto_migrate_constraints_down(repository_name)
16
+ super
17
+ self
18
+ end
19
+
20
+ private def auto_migrate_up!(repository_name = nil)
21
+ super
22
+ auto_migrate_constraints_up(repository_name)
23
+ self
24
+ end
25
+
26
+ # @api private
27
+ private def auto_migrate_constraints_up(repository_name = nil)
28
+ DataMapper::Model.descendants.each do |model|
29
+ model.auto_migrate_constraints_up(repository_name || model.default_repository_name)
30
+ end
31
+ end
32
+
33
+ # @api private
34
+ private def auto_migrate_constraints_down(repository_name = nil)
35
+ DataMapper::Model.descendants.each do |model|
36
+ model.auto_migrate_constraints_down(repository_name || model.default_repository_name)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ extend Constraints::Migrations::SingletonMethods
44
+ end
@@ -0,0 +1,48 @@
1
+ require 'data_mapper/constraints/relationship/one_to_many'
2
+
3
+ module DataMapper
4
+ module Constraints
5
+ module Relationship
6
+ module ManyToMany
7
+
8
+ private def one_to_many_options
9
+ super.merge(constraint: @constraint)
10
+ end
11
+
12
+ # Checks that the constraint type is appropriate to the relationship
13
+ #
14
+ # @param [Fixnum] cardinality
15
+ # cardinality of relationship
16
+ # @param [Symbol] name
17
+ # name of relationship to evaluate constraint of
18
+ # @param [Hash] options
19
+ # options hash
20
+ #
21
+ # @option *args :constraint[Symbol]
22
+ # one of VALID_CONSTRAINT_VALUES
23
+ #
24
+ # @raise ArgumentError
25
+ # if @option :constraint is not one of VALID_CONSTRAINT_TYPES
26
+ #
27
+ # @return [Undefined]
28
+ #
29
+ # @api private
30
+ private def assert_valid_constraint
31
+ super
32
+
33
+ # TODO: is any constraint valid for a m:m relationship?
34
+ return unless @constraint == :set_nil
35
+
36
+ raise ArgumentError, "#{@constraint} is not a valid constraint type for #{self.class}"
37
+ end
38
+
39
+ end
40
+ end
41
+ end
42
+
43
+ Associations::ManyToMany::Relationship::OPTIONS << :constraint
44
+
45
+ Associations::ManyToMany::Relationship.class_eval do
46
+ include Constraints::Relationship::ManyToMany
47
+ end
48
+ end
@@ -0,0 +1,79 @@
1
+ module DataMapper
2
+ module Constraints
3
+ module Relationship
4
+ module OneToMany
5
+ attr_reader :constraint
6
+
7
+ # @api private
8
+ def enforce_destroy_constraint(resource)
9
+ return true unless (association = get(resource))
10
+
11
+ constraint = self.constraint
12
+
13
+ case constraint
14
+ when :protect
15
+ Array(association).empty?
16
+ when :destroy, :destroy!
17
+ association.__send__(constraint)
18
+ when :set_nil
19
+ Array(association).all? do |r|
20
+ r.update(inverse => nil)
21
+ end
22
+ when :skip
23
+ true # do nothing
24
+ end
25
+ end
26
+
27
+ ##
28
+ # Adds the delete constraint options to a relationship
29
+ #
30
+ # @param args [*args] Arguments passed to Relationship#initialize
31
+ #
32
+ # @return [nil]
33
+ #
34
+ # @api private
35
+ private def initialize(*args)
36
+ super
37
+ set_constraint
38
+ assert_valid_constraint
39
+ end
40
+
41
+ private def set_constraint
42
+ @constraint = @options.fetch(:constraint, :protect) || :skip
43
+ end
44
+
45
+ # Checks that the constraint type is appropriate to the relationship
46
+ #
47
+ # @param [Fixnum] cardinality
48
+ # cardinality of relationship
49
+ # @param [Symbol] name
50
+ # name of relationship to evaluate constraint of
51
+ # @param [Hash] options
52
+ # options hash
53
+ #
54
+ # @option *args :constraint[Symbol]
55
+ # one of VALID_CONSTRAINT_VALUES
56
+ #
57
+ # @raise ArgumentError
58
+ # if @option :constraint is not one of VALID_CONSTRAINT_VALUES
59
+ #
60
+ # @return [Undefined]
61
+ #
62
+ # @api semipublic
63
+ private def assert_valid_constraint
64
+ return unless @constraint
65
+
66
+ return if VALID_CONSTRAINT_VALUES.include?(@constraint)
67
+
68
+ raise ArgumentError, ":constraint option must be one of #{VALID_CONSTRAINT_VALUES.to_a.join(', ')}"
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ Associations::OneToMany::Relationship::OPTIONS << :constraint
75
+
76
+ Associations::OneToMany::Relationship.class_eval do
77
+ include Constraints::Relationship::OneToMany
78
+ end
79
+ end
@@ -0,0 +1,30 @@
1
+ module DataMapper
2
+ module Constraints
3
+ module Resource
4
+ def before_destroy_hook
5
+ enforce_destroy_constraints
6
+ super
7
+ end
8
+
9
+ # Check delete constraints prior to destroying a dm resource or collection
10
+ #
11
+ # @note
12
+ # - It only considers a relationship's constraints if this is the parent model (ie a child shouldn't delete a parent)
13
+ # - Many to Many Relationships are skipped, as they are evaluated by their underlying 1:M relationships
14
+ #
15
+ # @return [nil]
16
+ #
17
+ # @api semi-public
18
+ private def enforce_destroy_constraints
19
+ relationships.each do |relationship|
20
+ next unless relationship.respond_to?(:enforce_destroy_constraint)
21
+
22
+ constraint_satisfied = relationship.enforce_destroy_constraint(self)
23
+
24
+ throw(:halt, false) unless constraint_satisfied
25
+ end
26
+ end
27
+ end
28
+ end
29
+ Model.append_inclusions Constraints::Resource
30
+ end
@@ -0,0 +1,5 @@
1
+ module DataMapper
2
+ module Constraints
3
+ VERSION = '1.3.0.beta'.freeze
4
+ end
5
+ end