mv-core 0.1.0

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 (38) hide show
  1. data/LICENSE.txt +20 -0
  2. data/README.rdoc +88 -0
  3. data/lib/migration_validators/active_record/base.rb +24 -0
  4. data/lib/migration_validators/active_record/connection_adapters/abstract_adapter.rb +36 -0
  5. data/lib/migration_validators/active_record/connection_adapters/native_adapter.rb +118 -0
  6. data/lib/migration_validators/active_record/connection_adapters/table.rb +15 -0
  7. data/lib/migration_validators/active_record/connection_adapters/table_definition.rb +24 -0
  8. data/lib/migration_validators/active_record/migration.rb +29 -0
  9. data/lib/migration_validators/active_record/schema.rb +31 -0
  10. data/lib/migration_validators/active_record/schema_dumper.rb +24 -0
  11. data/lib/migration_validators/adapters/base.rb +15 -0
  12. data/lib/migration_validators/adapters/containers.rb +102 -0
  13. data/lib/migration_validators/adapters/routing.rb +104 -0
  14. data/lib/migration_validators/adapters/syntax.rb +53 -0
  15. data/lib/migration_validators/adapters/validator_definitions.rb +131 -0
  16. data/lib/migration_validators/core/adapter_wrapper.rb +88 -0
  17. data/lib/migration_validators/core/db_validator.rb +178 -0
  18. data/lib/migration_validators/core/statement_builder.rb +60 -0
  19. data/lib/migration_validators/core/validator_container.rb +110 -0
  20. data/lib/migration_validators/core/validator_definition.rb +91 -0
  21. data/lib/migration_validators/core/validator_router.rb +45 -0
  22. data/lib/mv-core.rb +100 -0
  23. data/lib/options.rb +7 -0
  24. data/spec/migration_validators/active_record/connection_adapters/abstract_adapter_spec.rb +440 -0
  25. data/spec/migration_validators/active_record/connection_adapters/table_definition_spec.rb +4 -0
  26. data/spec/migration_validators/active_record/migration.rb +82 -0
  27. data/spec/migration_validators/active_record/schema_dumper_spec.rb +44 -0
  28. data/spec/migration_validators/adapters/base_spec.rb +89 -0
  29. data/spec/migration_validators/core/adapter_wrapper_spec.rb +168 -0
  30. data/spec/migration_validators/core/db_validator_spec.rb +347 -0
  31. data/spec/migration_validators/core/statement_builder_spec.rb +36 -0
  32. data/spec/migration_validators/core/validator_container_spec.rb +121 -0
  33. data/spec/migration_validators/core/validator_definition_spec.rb +131 -0
  34. data/spec/migration_validators/core/validator_router_spec.rb +60 -0
  35. data/spec/mv-core_spec.rb +4 -0
  36. data/spec/spec_helper.rb +15 -0
  37. data/spec/support/factories/db_validator.rb +43 -0
  38. metadata +152 -0
@@ -0,0 +1,104 @@
1
+ module MigrationValidators
2
+ module Adapters
3
+ module Routing
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ ###################################################################
9
+ ## UNIQUENESS
10
+ ###################################################################
11
+ def validate_uniqueness_index validators
12
+ add_index validators.first
13
+ [validators.first]
14
+ end
15
+
16
+ def remove_validate_uniqueness_index validators
17
+ remove_index(validators.first)
18
+ [validators.first]
19
+ end
20
+
21
+ def validate_uniqueness validators
22
+ validate_uniqueness_index validators
23
+ end
24
+
25
+ def remove_validate_uniqueness validators
26
+ remove_validate_uniqueness_index validators
27
+ end
28
+
29
+ private
30
+
31
+ def compose_index_name validator
32
+ if validator.options.blank? || (index_name = validator.options[:index_name]).blank?
33
+ "idx_mgr_validates_#{validator.table_name}_#{validator.column_name}_#{validator.validator_name}"
34
+ else
35
+ index_name
36
+ end
37
+ end
38
+
39
+ def add_index validator
40
+ ::ActiveRecord::Base.connection.add_index validator.table_name,
41
+ validator.column_name,
42
+ :name => compose_index_name(validator),
43
+ :unique => true
44
+ end
45
+
46
+ def remove_index validator
47
+ ::ActiveRecord::Base.connection.remove_index validator.table_name,
48
+ :name => compose_index_name(validator)
49
+ end
50
+
51
+ def execute statements
52
+ statements = [statements] if statements.kind_of?(String)
53
+
54
+ statements.each {|stmt| ::ActiveRecord::Base.connection.execute(stmt) }
55
+ end
56
+
57
+ module ClassMethods
58
+ def routers to = nil
59
+ @routers ||= {}
60
+ end
61
+
62
+
63
+ def router validator_name, container_type, &block
64
+ router = routers["#{validator_name}_#{container_type}"] ||= MigrationValidators::Core::ValidatorRouter.new(containers)
65
+ router.instance_eval(&block) if block
66
+ router
67
+ end
68
+
69
+ def route validator_name, container_type, opts = {}, &block
70
+ router validator_name, container_type, &block
71
+
72
+ default = opts.delete(:default)
73
+ remove = opts.delete(:remove)
74
+ remove = true if remove.nil?
75
+
76
+ if (to = opts[:to])
77
+ to = [to] unless to.kind_of?(Array)
78
+ to.each {|container_name| router(validator_name, container_type).to container_name}
79
+ end
80
+
81
+ define_method :"validate_#{validator_name}_#{container_type}" do |validators|
82
+ execute(self.class.router(validator_name, container_type).add_validators(validators))
83
+ validators
84
+ end
85
+ alias_method(:"validate_#{validator_name}", :"validate_#{validator_name}_#{container_type}") if default
86
+
87
+ if remove
88
+ define_method :"remove_validate_#{validator_name}_#{container_type}" do |validators|
89
+ execute(self.class.router(validator_name, container_type).remove_validators(validators))
90
+ validators
91
+ end
92
+ alias_method(:"remove_validate_#{validator_name}", :"remove_validate_#{validator_name}_#{container_type}") if default
93
+ end
94
+ end
95
+
96
+ def clear_routing
97
+ public_instance_methods.grep(/^remove_validate_/) { |method_name| undef_method method_name }
98
+ public_instance_methods.grep(/^validate_/) { |method_name| undef_method method_name }
99
+ end
100
+ end
101
+
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,53 @@
1
+ module MigrationValidators
2
+ module Adapters
3
+ module Syntax
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ def define_base_syntax
10
+ syntax do
11
+ operation(:db_name)
12
+ operation(:db_value)
13
+ operation(:wrap) {|value| "(#{value})"}
14
+ operation(:and) {|stmt, value| "#{stmt} AND #{value}"}
15
+ operation(:or) {|stmt, value| "#{stmt} OR #{value}"}
16
+ operation(:length) {|value| "LENGTH(#{stmt})"}
17
+ operation(:trim) {|value| "TRIM(#{stmt})"}
18
+ operation(:coalesce) {|value| "COALESCE(#{stmt}, '')"}
19
+ operation(:if) {|if_stmt, then_stmt| "IF #{if_stmt} THEN #{then_stmt} ENDIF"}
20
+ operation(:regexp) {|stmt, value| "#{stmt} REGEXP #{value}"}
21
+ operation(:greater_than) {|stmt, value| "#{stmt} > #{value}"}
22
+ operation(:greater_or_equal_to) {|stmt, value| "#{stmt} >= #{value}"}
23
+ operation(:less_than) {|stmt, value| "#{stmt} < #{value}"}
24
+ operation(:less_or_equal_to) {|stmt, value| "#{stmt} <= #{value}"}
25
+ operation(:equal_to) {|stmt, value| "#{stmt} = #{value}"}
26
+ operation(:between) do |stmt, range|
27
+ "#{@stmt} >= #{compile(range.first).db_value} AND #{@stmt} <#{range.exclude_end? ? '' : '='} #{compile(range.last).db_value}"
28
+ end
29
+ operation(:in) do |stmt, array|
30
+ "#{@stmt} IN (#{array.collect{|value| compile(value).db_value}.join(', ')})"
31
+ end
32
+ operation(:not_in) do |stmt, array|
33
+ "#{@stmt} NOT IN (#{array.collect{|value| compile(value).db_value}.join(', ')})"
34
+ end
35
+ operation(:not_null) {|value| "#{value} IS NOT NULL"}
36
+ operation(:null) {|value| "#{value} IS NULL"}
37
+ operation(:not) {|value| "NOT #{value}"}
38
+ operation(:exists) {|value| "EXISTS #{value}"}
39
+ operation(:select) {|value| "SELECT #{value}"}
40
+ operation(:from) {|value| " FROM #{value}"}
41
+ operation(:where) {|value| "WHERE #{value}"}
42
+ end
43
+ end
44
+
45
+ def syntax &block
46
+ @builder ||= MigrationValidators::Core::StatementBuilder.new
47
+ @builder.instance_eval(&block) if block
48
+ @builder
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,131 @@
1
+ module MigrationValidators
2
+ module Adapters
3
+ module ValidatorDefinitions
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ def validators
10
+ @validators ||= {}
11
+ end
12
+
13
+ def validator name, opts = {}, &block
14
+ validator = validators[name] ||= MigrationValidators::Core::ValidatorDefinition.new(syntax)
15
+
16
+ validator.post :allow_nil => true do
17
+ self.wrap.or(column.db_name.null)
18
+ end if opts[:allow_nil] || !opts.key?(:allow_nil)
19
+
20
+ validator.post :allow_blank => true do
21
+ self.wrap.or(column.db_name.coalesce.trim.length.equal_to(0))
22
+ end if opts[:allow_blank] || !opts.key?(:allow_blank)
23
+
24
+ validator.instance_eval(&block) if block
25
+ end
26
+
27
+ def define_base_validators
28
+ validator :inclusion do
29
+ property :in do |value|
30
+ if value.kind_of?(Array)
31
+ column.db_name.not_null.and(column.db_name.in(value))
32
+ elsif value.kind_of?(Range)
33
+ column.db_name.not_null.and(db_name.between(value))
34
+ else
35
+ column.db_name.not_null.and(column.db_name.equal_to(compile(value).db_value))
36
+ end
37
+ end
38
+ end
39
+
40
+ validator :exclusion do
41
+ property :in do |value|
42
+ if value.kind_of?(Array)
43
+ column.db_name.not_in(value)
44
+ elsif value.kind_of?(Range)
45
+ column.db_name.between(value).wrap.not
46
+ else
47
+ column.db_name.equal_to(compile(value).db_value).not
48
+ end
49
+ end
50
+ end
51
+
52
+ validator :format do
53
+ property :with do |value|
54
+ column.db_name.not_null.and(column.db_name.regexp(compile(value).db_value))
55
+ end
56
+ end
57
+
58
+ validator :length do
59
+ property :is, :message => :wrong_length do |value|
60
+ column.db_name.coalesce.length.equal_to(compile(value).db_value)
61
+ end
62
+ property :maximum, :message => :too_long do |value|
63
+ column.db_name.coalesce.length.less_or_equal_to(compile(value).db_value)
64
+ end
65
+ property :minimum, :message => :too_short do |value|
66
+ column.db_name.coalesce.length.greater_or_equal_to(compile(value).db_value)
67
+ end
68
+
69
+ property :in do |value|
70
+ case value.class.name
71
+ when "Array" then column.db_name.coalesce.length.in(value)
72
+ when "Range" then column.db_name.coalesce.length.between(value)
73
+ else column.db_name.coalesce.length.equal_to(compile(value).db_value)
74
+ end
75
+ end
76
+
77
+ property :within do |value|
78
+ case value.class.name
79
+ when "Array" then column.db_name.coalesce.length.in(value)
80
+ when "Range" then column.db_name.coalesce.length.between(value)
81
+ else column.db_name.coalesce.length.equal_to(compile(value).db_value)
82
+ end
83
+ end
84
+ end
85
+
86
+ validator :size do
87
+ property :is, :message => :wrong_length do |value|
88
+ column.db_name.coalesce.length.equal_to(compile(value).db_value)
89
+ end
90
+ property :maximum, :message => :too_long do |value|
91
+ column.db_name.coalesce.length.less_or_equal_to(compile(value).db_value)
92
+ end
93
+ property :minimum, :message => :too_short do |value|
94
+ column.db_name.coalesce.length.greater_or_equal_to(compile(value).db_value)
95
+ end
96
+
97
+ property :in do |value|
98
+ case value.class.name
99
+ when "Array" then column.db_name.coalesce.length.in(value)
100
+ when "Range" then column.db_name.coalesce.length.between(value)
101
+ else column.db_name.coalesce.length.equal_to(compile(value).db_value)
102
+ end
103
+ end
104
+
105
+ property :within do |value|
106
+ case value.class.name
107
+ when "Array" then column.db_name.coalesce.length.in(value)
108
+ when "Range" then column.db_name.coalesce.length.between(value)
109
+ else column.db_name.coalesce.length.equal_to(compile(value).db_value)
110
+ end
111
+ end
112
+ end
113
+
114
+ validator :presense do
115
+ property do |value|
116
+ column.db_name.not_null.and(column.db_name.trim.length.greater_than(0))
117
+ end
118
+ end
119
+
120
+ validator :uniqueness do
121
+ property do |value|
122
+ "NOT EXISTS(SELECT #{column}
123
+ FROM #{validator.table_name}
124
+ WHERE (#{column.db_name} = #{column}))"
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,88 @@
1
+ module MigrationValidators
2
+ module Core
3
+ class AdapterWrapper
4
+ class AdapterCaller
5
+ def initialize adapter, action
6
+ @adapter = adapter
7
+ @action = action
8
+ end
9
+
10
+ def process validators
11
+ return if validators.blank?
12
+
13
+ check_validators(validators)
14
+
15
+ validators.group_by{|validator| [validator.table_name, compose_method_name(validator)]}.each do |group_id, group|
16
+
17
+ until group.blank?
18
+ handled_validators = @adapter.send(group_id.last, group)
19
+
20
+ break if handled_validators.blank?
21
+
22
+ group = group - handled_validators
23
+ end
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def compose_method_name validator
30
+ method_suffix = validator.options.blank? || validator.options[:as].blank? ? "" : "_#{validator.options[:as]}"
31
+ :"#{@action}_#{validator.validator_name}#{method_suffix}"
32
+ end
33
+
34
+ def grep_public_instance_methods expr
35
+ @adapter.class.public_instance_methods.collect{|method| method.to_s.match(expr)}.compact.collect{|match| match[1]}
36
+ end
37
+
38
+ def supported_validators
39
+ grep_public_instance_methods(/^#{@action}_([a-z]*)/).uniq
40
+ end
41
+
42
+ def supported_db_forms validator_name
43
+ grep_public_instance_methods(/^#{@action}_#{validator_name}_([a-z]*)/).uniq
44
+ end
45
+
46
+ def default_db_form_supported? validator_name
47
+ @adapter.class.method_defined? :"#{@action}_#{validator_name}"
48
+ end
49
+
50
+ def array2str array
51
+ "[" + array.collect {|elem| "'#{elem}'"}.join(',') + "]"
52
+ end
53
+
54
+ def check_validators validators
55
+ validators.each do |validator|
56
+
57
+ unless supported_validators.include?(validator.validator_name.to_s)
58
+ raise MigrationValidatorsException.new("Adapter '#{@adapter.name}'. 'Action '#{@action}' for '#{validator.validator_name}' is not supported. Available validators: #{array2str(supported_validators)}")
59
+ end
60
+
61
+ if validator.options && (db_form = validator.options[:as])
62
+ unless supported_db_forms(validator.validator_name).include?(db_form.to_s)
63
+ raise MigrationValidatorsException.new("Adapter '#{@adapter.name}'. Action '#{@action}' for db form '#{db_form}' for validator '#{validator.validator_name}' is not supported. Available db forms: #{array2str(supported_db_forms(validator.validator_name))}")
64
+ end
65
+ else
66
+ unless default_db_form_supported?(validator.validator_name.to_s)
67
+ raise MigrationValidatorsException.new("Adapter '#{@adapter.name}'. 'Action '#{@action}' for '#{validator.validator_name}' with default db form is not supported")
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ def initialize adapter
75
+ @creator = AdapterCaller.new adapter, :validate
76
+ @remover = AdapterCaller.new adapter, :remove_validate
77
+ end
78
+
79
+ def create_validators validators
80
+ @creator.process validators
81
+ end
82
+
83
+ def remove_validators validators
84
+ @remover.process validators
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,178 @@
1
+ module MigrationValidators
2
+ module Core
3
+ class DbValidator < ::ActiveRecord::Base
4
+ set_table_name MigrationValidators.migration_validators_table_name
5
+
6
+ validates_presence_of :table_name
7
+ validates_length_of :table_name, :maximum => 255
8
+ validates_length_of :column_name, :maximum => 255
9
+
10
+ validates_presence_of :validator_name
11
+ validates_length_of :validator_name, :maximum => 255
12
+
13
+ validate do |validator|
14
+ unless ::ActiveRecord::Base.connection.table_exists?(validator.table_name)
15
+ validator.errors[:table_name] << "table '#{table_name}' does not exist"
16
+ else
17
+ unless ::ActiveRecord::Base.connection.columns(validator.table_name).any?{|col| col.name == validator.column_name.to_s}
18
+ validator.errors[:column_name] << "column '#{column_name}' does not exist in the table '#{table_name}'"
19
+ end
20
+ end
21
+ end
22
+
23
+ before_save :prepare_options
24
+
25
+ serialize :options, Hash
26
+ serialize :constraints, Array
27
+
28
+ def name
29
+ "#{table_name}_#{column_name}_#{validator_name}"
30
+ end
31
+
32
+
33
+ def error_message
34
+ unless (options.blank? || (message = options[:message]).blank?)
35
+ message
36
+ else
37
+ "#{validator_name} violated for #{table_name} field #{column_name}"
38
+ end
39
+ end
40
+
41
+ def satisfies opts
42
+ return true if opts.blank?
43
+
44
+ opts.collect do |property_name, property_value|
45
+ [property_name, property_value.kind_of?(Array) ? property_value : [property_value]]
46
+ end.all? do |property_name, property_value|
47
+ property_value.include?(options[property_name])
48
+ end
49
+ end
50
+
51
+ def save_to_constraint constraint_name
52
+ self.constraints ||= []
53
+ self.constraints << constraint_name.to_s unless self.constraints.include?(constraint_name.to_s)
54
+ end
55
+
56
+ def remove_from_constraint constraint_name
57
+ self.constraints.delete(constraint_name.to_s) if self.constraints
58
+ end
59
+
60
+ def in_constraint? constraint_name
61
+ self.constraints && self.constraints.include?(constraint_name.to_s)
62
+ end
63
+
64
+ private
65
+ def prepare_options
66
+ options = options.inject({}) do |res, (key, value)|
67
+ res[key.to_s] = value
68
+ res
69
+ end unless options.blank?
70
+
71
+ true
72
+ end
73
+
74
+ class << self
75
+
76
+ def add_column_validator table_name, column_name, validator_name, opts
77
+ remove_validators(:table_name => table_name.to_s,
78
+ :column_name => column_name.to_s,
79
+ :validator_name => validator_name.to_s)
80
+
81
+ add_new_validator(:table_name => table_name.to_s,
82
+ :column_name => column_name.to_s,
83
+ :validator_name => validator_name.to_s,
84
+ :options => opts)
85
+ end
86
+
87
+ def remove_column_validator table_name, column_name, validator_name
88
+ remove_validators :table_name => table_name.to_s, :column_name => column_name.to_s, :validator_name => validator_name.to_s
89
+ end
90
+
91
+ def remove_column_validators table_name, column_name
92
+ remove_validators :table_name => table_name.to_s, :column_name => column_name.to_s
93
+ end
94
+
95
+ def table_validators table_name, opts = {}
96
+ with_options opts do
97
+ DbValidator.find(:all, :conditions => { :table_name => table_name.to_s })
98
+ end
99
+ end
100
+
101
+ def constraint_validators constraint
102
+ (DbValidator.find(:all, :conditions => ["constraints LIKE ?", "%#{constraint}%"]) + validators_to_add - validators_to_remove).select{|validator| validator.in_constraint?(constraint)}
103
+ end
104
+
105
+ def remove_table_validators table_name
106
+ remove_validators :table_name => table_name.to_s
107
+ end
108
+
109
+ def column_validators table_name, column_name, opts = {}
110
+ with_options opts do
111
+ DbValidator.find :all, :conditions => { :table_name => table_name.to_s, :column_name => column_name.to_s }
112
+ end
113
+ end
114
+
115
+ def rename_column table_name, old_column_name, new_column_name
116
+ DbValidator.update_all({:column_name => new_column_name.to_s}, :column_name => old_column_name.to_s)
117
+ end
118
+
119
+ def rename_table old_table_name, new_table_name
120
+ DbValidator.update_all({:table_name => new_table_name.to_s}, :table_name => old_table_name.to_s)
121
+ end
122
+
123
+ def commit adapter = nil
124
+ adapter.remove_validators(validators_to_remove.select{|validator| ::ActiveRecord::Base.connection.table_exists?(validator.table_name)}) if adapter
125
+ validators_to_remove.each(&:destroy)
126
+ validators_to_remove.clear
127
+
128
+ adapter.create_validators(validators_to_add) if adapter
129
+ validators_to_add.each(&:save!)
130
+ validators_to_add.clear
131
+ end
132
+
133
+ def rollback
134
+ validators_to_remove.clear
135
+ validators_to_add.clear
136
+ end
137
+
138
+
139
+ def clear_all
140
+ rollback
141
+ DbValidator.delete_all
142
+ end
143
+
144
+ def validators_to_remove
145
+ @validators_to_remove ||= []
146
+ end
147
+
148
+ def validators_to_add
149
+ @validators_to_add ||= []
150
+ end
151
+
152
+ private
153
+
154
+ def validators_to_remove
155
+ @validators_to_remove ||= []
156
+ end
157
+
158
+ def validators_to_add
159
+ @validators_to_add ||= []
160
+ end
161
+
162
+ def with_options opts, &block
163
+ block.call.select {|validator| validator.satisfies(opts) }
164
+ end
165
+
166
+ def add_new_validator opts
167
+ validators_to_add << new(opts)
168
+ end
169
+
170
+ def remove_validators opts
171
+ DbValidator.find(:all, :conditions => opts).each { |validator| validators_to_remove << validator }
172
+ end
173
+
174
+
175
+ end
176
+ end
177
+ end
178
+ end