dm-constraints 1.1.0 → 1.2.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +8 -8
- data/README.rdoc +23 -21
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/dm-constraints.gemspec +36 -39
- data/lib/data_mapper/constraints/adapters/abstract_adapter.rb +36 -0
- data/lib/{dm-constraints/adapters/dm-do-adapter.rb → data_mapper/constraints/adapters/do_adapter.rb} +48 -39
- data/lib/data_mapper/constraints/adapters/extension.rb +49 -0
- data/lib/{dm-constraints/adapters/dm-mysql-adapter.rb → data_mapper/constraints/adapters/mysql_adapter.rb} +2 -8
- data/lib/{dm-constraints/adapters/dm-oracle-adapter.rb → data_mapper/constraints/adapters/oracle_adapter.rb} +9 -2
- data/lib/{dm-constraints/adapters/dm-postgres-adapter.rb → data_mapper/constraints/adapters/postgres_adapter.rb} +1 -1
- data/lib/{dm-constraints/adapters/dm-sqlite-adapter.rb → data_mapper/constraints/adapters/sqlite_adapter.rb} +4 -0
- data/lib/{dm-constraints/adapters/dm-sqlserver-adapter.rb → data_mapper/constraints/adapters/sqlserver_adapter.rb} +1 -1
- data/lib/data_mapper/constraints/migrations/model.rb +36 -0
- data/lib/data_mapper/constraints/migrations/relationship.rb +42 -0
- data/lib/data_mapper/constraints/migrations/singleton_methods.rb +48 -0
- data/lib/data_mapper/constraints/relationship/many_to_many.rb +50 -0
- data/lib/data_mapper/constraints/relationship/one_to_many.rb +83 -0
- data/lib/data_mapper/constraints/resource.rb +34 -0
- data/lib/dm-constraints.rb +12 -88
- data/spec/integration/constraints_spec.rb +4 -2
- data/spec/isolated/require_spec.rb +7 -4
- metadata +79 -44
- data/lib/dm-constraints/adapters/dm-abstract-adapter.rb +0 -22
- data/lib/dm-constraints/delete_constraint.rb +0 -115
- data/lib/dm-constraints/migrations.rb +0 -60
- data/lib/dm-constraints/relationships.rb +0 -41
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
1
|
+
require 'data_mapper/constraints/adapters/do_adapter'
|
2
2
|
|
3
3
|
module DataMapper
|
4
4
|
module Constraints
|
@@ -8,8 +8,10 @@ module DataMapper
|
|
8
8
|
include DataObjectsAdapter
|
9
9
|
|
10
10
|
# oracle does not provide the information_schema table
|
11
|
-
# To question
|
11
|
+
# To question internal state like postgres or mysql
|
12
|
+
#
|
12
13
|
# @see DataMapper::Constraints::Adapters::DataObjectsAdapter
|
14
|
+
#
|
13
15
|
# @api private
|
14
16
|
def constraint_exists?(storage_name, constraint_name)
|
15
17
|
statement = DataMapper::Ext::String.compress_lines(<<-SQL)
|
@@ -24,6 +26,10 @@ module DataMapper
|
|
24
26
|
|
25
27
|
|
26
28
|
# @see DataMapper::Constraints::Adapters::DataObjectsAdapter#create_constraints_statement
|
29
|
+
#
|
30
|
+
# @api private
|
31
|
+
#
|
32
|
+
# TODO: is it desirable to always set `INITIALLY DEFERRED DEFERRABLE`?
|
27
33
|
def create_constraints_statement(storage_name, constraint_name, constraint_type, foreign_keys, reference_storage_name, reference_keys)
|
28
34
|
DataMapper::Ext::String.compress_lines(<<-SQL)
|
29
35
|
ALTER TABLE #{quote_name(storage_name)}
|
@@ -34,6 +40,7 @@ module DataMapper
|
|
34
40
|
SQL
|
35
41
|
end
|
36
42
|
|
43
|
+
# @api private
|
37
44
|
def destroy_constraints_statement(storage_name, constraint_name)
|
38
45
|
DataMapper::Ext::String.compress_lines(<<-SQL)
|
39
46
|
ALTER TABLE #{quote_name(storage_name)}
|
@@ -4,17 +4,21 @@ module DataMapper
|
|
4
4
|
|
5
5
|
module SqliteAdapter
|
6
6
|
|
7
|
+
# @api private
|
7
8
|
def constraint_exists?(*)
|
8
9
|
false
|
9
10
|
end
|
10
11
|
|
12
|
+
# @api private
|
11
13
|
def create_relationship_constraint(*)
|
12
14
|
false
|
13
15
|
end
|
14
16
|
|
17
|
+
# @api private
|
15
18
|
def destroy_relationship_constraint(*)
|
16
19
|
false
|
17
20
|
end
|
21
|
+
|
18
22
|
end
|
19
23
|
|
20
24
|
end
|
@@ -0,0 +1,36 @@
|
|
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
|
+
|
10
|
+
# @api private
|
11
|
+
def auto_migrate_constraints_up(repository_name = self.repository_name)
|
12
|
+
# TODO: this check should not be here
|
13
|
+
return if self.respond_to?(:is_remixable?) && self.is_remixable?
|
14
|
+
|
15
|
+
relationships(repository_name).each do |relationship|
|
16
|
+
relationship.auto_migrate_constraints_up(repository_name)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# @api private
|
21
|
+
def auto_migrate_constraints_down(repository_name = self.repository_name)
|
22
|
+
return unless storage_exists?(repository_name)
|
23
|
+
# TODO: this check should not be here
|
24
|
+
return if self.respond_to?(:is_remixable?) && self.is_remixable?
|
25
|
+
|
26
|
+
relationships(repository_name).each do |relationship|
|
27
|
+
relationship.auto_migrate_constraints_down(repository_name)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end # module Model
|
32
|
+
end # module Migrations
|
33
|
+
end # module Constraints
|
34
|
+
|
35
|
+
Model.append_extensions Constraints::Migrations::Model
|
36
|
+
end # module DataMapper
|
@@ -0,0 +1,42 @@
|
|
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
|
+
|
31
|
+
end # module Relationship
|
32
|
+
end # module Migrations
|
33
|
+
end # module Constraints
|
34
|
+
|
35
|
+
Associations::Relationship.class_eval do
|
36
|
+
include Constraints::Migrations::Relationship
|
37
|
+
end
|
38
|
+
|
39
|
+
Associations::ManyToOne::Relationship.class_eval do
|
40
|
+
include Constraints::Migrations::Relationship::ManyToOne
|
41
|
+
end
|
42
|
+
end # module DataMapper
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Constraints
|
3
|
+
module Migrations
|
4
|
+
module SingletonMethods
|
5
|
+
|
6
|
+
def auto_migrate!(repository_name = nil)
|
7
|
+
auto_migrate_constraints_down(repository_name)
|
8
|
+
# TODO: Model#auto_migrate! drops and adds constraints, as well.
|
9
|
+
# is that an avoidable duplication?
|
10
|
+
super
|
11
|
+
auto_migrate_constraints_up(repository_name)
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def auto_migrate_down!(repository_name = nil)
|
18
|
+
auto_migrate_constraints_down(repository_name)
|
19
|
+
super
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def auto_migrate_up!(repository_name = nil)
|
24
|
+
super
|
25
|
+
auto_migrate_constraints_up(repository_name)
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
# @api private
|
30
|
+
def auto_migrate_constraints_up(repository_name = nil)
|
31
|
+
DataMapper::Model.descendants.each do |model|
|
32
|
+
model.auto_migrate_constraints_up(repository_name || model.default_repository_name)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# @api private
|
37
|
+
def auto_migrate_constraints_down(repository_name = nil)
|
38
|
+
DataMapper::Model.descendants.each do |model|
|
39
|
+
model.auto_migrate_constraints_down(repository_name || model.default_repository_name)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end # module SingletonMethods
|
44
|
+
end # module Migrations
|
45
|
+
end # module Constraints
|
46
|
+
|
47
|
+
extend Constraints::Migrations::SingletonMethods
|
48
|
+
end # module DataMapper
|
@@ -0,0 +1,50 @@
|
|
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
|
9
|
+
|
10
|
+
def one_to_many_options
|
11
|
+
super.merge(:constraint => @constraint)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Checks that the constraint type is appropriate to the relationship
|
15
|
+
#
|
16
|
+
# @param [Fixnum] cardinality
|
17
|
+
# cardinality of relationship
|
18
|
+
# @param [Symbol] name
|
19
|
+
# name of relationship to evaluate constraint of
|
20
|
+
# @param [Hash] options
|
21
|
+
# options hash
|
22
|
+
#
|
23
|
+
# @option *args :constraint[Symbol]
|
24
|
+
# one of VALID_CONSTRAINT_VALUES
|
25
|
+
#
|
26
|
+
# @raise ArgumentError
|
27
|
+
# if @option :constraint is not one of VALID_CONSTRAINT_TYPES
|
28
|
+
#
|
29
|
+
# @return [Undefined]
|
30
|
+
#
|
31
|
+
# @api private
|
32
|
+
def assert_valid_constraint
|
33
|
+
super
|
34
|
+
|
35
|
+
# TODO: is any constraint valid for a m:m relationship?
|
36
|
+
if @constraint == :set_nil
|
37
|
+
raise ArgumentError, "#{@constraint} is not a valid constraint type for #{self.class}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end # module ManyToMany
|
42
|
+
end # module Relationship
|
43
|
+
end # module Constraints
|
44
|
+
|
45
|
+
Associations::ManyToMany::Relationship::OPTIONS << :constraint
|
46
|
+
|
47
|
+
Associations::ManyToMany::Relationship.class_eval do
|
48
|
+
include Constraints::Relationship::ManyToMany
|
49
|
+
end
|
50
|
+
end # module DataMapper
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Constraints
|
3
|
+
module Relationship
|
4
|
+
module OneToMany
|
5
|
+
|
6
|
+
attr_reader :constraint
|
7
|
+
|
8
|
+
# @api private
|
9
|
+
def enforce_destroy_constraint(resource)
|
10
|
+
return true unless association = get(resource)
|
11
|
+
|
12
|
+
constraint = self.constraint
|
13
|
+
|
14
|
+
case constraint
|
15
|
+
when :protect
|
16
|
+
Array(association).empty?
|
17
|
+
when :destroy, :destroy!
|
18
|
+
association.__send__(constraint)
|
19
|
+
when :set_nil
|
20
|
+
Array(association).all? do |resource|
|
21
|
+
resource.update(inverse => nil)
|
22
|
+
end
|
23
|
+
when :skip
|
24
|
+
true # do nothing
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
##
|
31
|
+
# Adds the delete constraint options to a relationship
|
32
|
+
#
|
33
|
+
# @param params [*ARGS] Arguments passed to Relationship#initialize
|
34
|
+
#
|
35
|
+
# @return [nil]
|
36
|
+
#
|
37
|
+
# @api private
|
38
|
+
def initialize(*args)
|
39
|
+
super
|
40
|
+
set_constraint
|
41
|
+
assert_valid_constraint
|
42
|
+
end
|
43
|
+
|
44
|
+
def set_constraint
|
45
|
+
@constraint = @options.fetch(:constraint, :protect) || :skip
|
46
|
+
end
|
47
|
+
|
48
|
+
# Checks that the constraint type is appropriate to the relationship
|
49
|
+
#
|
50
|
+
# @param [Fixnum] cardinality
|
51
|
+
# cardinality of relationship
|
52
|
+
# @param [Symbol] name
|
53
|
+
# name of relationship to evaluate constraint of
|
54
|
+
# @param [Hash] options
|
55
|
+
# options hash
|
56
|
+
#
|
57
|
+
# @option *args :constraint[Symbol]
|
58
|
+
# one of VALID_CONSTRAINT_VALUES
|
59
|
+
#
|
60
|
+
# @raise ArgumentError
|
61
|
+
# if @option :constraint is not one of VALID_CONSTRAINT_VALUES
|
62
|
+
#
|
63
|
+
# @return [Undefined]
|
64
|
+
#
|
65
|
+
# @api semipublic
|
66
|
+
def assert_valid_constraint
|
67
|
+
return unless @constraint
|
68
|
+
|
69
|
+
unless VALID_CONSTRAINT_VALUES.include?(@constraint)
|
70
|
+
raise ArgumentError, ":constraint option must be one of #{VALID_CONSTRAINT_VALUES.to_a.join(', ')}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end # module OneToMany
|
75
|
+
end # module Relationship
|
76
|
+
end # module Constraints
|
77
|
+
|
78
|
+
Associations::OneToMany::Relationship::OPTIONS << :constraint
|
79
|
+
|
80
|
+
Associations::OneToMany::Relationship.class_eval do
|
81
|
+
include Constraints::Relationship::OneToMany
|
82
|
+
end
|
83
|
+
end # module DataMapper
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Constraints
|
3
|
+
module Resource
|
4
|
+
def before_destroy_hook
|
5
|
+
enforce_destroy_constraints
|
6
|
+
super
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
# Check delete constraints prior to destroying a dm resource or collection
|
12
|
+
#
|
13
|
+
# @note
|
14
|
+
# - It only considers a relationship's constraints if this is the parent model (ie a child shouldn't delete a parent)
|
15
|
+
# - Many to Many Relationships are skipped, as they are evaluated by their underlying 1:M relationships
|
16
|
+
#
|
17
|
+
# @return [nil]
|
18
|
+
#
|
19
|
+
# @api semi-public
|
20
|
+
def enforce_destroy_constraints
|
21
|
+
relationships.each do |relationship|
|
22
|
+
next unless relationship.respond_to?(:enforce_destroy_constraint)
|
23
|
+
|
24
|
+
constraint_satisfied = relationship.enforce_destroy_constraint(self)
|
25
|
+
|
26
|
+
throw(:halt, false) unless constraint_satisfied
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end # module Resource
|
31
|
+
end # module Constraints
|
32
|
+
|
33
|
+
Model.append_inclusions Constraints::Resource
|
34
|
+
end # module DataMapper
|
data/lib/dm-constraints.rb
CHANGED
@@ -1,95 +1,19 @@
|
|
1
1
|
require 'dm-core'
|
2
2
|
|
3
|
-
require '
|
4
|
-
require 'dm-constraints/delete_constraint'
|
5
|
-
require 'dm-constraints/relationships'
|
6
|
-
require 'dm-constraints/adapters/dm-abstract-adapter'
|
3
|
+
require 'data_mapper/constraints/resource'
|
7
4
|
|
8
|
-
|
5
|
+
require 'data_mapper/constraints/migrations/model'
|
6
|
+
require 'data_mapper/constraints/migrations/relationship'
|
7
|
+
require 'data_mapper/constraints/migrations/singleton_methods'
|
9
8
|
|
10
|
-
|
9
|
+
require 'data_mapper/constraints/relationship/one_to_many'
|
10
|
+
require 'data_mapper/constraints/relationship/many_to_many'
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
append_extensions DataMapper::Constraints::Migrations::Model
|
15
|
-
end
|
12
|
+
require 'data_mapper/constraints/adapters/extension'
|
13
|
+
require 'data_mapper/constraints/adapters/abstract_adapter'
|
16
14
|
|
15
|
+
module DataMapper
|
17
16
|
module Constraints
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
module ClassMethods
|
22
|
-
include DeleteConstraint::ClassMethods
|
23
|
-
end
|
24
|
-
|
25
|
-
##
|
26
|
-
# Add before hooks to #has to check for proper constraint definitions
|
27
|
-
# Add before hooks to #destroy to properly constrain children
|
28
|
-
#
|
29
|
-
def self.included(model)
|
30
|
-
model.extend(ClassMethods)
|
31
|
-
model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
32
|
-
before_class_method :has, :check_delete_constraint_type
|
33
|
-
|
34
|
-
if instance_methods.any? { |m| m.to_sym == :destroy }
|
35
|
-
before :destroy, :check_delete_constraints
|
36
|
-
end
|
37
|
-
RUBY
|
38
|
-
end
|
39
|
-
|
40
|
-
def self.include_constraint_api
|
41
|
-
DataMapper::Adapters::AbstractAdapter.descendants.each do |adapter_class|
|
42
|
-
DataMapper::Adapters.include_constraint_api(DataMapper::Inflector.demodulize(adapter_class.name))
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
Model.append_inclusions self
|
47
|
-
|
48
|
-
end # module Constraints
|
49
|
-
|
50
|
-
module Adapters
|
51
|
-
|
52
|
-
class AbstractAdapter
|
53
|
-
include DataMapper::Constraints::Adapters::AbstractAdapter
|
54
|
-
end
|
55
|
-
|
56
|
-
def self.include_constraint_api(const_name)
|
57
|
-
require constraint_extensions(const_name)
|
58
|
-
if Constraints::Adapters.const_defined?(const_name)
|
59
|
-
adapter = const_get(const_name)
|
60
|
-
adapter.send(:include, constraint_module(const_name))
|
61
|
-
end
|
62
|
-
rescue LoadError
|
63
|
-
# Silently ignore the fact that no adapter extensions could be required
|
64
|
-
# This means that the adapter in use doesn't support constraints
|
65
|
-
end
|
66
|
-
|
67
|
-
def self.constraint_module(const_name)
|
68
|
-
Constraints::Adapters.const_get(const_name)
|
69
|
-
end
|
70
|
-
|
71
|
-
class << self
|
72
|
-
private
|
73
|
-
|
74
|
-
# @api private
|
75
|
-
def constraint_extensions(const_name)
|
76
|
-
name = adapter_name(const_name)
|
77
|
-
name = 'do' if name == 'dataobjects'
|
78
|
-
"dm-constraints/adapters/dm-#{name}-adapter"
|
79
|
-
end
|
80
|
-
|
81
|
-
end
|
82
|
-
|
83
|
-
extendable do
|
84
|
-
# @api private
|
85
|
-
def const_added(const_name)
|
86
|
-
include_constraint_api(const_name)
|
87
|
-
super
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
end # module Adapters
|
92
|
-
|
93
|
-
Constraints.include_constraint_api
|
94
|
-
|
95
|
-
end # module DataMapper
|
17
|
+
VALID_CONSTRAINT_VALUES = [ :protect, :destroy, :destroy!, :set_nil, :skip ].to_set.freeze
|
18
|
+
end
|
19
|
+
end
|