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.
Files changed (27) hide show
  1. data/Gemfile +8 -8
  2. data/README.rdoc +23 -21
  3. data/Rakefile +1 -1
  4. data/VERSION +1 -1
  5. data/dm-constraints.gemspec +36 -39
  6. data/lib/data_mapper/constraints/adapters/abstract_adapter.rb +36 -0
  7. data/lib/{dm-constraints/adapters/dm-do-adapter.rb → data_mapper/constraints/adapters/do_adapter.rb} +48 -39
  8. data/lib/data_mapper/constraints/adapters/extension.rb +49 -0
  9. data/lib/{dm-constraints/adapters/dm-mysql-adapter.rb → data_mapper/constraints/adapters/mysql_adapter.rb} +2 -8
  10. data/lib/{dm-constraints/adapters/dm-oracle-adapter.rb → data_mapper/constraints/adapters/oracle_adapter.rb} +9 -2
  11. data/lib/{dm-constraints/adapters/dm-postgres-adapter.rb → data_mapper/constraints/adapters/postgres_adapter.rb} +1 -1
  12. data/lib/{dm-constraints/adapters/dm-sqlite-adapter.rb → data_mapper/constraints/adapters/sqlite_adapter.rb} +4 -0
  13. data/lib/{dm-constraints/adapters/dm-sqlserver-adapter.rb → data_mapper/constraints/adapters/sqlserver_adapter.rb} +1 -1
  14. data/lib/data_mapper/constraints/migrations/model.rb +36 -0
  15. data/lib/data_mapper/constraints/migrations/relationship.rb +42 -0
  16. data/lib/data_mapper/constraints/migrations/singleton_methods.rb +48 -0
  17. data/lib/data_mapper/constraints/relationship/many_to_many.rb +50 -0
  18. data/lib/data_mapper/constraints/relationship/one_to_many.rb +83 -0
  19. data/lib/data_mapper/constraints/resource.rb +34 -0
  20. data/lib/dm-constraints.rb +12 -88
  21. data/spec/integration/constraints_spec.rb +4 -2
  22. data/spec/isolated/require_spec.rb +7 -4
  23. metadata +79 -44
  24. data/lib/dm-constraints/adapters/dm-abstract-adapter.rb +0 -22
  25. data/lib/dm-constraints/delete_constraint.rb +0 -115
  26. data/lib/dm-constraints/migrations.rb +0 -60
  27. data/lib/dm-constraints/relationships.rb +0 -41
@@ -1,4 +1,4 @@
1
- require 'dm-constraints/adapters/dm-do-adapter'
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 intenal state like postgres or mysql
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)}
@@ -1,4 +1,4 @@
1
- require 'dm-constraints/adapters/dm-do-adapter'
1
+ require 'data_mapper/constraints/adapters/do_adapter'
2
2
 
3
3
  module DataMapper
4
4
  module Constraints
@@ -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
@@ -1,4 +1,4 @@
1
- require 'dm-constraints/adapters/dm-do-adapter'
1
+ require 'data_mapper/constraints/adapters/do_adapter'
2
2
 
3
3
  module DataMapper
4
4
  module Constraints
@@ -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
@@ -1,95 +1,19 @@
1
1
  require 'dm-core'
2
2
 
3
- require 'dm-constraints/migrations'
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
- module DataMapper
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
- extend Constraints::Migrations::SingletonMethods
9
+ require 'data_mapper/constraints/relationship/one_to_many'
10
+ require 'data_mapper/constraints/relationship/many_to_many'
11
11
 
12
- module Model
13
- extend DataMapper::Constraints::Migrations::Model
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
- include DeleteConstraint
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