themis 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f56efba984b1bd0789799d9dc814f19bbaa3fc17
4
+ data.tar.gz: 1849671bff67b2cf9a1fa3ed0e6a13721e08af5e
5
+ SHA512:
6
+ metadata.gz: 085e50ffaada519d96163830581c7c8c4e586edf2a4e97223cb649d41105c1e87fdde42f177a6b7353f19393de5c20d9c286841b012344bb0ee91066f06c3112
7
+ data.tar.gz: dc74d323204022de865f3f607e554d18065126e368b67129b6ae46377d979476aa9a43ebe95b473c888248a7bcc9999d32b7cebadee444f44313568de592f68f
data/README.markdown ADDED
@@ -0,0 +1,213 @@
1
+ # Themis
2
+
3
+ [![Build Status](https://secure.travis-ci.org/TMXCredit/themis.png)](http://travis-ci.org/TMXCredit/themis)
4
+ [![Dependency Status](https://gemnasium.com/TMXCredit/themis.png)](https://gemnasium.com/TMXCredit/themis)
5
+ [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/TMXCredit/themis)
6
+
7
+ Modular and switchable validations for ActiveRecord models.
8
+
9
+ ## Usage
10
+
11
+ ### Define validation module
12
+
13
+ ```ruby
14
+ module PersonValidation
15
+ extend Themis::Validation
16
+
17
+ validates_presence_of :first_name
18
+ validates_presence_of :last_name
19
+ end
20
+ ```
21
+
22
+ ### Mix validation modules
23
+
24
+ ```ruby
25
+ module UserValidation
26
+ extend Themis::Validation
27
+
28
+ # use all validators defined in PersonValidation
29
+ include PersonValidation
30
+
31
+ validates :email, :format => {:with /\A.*@.*\z/ }, :presence => true
32
+ validate_presence_of :spouse, :if => :married?
33
+ end
34
+ ```
35
+
36
+ ### ActiveRecord model
37
+
38
+ #### Including validation modules in models:
39
+
40
+ You can include a validation module in a ActiveRecord model to apply to the model all validators
41
+ defined in the module:
42
+
43
+ ```ruby
44
+ class User < ActiveRecord::Base
45
+ include UserValidation
46
+ end
47
+ ```
48
+
49
+ It's equivalent to:
50
+
51
+ ```ruby
52
+ class User < ActiveRecord::Base
53
+ validates_presence_of :first_name
54
+ validates_presence_of :last_name
55
+ validates :email, :format => {:with /\A.*@.*\z/ }, :presence => true
56
+ validate_presence_of :spouse, :if => :married?
57
+ end
58
+ ```
59
+
60
+ #### Using has\_validation and use\_validation methods
61
+
62
+ You can define a number of validator sets for a model using the `.has_validation` method. So you can
63
+ choose with the `#use_validation` method which validator set to use depending on the context.
64
+
65
+ ```ruby
66
+ class User < ActiveRecord::Base
67
+ has_validation :soft, PersonValidation
68
+ has_validation :hard, UserValidation
69
+ end
70
+
71
+ user = User.new
72
+ user.valid? # no validators are used
73
+ user.use_validation(:soft)
74
+ user.valid? # validate first_name and last_name
75
+ user.use_validation(:hard)
76
+ user.valid? # validate first_name, last_name, email and spouse(if user is married)
77
+
78
+ user.use_no_validation
79
+ user.valid? # no validators are used
80
+ ```
81
+
82
+ #### has\_validation syntax
83
+
84
+ ##### With module:
85
+
86
+ ```ruby
87
+ has_validation :soft, SoftValidation
88
+ ```
89
+
90
+ ##### With block:
91
+
92
+ ```ruby
93
+ has_validation :hard do |model|
94
+ # you can include validation module within block as well
95
+ model.include SoftValidation
96
+ model.validate_presence_of :email
97
+ end
98
+ ```
99
+
100
+ ##### With module and block:
101
+
102
+ ```ruby
103
+ # It's equivalent to the example above
104
+ has_validation :hard, SoftValidation do |model|
105
+ model.validate_presence_of :email
106
+ end
107
+ ```
108
+
109
+ ##### Multiple validations with one block or module:
110
+
111
+ ```ruby
112
+ # declare :soft and :hard validation
113
+ has_validation :soft, :hard, SoftValidation
114
+
115
+ # extended :hard validation
116
+ has_validation :hard do |model|
117
+ model.validate_presence_of :email
118
+ end
119
+ ```
120
+
121
+
122
+ ##### Option `:default`:
123
+
124
+ ```ruby
125
+ class User < ActiveRecord::Base
126
+ has_validation :soft, PersonValidation, :default => true
127
+ end
128
+
129
+ user = User.new
130
+ user.themis_validation # => :soft
131
+ ```
132
+
133
+ ##### Option `:nested`:
134
+
135
+ The `:nested` option causes the `use_validation` method to be called recursively on associations passed to it.
136
+ It receives a symbol or array of symbols with association names.
137
+
138
+ ```ruby
139
+ class User < ActiveRecord::Base
140
+ has_one :account
141
+ has_validation :soft, PersonValidation, :nested => :account
142
+ end
143
+
144
+ class Account < ActiveRecord::Base
145
+ has_validation :soft do |model|
146
+ model.validates_presence_of :nickname
147
+ end
148
+ end
149
+
150
+ user = User.first
151
+ user.themis_validation # => nil
152
+ user.account.themis_validation # => nil
153
+ user.use_validation(:soft)
154
+ user.themis_validation # => :soft
155
+ user.account.themis_validation # => :soft
156
+ ```
157
+
158
+ #### Using use\_nested\_validation\_on method
159
+
160
+ If you don't want to repeat yourself with the `:nested` option:
161
+
162
+ ```ruby
163
+ class User
164
+ has_validation :none, NoneValidation, :nested => [:accounts, :preferences, :info]
165
+ has_validation :soft, SoftValidation, :nested => [:accounts, :preferences, :info]
166
+ has_validation :hard, HardValidation, :nested => [:accounts, :preferences, :info]
167
+ end
168
+ ```
169
+
170
+ You can use `use_nested_validation_on` method:
171
+
172
+ ```ruby
173
+ class User
174
+ use_nested_validation_on :accounts, :preferences, :info
175
+ has_validation :none, NoneValidation
176
+ has_validation :soft, SoftValidation
177
+ has_validation :hard, HardValidation
178
+ end
179
+ ```
180
+
181
+ Also `use_nested_validation_on` supports deep nesting:
182
+
183
+ ```ruby
184
+ class User
185
+ use_nested_validation_on :preferences, :info => [:email, :history]
186
+ end
187
+ ```
188
+
189
+ # Running specs
190
+
191
+ To run specs:
192
+
193
+ ```
194
+ rake spec
195
+ ```
196
+
197
+ To verify test coverage use SimpleCov with Ruby 1.9.3:
198
+
199
+ ```
200
+ rvm use 1.9.3
201
+ bundle install
202
+ rake spec
203
+ $BROWSER ./coverage/index.html
204
+ ```
205
+
206
+
207
+ ## Credits
208
+
209
+ * [Potapov Sergey](https://github.com/greyblake)
210
+
211
+ ## Copyright
212
+
213
+ Copyright (c) 2013 TMX Credit.
@@ -0,0 +1,25 @@
1
+ module Themis
2
+ module AR
3
+ # Extends ActiveRecord::Associations::Association
4
+ # Hooks load_target method with to process after_association_loaded callback.
5
+ module AssociationExtension
6
+ extend ActiveSupport::Concern
7
+
8
+ # Run original load_target method and process after_association_loaded
9
+ # callback.
10
+ def load_target_with_after_association_loaded(*args, &block)
11
+ result = load_target_without_after_association_loaded(*args, &block)
12
+
13
+ if callback = self.owner._after_association_loaded_callbacks[self.reflection.name]
14
+ callback.call(self)
15
+ end
16
+
17
+ result
18
+ end
19
+
20
+ included do
21
+ alias_method_chain :load_target, :after_association_loaded
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,115 @@
1
+ module Themis
2
+ module AR
3
+ # Extends ActiveRecord::Base to make it support has_validation and use_validation
4
+ # methods.
5
+ # It adds some class attributes to model:
6
+ # * themis_validation - name of validation, symbol or nil
7
+ # * themis_validation_sets - hash where key is symbol(validation name) and value is {ValidationSet}.
8
+ # * themis_default_validation - name of default validation.
9
+ # * themis_default_nested - default value for :nested option
10
+ module BaseExtension
11
+ extend ActiveSupport::Autoload
12
+
13
+ # :nodoc:
14
+ def self.included(base)
15
+ base.extend ClassMethods
16
+ base.send :include, InstanceMethods
17
+ base.send :include, Callbacks
18
+
19
+ base.class_eval(<<-eoruby, __FILE__, __LINE__+1)
20
+ attr_reader :themis_validation
21
+
22
+ class_attribute :themis_validation_sets
23
+ class_attribute :themis_default_validation
24
+ class_attribute :themis_default_nested
25
+
26
+ delegate :has_themis_validation?, :to => "self.class"
27
+ eoruby
28
+ end
29
+
30
+ # :nodoc:
31
+ module ClassMethods
32
+ # @overload has_validation(name, options, &block)
33
+ # Declare validation set using block
34
+ # @example
35
+ # has_validation :soft, :nested => :account, :default => true do |validation|
36
+ # validation.validates_presence_of :some_date
37
+ # end
38
+ # @param [Symbol] name name of validation set
39
+ # @param [Hash] options options: :default, :nested
40
+ # @param [Proc] block proc which receives {ModelProxy} and defines validators
41
+ # @option options [Boolean] :default make it validation be used by default
42
+ # @option options [Symbol, Array<Symbol>] :nested association which should be affected when validation {#use_validation} is called
43
+ #
44
+ # @overload has_validation(name_1, name_2, options, &block)
45
+ # Declare validation in 2 sets using a block:
46
+ # @example
47
+ # has_validation :soft, :hard :nested => :account, :default => true do |validation|
48
+ # validation.validates_presence_of :some_date
49
+ # end
50
+ #
51
+ # @overload has_validation(name, validation_module, options, &block)
52
+ # Declare validation set on model using {Themis::Validation validation module} or(and) block.
53
+ # @example
54
+ # has_validation :soft, SoftValidation, :default => true
55
+ # @param [Symbol] name name of validation set
56
+ # @param [Module] validation_module module extended by {Themis::Validation}.
57
+ # @param [Hash] options options: :default, :nested
58
+ # @param [Proc] block proc which receives {ModelProxy} and defines validators
59
+ # @option options [Boolean] :default make it validation be used by default
60
+ # @option options [Symbol, Array<Symbol>] :nested association which should be affect when validation {#use_validation} is called
61
+ def has_validation(*args_and_options, &block)
62
+ options = args_and_options.extract_options!
63
+ names, args = args_and_options.partition { |obj| obj.class.in?([String, Symbol]) }
64
+ validation_module = args.first
65
+ Themis::AR::HasValidationMethod.new(self, names, validation_module, options, block).execute!
66
+ end
67
+
68
+ # Verify that model has {ValidationSet validation set} with passed name.
69
+ # @param [Symbol] name name of validation set
70
+ def has_themis_validation?(name)
71
+ themis_validation_sets.keys.include?(name.to_sym)
72
+ end
73
+
74
+ # Set the default value of the +:nested+ option for validations.
75
+ # @example
76
+ # use_nested_validation_on :author
77
+ #
78
+ # @example
79
+ # use_nested_validation_on :author, :comments
80
+ #
81
+ # @example
82
+ # use_nested_validation_on :author => {:posts => :comments }
83
+ #
84
+ # @param [Array<Symbol>, Hash] args an association or associations which should be effected
85
+ def use_nested_validation_on(*args)
86
+ if themis_default_nested
87
+ warn "WARNING: default nested validation is already defined: " \
88
+ "`#{themis_default_nested.inspect}` on #{self}"
89
+ end
90
+
91
+ args = args.flatten
92
+ deep_nested = args.extract_options!
93
+ associations = args + deep_nested.keys
94
+
95
+ UseNestedValidationOnMethod.new(self, associations, deep_nested).execute
96
+ end
97
+ end # module ClassMethods
98
+
99
+ # :nodoc:
100
+ module InstanceMethods
101
+ # Switch validation.
102
+ # @param [Symbol] validation_name name of {ValidationSet validation set}
103
+ def use_validation(validation_name)
104
+ Themis::AR::UseValidationMethod.new(self, validation_name).execute!
105
+ end
106
+
107
+ # Do not use any of {ValidationSet validation sets}.
108
+ def use_no_validation
109
+ @themis_validation = nil
110
+ end
111
+ end # module InstanceMethods
112
+
113
+ end # module BaseExtension
114
+ end # module AR
115
+ end # module Themis
@@ -0,0 +1,40 @@
1
+ module Themis
2
+ module AR
3
+ # Adds after_association_loaded callback to ActiveRecord::Base
4
+ module Callbacks
5
+ # :nodoc:
6
+ def self.included(base)
7
+ base.extend ClassMethods
8
+
9
+ base.class_eval(<<-eoruby, __FILE__, __LINE__+1)
10
+ class_attribute :_after_association_loaded_callbacks
11
+ self._after_association_loaded_callbacks = {}
12
+ eoruby
13
+ end
14
+
15
+ # :nodoc:
16
+ module ClassMethods
17
+ # Save callback in appropriate callback collection
18
+ #
19
+ # @example
20
+ # class User < ActiveRecord::Base
21
+ # has_many :accounts
22
+ #
23
+ # # List accounts after loading
24
+ # after_association_loaded(:accounts) do |association|
25
+ # association.target.each do |account|
26
+ # puts account.inspect
27
+ # end
28
+ # end
29
+ # end
30
+ #
31
+ # @param [Symbol] association_name association name as a symbol
32
+ # @yield [ActiveRecord::Associations::Association] a block which receives association
33
+ def after_association_loaded(association_name, &block)
34
+ self._after_association_loaded_callbacks[association_name] = block
35
+ end
36
+ end
37
+
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,108 @@
1
+ module Themis
2
+ module AR
3
+ # Encapsulates implementation of
4
+ # {AR::BaseExtension::ClassMethods#has_validation has_validation} method.
5
+ class HasValidationMethod
6
+ # @param [ActiveRecord::Base] model_class
7
+ # @param [Symbol] names names of validation sets
8
+ # @param [Module, nil] validation_module
9
+ # @param [Hash, nil] options
10
+ # @param [Proc, nil] block
11
+ def initialize(model_class, names, validation_module, options, block)
12
+ @model_class = model_class
13
+ @names = names
14
+ @module = validation_module
15
+ @default = options[:default] || false
16
+ @nested = options[:nested]
17
+ @block = block
18
+ end
19
+
20
+ # Execute the method.
21
+ def execute!
22
+ preinitialize_model_class!
23
+ validate!
24
+ register_validation_sets!
25
+ add_conditional_validators!
26
+ add_after_initialize_hook! if @default
27
+ add_before_validation_hook!
28
+ end
29
+
30
+
31
+ # Unless themis_validation_sets and themis_default_validation
32
+ # is set, then set them. This is necessary to do in every inheritor
33
+ # of ActiveRecord::Base to avoid overriding values for the entire
34
+ # ActiveRecord::Base hierarchy.
35
+ def preinitialize_model_class!
36
+ @model_class.themis_validation_sets ||= {}
37
+ @model_class.themis_default_validation ||= nil
38
+ end
39
+ private :preinitialize_model_class!
40
+
41
+
42
+ # Add {ValidationSet validation sets} to themis_validation_sets collection.
43
+ def register_validation_sets!
44
+ @names.each do |name|
45
+ @model_class.themis_validation_sets[name] ||= ValidationSet.new(
46
+ :name => name,
47
+ :module => @module,
48
+ :default => @default,
49
+ :nested => @nested,
50
+ :block => @block
51
+ )
52
+ end
53
+ end
54
+ private :register_validation_sets!
55
+
56
+
57
+ # Add conditional validation to ActiveRecord model.
58
+ def add_conditional_validators!
59
+ # Define local variable to have ability to pass its value to lambda
60
+ validation_names = @names
61
+
62
+ condition = lambda { |obj| obj.themis_validation.in?(validation_names) }
63
+ model_proxy = ModelProxy.new(@model_class, condition)
64
+
65
+ if @module
66
+ @module.validators.each do |validator|
67
+ model_proxy.send(validator.name, *validator.args)
68
+ end
69
+ end
70
+ @block.call(model_proxy) if @block
71
+ end
72
+ private :add_conditional_validators!
73
+
74
+
75
+ # Add after_initialize hook to set default validation.
76
+ def add_after_initialize_hook!
77
+ if @names.size > 1
78
+ raise "Can not set default to multiple validations"
79
+ end
80
+
81
+ # Define local variable to have ability to pass its value to proc
82
+ validation_name = @names.first
83
+ @model_class.themis_default_validation = validation_name
84
+ @model_class.after_initialize { use_validation(validation_name) }
85
+ end
86
+ private :add_after_initialize_hook!
87
+
88
+ # Add before_validation hook to make all nested models use same
89
+ # validation set.
90
+ def add_before_validation_hook!
91
+ @model_class.before_validation do
92
+ themis_validation ? use_validation(themis_validation) : use_no_validation
93
+ end
94
+ end
95
+ private :add_before_validation_hook!
96
+
97
+ # Run validation to be sure that minimum of necessary parameters were passed.
98
+ def validate!
99
+ if @default && @model_class.themis_default_validation
100
+ warn "WARNING: validation `#{@model_class.themis_default_validation}` " \
101
+ "is already used as default on #{@model_class}"
102
+ end
103
+ end
104
+ private :validate!
105
+
106
+ end # class HasValidationMethod
107
+ end # module AR
108
+ end # module Themis
@@ -0,0 +1,82 @@
1
+ module Themis
2
+ module AR
3
+ # It wraps a model class to override validation parameters
4
+ # and add(update) :if option.
5
+ # Also it provides some DSL syntax to include validation modules.
6
+ #
7
+ # See where exactly it does its job:
8
+ # class User
9
+ # has_validation :soft do |validation|
10
+ # validation.class # => ModelProxy
11
+ # end
12
+ # end
13
+ class ModelProxy
14
+ # @param [ActiveRecord::Base] model_class
15
+ # @param [Proc] condition lambda which is passed as :if option for conditional validation
16
+ def initialize(model_class, condition)
17
+ @model_class = model_class
18
+ @condition = condition
19
+ end
20
+
21
+ # Defines conditional validations from module.
22
+ # @param [Themis::Validation] validation_module any module extended by {Themis::Validation}
23
+ def include(validation_module)
24
+ validation_module.validators.each do |validator|
25
+ cargs = args_with_condition(validator.args)
26
+ @model_class.send(validator.name, *cargs)
27
+ end
28
+ end
29
+
30
+ # Defines conditional validation by adding(updating) :if option
31
+ # to original method call.
32
+ def method_missing(*args)
33
+ cargs = args_with_condition(args)
34
+ @model_class.send(*cargs)
35
+ end
36
+
37
+
38
+ # Build new arguments with modified :if option to make validation
39
+ # conditional.
40
+ # @param [Array] arguments validation method and its options
41
+ def args_with_condition(arguments)
42
+ # Ala deep duplication to not override original array.
43
+ args = arguments.map { |v| v.is_a?(Symbol) ? v : v.dup }
44
+
45
+ if args.last.is_a?(Hash)
46
+ old_opts = args.pop
47
+ args << build_opts(old_opts)
48
+ else
49
+ args << build_opts
50
+ end
51
+
52
+ args
53
+ end
54
+
55
+ # Build options for validator with :if option to make validation conditional
56
+ # so it would be used depending on `@themis_validation` value.
57
+ # If :if option already was passed then merge lambdas to create new one and
58
+ # use it as :if option.
59
+ # @param [Hash] old_opts old validator options
60
+ def build_opts(old_opts = nil)
61
+ # define local variable so its value can be addressed in lambda
62
+ condition = @condition
63
+ new_opts = old_opts || {}
64
+
65
+ if old_opts && old_opts.has_key?(:if)
66
+ old_if = old_opts[:if]
67
+ final_condition = lambda do
68
+ instance_eval(&old_if) &&
69
+ instance_eval(&condition)
70
+ end
71
+ else
72
+ final_condition = condition
73
+ end
74
+
75
+ new_opts[:if] = final_condition
76
+ new_opts
77
+ end
78
+ private :build_opts
79
+
80
+ end # class ModelProxy
81
+ end # module AR
82
+ end # module Themis
@@ -0,0 +1,55 @@
1
+ module Themis
2
+ module AR
3
+ # Encapsulates {Themis::AR::BaseExtension#use_nested_validation_on} method
4
+ class UseNestedValidationOnMethod
5
+
6
+ # @param [ActiveRecord::Base] model base mdoel
7
+ # @param [Array<Symbol>] associations associations on base model
8
+ # @param [Hash] nested_associations deep nested associations
9
+ def initialize(model, associations, nested_associations)
10
+ @model = model
11
+ @associations = associations
12
+ @nested_associations = nested_associations
13
+ end
14
+
15
+ # Trigger calling use_nested_validation_on on associations and adds
16
+ # after_association_loaded hooks.
17
+ def execute
18
+ # Set themis_default_nested for current model
19
+ @model.themis_default_nested = @associations unless @associations.empty?
20
+
21
+ process_nested_validations
22
+ add_after_association_loaded_hooks
23
+ end
24
+
25
+ # Iterate over associations and recursively call #use_nested_validation_on
26
+ def process_nested_validations
27
+ @nested_associations.each do |association_name, nested|
28
+ reflection = @model.reflect_on_association(association_name)
29
+ model_class = reflection.class_name.constantize
30
+ model_class.use_nested_validation_on(nested)
31
+ end
32
+ end
33
+
34
+ # Add after_association_loaded hooks to associations so when association
35
+ # is loaded it would have same validation as base model.
36
+ def add_after_association_loaded_hooks
37
+ @associations.each do |association_name|
38
+ @model.after_association_loaded(association_name) do |association|
39
+ validation = association.owner.themis_validation
40
+ target = association.target
41
+
42
+ if validation
43
+ if target.respond_to?(:each)
44
+ target.each { |model| model.use_validation(validation) }
45
+ elsif target.is_a? ActiveRecord::Base
46
+ target.use_validation(validation) if validation
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,69 @@
1
+ module Themis
2
+ module AR
3
+ # It encapsulates # {AR::BaseExtension::InstanceMethods#use_validation use_validation}
4
+ # method.
5
+ # It makes a model and its nested associations use another validation.
6
+ class UseValidationMethod
7
+ # @param [ActiveRecord::Base] model instance of model
8
+ # @param [Symbol, String] new_name name of new validation set
9
+ def initialize(model, new_name)
10
+ @model = model
11
+ @new_name = new_name.to_sym
12
+
13
+ unless @model.has_themis_validation?(@new_name)
14
+ raise ArgumentError.new("Unknown validation: `#{@new_name.inspect}` for #{model.class}")
15
+ end
16
+
17
+ @new_validation_set = @model.themis_validation_sets[@new_name]
18
+ end
19
+
20
+ # Affect model and its nested associations.
21
+ # Make them use the new validation set by assigning `@themis_validation`
22
+ # instance variable.
23
+ def execute!
24
+ # NOTE: It breaks encapsulation of ActiveRecord model.
25
+ # We do it because we don't wanna public "themis_validation=" method.
26
+ # -- sergey.potapov 2012-08-14
27
+ @model.instance_variable_set("@themis_validation", @new_name)
28
+
29
+ nested = @new_validation_set.nested || @model.class.themis_default_nested
30
+ if nested
31
+ association_names = Array.wrap(nested)
32
+ affect_associations(association_names)
33
+ end
34
+ end
35
+
36
+
37
+ # Make associations use new validation set.
38
+ # @param [Array<Symbol>] association_names names of associations
39
+ def affect_associations(association_names)
40
+ association_names.each { |name| affect_association(name) }
41
+ end
42
+ private :affect_associations
43
+
44
+ # Make an association use new validation set.
45
+ # Affect associations that are already loaded.
46
+ # @param [Symbol] association_name
47
+ def affect_association(association_name)
48
+ unless @model.reflections.has_key?(association_name)
49
+ raise("`#{association_name}` is not an association on #{@model.class}")
50
+ end
51
+
52
+ association = @model.association(association_name)
53
+ return if (!association.loaded? && !@model.new_record?)
54
+
55
+ target = association.target
56
+ case target
57
+ when Array, ActiveRecord::Associations::CollectionProxy
58
+ target.each {|obj| obj.send(:use_validation, @new_name) }
59
+ when ActiveRecord::Base
60
+ target.send(:use_validation, @new_name)
61
+ else
62
+ # do nothing
63
+ end
64
+ end
65
+ private :affect_association
66
+
67
+ end # class UseValidationMethod
68
+ end # module AR
69
+ end # module Themis
@@ -0,0 +1,14 @@
1
+ module Themis
2
+ module AR
3
+
4
+ # Used to store options about validation sets on every single ActiveRecord model class.
5
+ class ValidationSet < Struct.new(:name, :module, :default, :nested, :block)
6
+ # Redefine `new` to initialize structure with hash
7
+ def initialize(hash)
8
+ members = self.class.members.map!(&:to_sym)
9
+ super(*hash.values_at(*members))
10
+ end
11
+ end
12
+
13
+ end # module AR
14
+ end # module Themis
data/lib/themis/ar.rb ADDED
@@ -0,0 +1,16 @@
1
+ module Themis
2
+ # All stuff related to ActiveRecord.
3
+ # Mainly it provides {AR::BaseExtension} to extend ActiveRecord::Base.
4
+ module AR
5
+ extend ActiveSupport::Autoload
6
+
7
+ autoload :Callbacks
8
+ autoload :AssociationExtension
9
+ autoload :BaseExtension
10
+ autoload :ModelProxy
11
+ autoload :ValidationSet
12
+ autoload :HasValidationMethod
13
+ autoload :UseValidationMethod
14
+ autoload :UseNestedValidationOnMethod
15
+ end # module AR
16
+ end # module Themis
@@ -0,0 +1,24 @@
1
+ module Themis
2
+ # :nodoc
3
+ class Engine < Rails::Engine
4
+
5
+ initializer 'themis' do
6
+ ActiveSupport.on_load(:active_record) do
7
+
8
+ ::ActiveRecord::Associations::CollectionAssociation.class_eval do
9
+ include Themis::AR::AssociationExtension
10
+ end
11
+
12
+ ::ActiveRecord::Associations::Association.class_eval do
13
+ include Themis::AR::AssociationExtension
14
+ end
15
+
16
+ ::ActiveRecord::Base.class_eval do
17
+ include Themis::AR::BaseExtension
18
+ end
19
+
20
+ end # on_load
21
+ end # initializer
22
+
23
+ end # Engine
24
+ end # Themis
@@ -0,0 +1,18 @@
1
+ module Themis
2
+ module Validation
3
+
4
+ # Simple structure to store information about methods called on
5
+ # {Themis::Validation validation module}. It saves name and arguments
6
+ # of validation method in order to apply it on model later.
7
+ class Validator
8
+ attr_reader :name, :args
9
+
10
+ # @param [Symbol, String] name validation method name, e.g. "validates_presence_of"
11
+ # @param [Array] args arguments of method
12
+ def initialize(name, args)
13
+ @name, @args = name, args
14
+ end
15
+ end
16
+
17
+ end # Validation
18
+ end # Themis
@@ -0,0 +1,58 @@
1
+ module Themis
2
+ # Extends other modules to make them be validation modules.
3
+ # Consider it as the "parent module" for all validation modules.
4
+ #
5
+ # @example define UserValidation
6
+ #
7
+ # module UserValidation
8
+ # extend Themis::Validation
9
+ #
10
+ # validates :email , :presence => true
11
+ # validates :nickname, :presence => true
12
+ # end
13
+ module Validation
14
+ extend ActiveSupport::Autoload
15
+
16
+ autoload :Validator
17
+
18
+ # Array {Validator validators} defined in module.
19
+ # @return [Array<Validator>] array of module's validators
20
+ def validators
21
+ @validators ||= []
22
+ end
23
+
24
+ # When included in another module: copy {Validator validators} to another module.
25
+ # When included in ActiveRecord model: define validators on model.
26
+ # @param [Module, ActiveRecord::Base] base another validation module or ActiveRecord model.
27
+ def included(base)
28
+ if base.instance_of?(Module) && base.respond_to?(:validators)
29
+ base.validators.concat(validators)
30
+ elsif base.ancestors.include? ::ActiveRecord::Base
31
+ apply_to_model!(base)
32
+ else
33
+ raise "Validation module `#{self.inspect}` can be included only in another validation module or in ActiveRecord model"
34
+ end
35
+ end
36
+
37
+ # Save all calls of validation methods as array of validators
38
+ def method_missing(method_name, *args)
39
+ if method_name.to_s =~ /\Avalidate/
40
+ self.validators << Themis::Validation::Validator.new(method_name, args)
41
+ else
42
+ super
43
+ end
44
+ end
45
+ private :method_missing
46
+
47
+ # Add validators to model
48
+ # @param [ActiveRecord::Base] model_class
49
+ def apply_to_model!(model_class)
50
+ validators.each do |validator|
51
+ method, args = validator.name, validator.args
52
+ model_class.send(method, *args)
53
+ end
54
+ end
55
+ private :apply_to_model!
56
+
57
+ end # module Validation
58
+ end # module Themis
@@ -0,0 +1,4 @@
1
+ module Themis
2
+ # :nodoc:
3
+ VERSION = "0.1.0"
4
+ end
data/lib/themis.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'themis/engine'
2
+
3
+ # Extends ActiveRecord to provide switchable validations.
4
+ module Themis
5
+ extend ActiveSupport::Autoload
6
+
7
+ autoload :Validation
8
+ autoload :AR
9
+ end
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: themis
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - TMX Credit
8
+ - Potapov Sergey
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-08-16 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rails
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ~>
19
+ - !ruby/object:Gem::Version
20
+ version: '3.2'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ~>
26
+ - !ruby/object:Gem::Version
27
+ version: '3.2'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rspec-rails
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ~>
33
+ - !ruby/object:Gem::Version
34
+ version: '2.11'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ~>
40
+ - !ruby/object:Gem::Version
41
+ version: '2.11'
42
+ - !ruby/object:Gem::Dependency
43
+ name: sqlite3
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - '>='
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: pry
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: jeweler
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ~>
75
+ - !ruby/object:Gem::Version
76
+ version: '1.8'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ~>
82
+ - !ruby/object:Gem::Version
83
+ version: '1.8'
84
+ - !ruby/object:Gem::Dependency
85
+ name: yard
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - '>='
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - '>='
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ description: Flexible and modular validations for ActiveRecord models
99
+ email:
100
+ - rubygems@tmxcredit.com
101
+ - blake131313@gmail.com
102
+ executables: []
103
+ extensions: []
104
+ extra_rdoc_files:
105
+ - README.markdown
106
+ files:
107
+ - README.markdown
108
+ - lib/themis.rb
109
+ - lib/themis/ar.rb
110
+ - lib/themis/ar/association_extension.rb
111
+ - lib/themis/ar/base_extension.rb
112
+ - lib/themis/ar/callbacks.rb
113
+ - lib/themis/ar/has_validation_method.rb
114
+ - lib/themis/ar/model_proxy.rb
115
+ - lib/themis/ar/use_nested_validation_on_method.rb
116
+ - lib/themis/ar/use_validation_method.rb
117
+ - lib/themis/ar/validation_set.rb
118
+ - lib/themis/engine.rb
119
+ - lib/themis/validation.rb
120
+ - lib/themis/validation/validator.rb
121
+ - lib/themis/version.rb
122
+ homepage:
123
+ licenses: []
124
+ metadata: {}
125
+ post_install_message:
126
+ rdoc_options: []
127
+ require_paths:
128
+ - lib
129
+ required_ruby_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ required_rubygems_version: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - '>='
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ requirements: []
140
+ rubyforge_project:
141
+ rubygems_version: 2.0.3
142
+ signing_key:
143
+ specification_version: 4
144
+ summary: Flexible and modular validations for ActiveRecord models
145
+ test_files: []