usecasing_validations 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +19 -0
  3. data/.rspec +3 -0
  4. data/.rvmrc +1 -0
  5. data/Gemfile +4 -0
  6. data/Gemfile.lock +40 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +1 -0
  9. data/Rakefile +6 -0
  10. data/lib/usecasing/depends_all.rb +21 -0
  11. data/lib/usecasing/validator.rb +31 -0
  12. data/lib/usecasing_validations/custom_validator.rb +21 -0
  13. data/lib/usecasing_validations/each_validator.rb +32 -0
  14. data/lib/usecasing_validations/errors.rb +364 -0
  15. data/lib/usecasing_validations/helpers.rb +50 -0
  16. data/lib/usecasing_validations/target.rb +44 -0
  17. data/lib/usecasing_validations/validations/format.rb +116 -0
  18. data/lib/usecasing_validations/validations/helper_methods.rb +16 -0
  19. data/lib/usecasing_validations/validations/length.rb +126 -0
  20. data/lib/usecasing_validations/validations/presence.rb +17 -0
  21. data/lib/usecasing_validations/validations/uniqueness.rb +56 -0
  22. data/lib/usecasing_validations/validator.rb +20 -0
  23. data/lib/usecasing_validations/version.rb +3 -0
  24. data/lib/usecasing_validations.rb +122 -0
  25. data/spec/spec_helper.rb +17 -0
  26. data/spec/support/models/ruby_post.rb +9 -0
  27. data/spec/support/models/ruby_post_with_comments.rb +28 -0
  28. data/spec/support/usecases/validate_comments.rb +34 -0
  29. data/spec/support/usecases/validate_comments_custom_target.rb +14 -0
  30. data/spec/support/usecases/validate_depends_all.rb +24 -0
  31. data/spec/support/usecases/validate_post.rb +18 -0
  32. data/spec/support/usecases/validate_post_clear_errors.rb +9 -0
  33. data/spec/support/usecases/validate_uniq_comments.rb +77 -0
  34. data/spec/usecasing/validate_comments_custom_target_spec.rb +36 -0
  35. data/spec/usecasing/validate_comments_spec.rb +77 -0
  36. data/spec/usecasing/validate_depends_all_spec.rb +22 -0
  37. data/spec/usecasing/validate_post_clear_errors_spec.rb +48 -0
  38. data/spec/usecasing/validates_uniqueness_of_spec.rb +124 -0
  39. data/usecasing_validations.gemspec +29 -0
  40. metadata +166 -0
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MGUyMzJjMTM2MDg0NWFhNzBhNjI2MWZjODc2NmUzYjA1MDlmMDI5Zg==
5
+ data.tar.gz: !binary |-
6
+ NWEyZTM5MTVlNjc3MThkYmUyOTg0NmE1MGUxYTA1ZjVmNzg2MTlkNQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ MmExMzUyN2U1OWI0NWUwOGVjOGY3YmI1NjMwMjhhYjcwN2RjYWM4ZWQ5MzE2
10
+ OTJkMjcyMmFiZjc5MmE1YWUwNWIxMjUzYzExZmVjZWIzZmUxMjQyZTk1ZDAy
11
+ N2I5ODk2MTBiODkxYWEwZTM1ODA1YzA5YjUwYWE3Y2YxNjRlNTg=
12
+ data.tar.gz: !binary |-
13
+ MzZhZTk0MWM4Y2IxZWQ1YzI3ZmRmNzcxMTY2MzRjZjAxNzE4Y2E4ZDliMjA1
14
+ NzIwOWQ0OTBhYmVkZjFmZDBlODc2MmU1Yjg3NTQ5MzFjOWVkZTZlNGUzZDhh
15
+ MmY1NzY1OGJiNjEzNjY0NGVjMzE0MDZmYmUyMDk3MDE3YTQ5MDY=
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp/*
18
+ *.DS_Store
19
+ db/test.sqlite3
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --tty
2
+ --color
3
+ --format documentation
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm 1.9.3@usecasing_validations --create
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in usecasing.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,40 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ usecasing_validations (0.5.0)
5
+ usecasing (= 0.1.4)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ coderay (1.1.0)
11
+ diff-lcs (1.2.5)
12
+ metaclass (0.0.2)
13
+ method_source (0.8.2)
14
+ mocha (1.0.0)
15
+ metaclass (~> 0.0.1)
16
+ pry (0.9.12.4)
17
+ coderay (~> 1.0)
18
+ method_source (~> 0.8)
19
+ slop (~> 3.4)
20
+ rake (10.1.1)
21
+ rspec (2.14.1)
22
+ rspec-core (~> 2.14.0)
23
+ rspec-expectations (~> 2.14.0)
24
+ rspec-mocks (~> 2.14.0)
25
+ rspec-core (2.14.7)
26
+ rspec-expectations (2.14.4)
27
+ diff-lcs (>= 1.1.3, < 2.0)
28
+ rspec-mocks (2.14.4)
29
+ slop (3.4.7)
30
+ usecasing (0.1.4)
31
+
32
+ PLATFORMS
33
+ ruby
34
+
35
+ DEPENDENCIES
36
+ mocha
37
+ pry
38
+ rake
39
+ rspec
40
+ usecasing_validations!
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Thiago Dantas
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1 @@
1
+ Nothing to add for now
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rspec/core/rake_task'
4
+
5
+ task :default => :test
6
+ RSpec::Core::RakeTask.new(:test)
@@ -0,0 +1,21 @@
1
+ module UseCase
2
+
3
+ class DependsAll < Base
4
+
5
+ private ######################### PRIVATE ######################
6
+
7
+ def self.tx(execution_order, context)
8
+ ctx = Context.new(context)
9
+ executed = []
10
+ execution_order.each do |usecase|
11
+ # break unless ctx.success?
12
+ executed.push(usecase)
13
+ yield usecase, ctx
14
+ end
15
+ rollback(executed, ctx) unless ctx.success?
16
+ ctx
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,31 @@
1
+ module UseCase
2
+
3
+ class Validator < Base
4
+
5
+ include UseCaseValidations
6
+
7
+ def perform
8
+ targets = [*target]
9
+
10
+ if target.nil?
11
+ all_validations_green = false
12
+
13
+ elsif targets.empty?
14
+ all_validations_green = true
15
+
16
+ else
17
+ all_validations_green = targets.map do |object_to_validate|
18
+ if Helpers._marked_for_destruction?(object_to_validate)
19
+ true
20
+ else
21
+ valid?(object_to_validate)
22
+ end
23
+ end.all?
24
+ end
25
+
26
+ failure(self.class.to_s.downcase.to_sym, :failed) unless all_validations_green
27
+ end
28
+
29
+ end
30
+
31
+ end
@@ -0,0 +1,21 @@
1
+ module UseCaseValidations
2
+
3
+ class CustomValidator < Validator
4
+
5
+ attr_reader :methods
6
+
7
+ def initialize(args)
8
+ options = Helpers._extract_options!(args)
9
+ @methods = args
10
+ super(options)
11
+ end
12
+
13
+ def validate(record)
14
+ [*methods].map do |method|
15
+ base.send(method, record)
16
+ end.all?
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,32 @@
1
+ module UseCaseValidations
2
+
3
+ class EachValidator < Validator
4
+
5
+ attr_reader :attributes
6
+
7
+ def initialize(options)
8
+ @attributes = Array(options.delete(:attributes))
9
+ raise ArgumentError, ":attributes cannot be blank" if @attributes.empty?
10
+ super
11
+ check_validity!
12
+ end
13
+
14
+ def validate(record)
15
+ attributes.each do |attribute|
16
+ value = record.respond_to?(attribute) ? record.send(attribute) : nil
17
+ next if (value.nil? && options[:allow_nil]) || (Helpers._blank?(value) && options[:allow_blank])
18
+ validate_each(record, attribute, value)
19
+ end
20
+ end
21
+
22
+ # Override this method in subclasses with the validation logic, adding
23
+ # errors to the records +errors+ array where necessary.
24
+ def validate_each(record, attribute, value)
25
+ raise NotImplementedError, "Subclasses must implement a validate_each(record, attribute, value) method"
26
+ end
27
+
28
+ def check_validity!; end
29
+
30
+ end
31
+
32
+ end
@@ -0,0 +1,364 @@
1
+ module UseCaseValidations
2
+
3
+ class Errors
4
+ include Enumerable
5
+
6
+ CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank, :strict]
7
+
8
+ attr_reader :messages
9
+
10
+ # Pass in the instance of the object that is using the errors object.
11
+ #
12
+ # class Person
13
+ # def initialize
14
+ # @errors = ActiveModel::Errors.new(self)
15
+ # end
16
+ # end
17
+ def initialize(base)
18
+ @base = base
19
+ @messages = {}
20
+ end
21
+
22
+ def initialize_dup(other) # :nodoc:
23
+ @messages = other.messages.dup
24
+ super
25
+ end
26
+
27
+ # Clear the error messages.
28
+ #
29
+ # person.errors.full_messages # => ["name can not be nil"]
30
+ # person.errors.clear
31
+ # person.errors.full_messages # => []
32
+ def clear
33
+ messages.clear
34
+ end
35
+
36
+ # Returns +true+ if the error messages include an error for the given key
37
+ # +attribute+, +false+ otherwise.
38
+ #
39
+ # person.errors.messages # => {:name=>["can not be nil"]}
40
+ # person.errors.include?(:name) # => true
41
+ # person.errors.include?(:age) # => false
42
+ def include?(attribute)
43
+ (v = messages[attribute]) && v.any?
44
+ end
45
+ # aliases include?
46
+ alias :has_key? :include?
47
+
48
+ # Get messages for +key+.
49
+ #
50
+ # person.errors.messages # => {:name=>["can not be nil"]}
51
+ # person.errors.get(:name) # => ["can not be nil"]
52
+ # person.errors.get(:age) # => nil
53
+ def get(key)
54
+ messages[key]
55
+ end
56
+
57
+ # Set messages for +key+ to +value+.
58
+ #
59
+ # person.errors.get(:name) # => ["can not be nil"]
60
+ # person.errors.set(:name, ["can't be nil"])
61
+ # person.errors.get(:name) # => ["can't be nil"]
62
+ def set(key, value)
63
+ messages[key] = value
64
+ end
65
+
66
+ # Delete messages for +key+. Returns the deleted messages.
67
+ #
68
+ # person.errors.get(:name) # => ["can not be nil"]
69
+ # person.errors.delete(:name) # => ["can not be nil"]
70
+ # person.errors.get(:name) # => nil
71
+ def delete(key)
72
+ messages.delete(key)
73
+ end
74
+
75
+ # When passed a symbol or a name of a method, returns an array of errors
76
+ # for the method.
77
+ #
78
+ # person.errors[:name] # => ["can not be nil"]
79
+ # person.errors['name'] # => ["can not be nil"]
80
+ def [](attribute)
81
+ get(attribute.to_sym) || set(attribute.to_sym, [])
82
+ end
83
+
84
+ # Adds to the supplied attribute the supplied error message.
85
+ #
86
+ # person.errors[:name] = "must be set"
87
+ # person.errors[:name] # => ['must be set']
88
+ def []=(attribute, error)
89
+ self[attribute] << error
90
+ end
91
+
92
+ # Iterates through each error key, value pair in the error messages hash.
93
+ # Yields the attribute and the error for that attribute. If the attribute
94
+ # has more than one error message, yields once for each error message.
95
+ #
96
+ # person.errors.add(:name, "can't be blank")
97
+ # person.errors.each do |attribute, error|
98
+ # # Will yield :name and "can't be blank"
99
+ # end
100
+ #
101
+ # person.errors.add(:name, "must be specified")
102
+ # person.errors.each do |attribute, error|
103
+ # # Will yield :name and "can't be blank"
104
+ # # then yield :name and "must be specified"
105
+ # end
106
+ def each
107
+ messages.each_key do |attribute|
108
+ self[attribute].each { |error| yield attribute, error }
109
+ end
110
+ end
111
+
112
+ # Returns the number of error messages.
113
+ #
114
+ # person.errors.add(:name, "can't be blank")
115
+ # person.errors.size # => 1
116
+ # person.errors.add(:name, "must be specified")
117
+ # person.errors.size # => 2
118
+ def size
119
+ values.flatten.size
120
+ end
121
+
122
+ # Returns all message values.
123
+ #
124
+ # person.errors.messages # => {:name=>["can not be nil", "must be specified"]}
125
+ # person.errors.values # => [["can not be nil", "must be specified"]]
126
+ def values
127
+ messages.values
128
+ end
129
+
130
+ # Returns all message keys.
131
+ #
132
+ # person.errors.messages # => {:name=>["can not be nil", "must be specified"]}
133
+ # person.errors.keys # => [:name]
134
+ def keys
135
+ messages.keys
136
+ end
137
+
138
+ # Returns an array of error messages, with the attribute name included.
139
+ #
140
+ # person.errors.add(:name, "can't be blank")
141
+ # person.errors.add(:name, "must be specified")
142
+ # person.errors.to_a # => ["name can't be blank", "name must be specified"]
143
+ def to_a
144
+ full_messages
145
+ end
146
+
147
+ # Returns the number of error messages.
148
+ #
149
+ # person.errors.add(:name, "can't be blank")
150
+ # person.errors.count # => 1
151
+ # person.errors.add(:name, "must be specified")
152
+ # person.errors.count # => 2
153
+ def count
154
+ to_a.size
155
+ end
156
+
157
+ # Returns +true+ if no errors are found, +false+ otherwise.
158
+ # If the error message is a string it can be empty.
159
+ #
160
+ # person.errors.full_messages # => ["name can not be nil"]
161
+ # person.errors.empty? # => false
162
+ def empty?
163
+ all? { |k, v| v && v.empty? && !v.is_a?(String) }
164
+ end
165
+ # aliases empty?
166
+ alias_method :blank?, :empty?
167
+
168
+ # Returns an xml formatted representation of the Errors hash.
169
+ #
170
+ # person.errors.add(:name, "can't be blank")
171
+ # person.errors.add(:name, "must be specified")
172
+ # person.errors.to_xml
173
+ # # =>
174
+ # # <?xml version=\"1.0\" encoding=\"UTF-8\"?>
175
+ # # <errors>
176
+ # # <error>name can't be blank</error>
177
+ # # <error>name must be specified</error>
178
+ # # </errors>
179
+ def to_xml(options={})
180
+ to_a.to_xml({ root: "errors", skip_types: true }.merge!(options))
181
+ end
182
+
183
+ # Returns a Hash that can be used as the JSON representation for this
184
+ # object. You can pass the <tt>:full_messages</tt> option. This determines
185
+ # if the json object should contain full messages or not (false by default).
186
+ #
187
+ # person.as_json # => {:name=>["can not be nil"]}
188
+ # person.as_json(full_messages: true) # => {:name=>["name can not be nil"]}
189
+ def as_json(options=nil)
190
+ to_hash(options && options[:full_messages])
191
+ end
192
+
193
+ # Returns a Hash of attributes with their error messages. If +full_messages+
194
+ # is +true+, it will contain full messages (see +full_message+).
195
+ #
196
+ # person.to_hash # => {:name=>["can not be nil"]}
197
+ # person.to_hash(true) # => {:name=>["name can not be nil"]}
198
+ def to_hash(full_messages = false)
199
+ if full_messages
200
+ messages = {}
201
+ self.messages.each do |attribute, array|
202
+ messages[attribute] = array.map { |message| full_message(attribute, message) }
203
+ end
204
+ messages
205
+ else
206
+ self.messages.dup
207
+ end
208
+ end
209
+
210
+ # Adds +message+ to the error messages on +attribute+. More than one error
211
+ # can be added to the same +attribute+. If no +message+ is supplied,
212
+ # <tt>:invalid</tt> is assumed.
213
+ #
214
+ # person.errors.add(:name)
215
+ # # => ["is invalid"]
216
+ # person.errors.add(:name, 'must be implemented')
217
+ # # => ["is invalid", "must be implemented"]
218
+ #
219
+ # person.errors.messages
220
+ # # => {:name=>["must be implemented", "is invalid"]}
221
+ #
222
+ # If +message+ is a symbol, it will be translated using the appropriate
223
+ # scope (see +generate_message+).
224
+ #
225
+ # If +message+ is a proc, it will be called, allowing for things like
226
+ # <tt>Time.now</tt> to be used within an error.
227
+ #
228
+ # person.errors.messages # => {}
229
+ def add(attribute, message = nil, options = {})
230
+ message = normalize_message(attribute, message, options)
231
+ self[attribute] << message
232
+ end
233
+
234
+ # Returns +true+ if an error on the attribute with the given message is
235
+ # present, +false+ otherwise. +message+ is treated the same as for +add+.
236
+ #
237
+ # person.errors.add :name, :blank
238
+ # person.errors.added? :name, :blank # => true
239
+ def added?(attribute, message = nil, options = {})
240
+ message = normalize_message(attribute, message, options)
241
+ self[attribute].include? message
242
+ end
243
+
244
+ # Returns all the full error messages in an array.
245
+ #
246
+ # class Person
247
+ # validates_presence_of :name, :address, :email
248
+ # validates_length_of :name, in: 5..30
249
+ # end
250
+ #
251
+ # person = Person.create(address: '123 First St.')
252
+ # person.errors.full_messages
253
+ # # => ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Email can't be blank"]
254
+ def full_messages
255
+ map { |attribute, message| full_message(attribute, message) }
256
+ end
257
+
258
+ # Returns all the full error messages for a given attribute in an array.
259
+ #
260
+ # class Person
261
+ # validates_presence_of :name, :email
262
+ # validates_length_of :name, in: 5..30
263
+ # end
264
+ #
265
+ # person = Person.create()
266
+ # person.errors.full_messages_for(:name)
267
+ # # => ["Name is too short (minimum is 5 characters)", "Name can't be blank"]
268
+ def full_messages_for(attribute)
269
+ (get(attribute) || []).map { |message| full_message(attribute, message) }
270
+ end
271
+
272
+ # Returns a full message for a given attribute.
273
+ #
274
+ # person.errors.full_message(:name, 'is invalid') # => "Name is invalid"
275
+ def full_message(attribute, message)
276
+ return message if attribute == :base || !@base.class.respond_to?(:model_name)
277
+
278
+ attr_name = attribute.to_s.tr('.', '_').humanize
279
+ attr_name = @base.class.human_attribute_name(attribute, default: attr_name)
280
+ I18n.t(:"errors.format", {
281
+ default: "%{attribute} %{message}",
282
+ attribute: attr_name,
283
+ message: message
284
+ })
285
+ end
286
+
287
+ # Translates an error message in its default scope
288
+ # (<tt>activemodel.errors.messages</tt>).
289
+ #
290
+ # Error messages are first looked up in <tt>models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>,
291
+ # if it's not there, it's looked up in <tt>models.MODEL.MESSAGE</tt> and if
292
+ # that is not there also, it returns the translation of the default message
293
+ # (e.g. <tt>activemodel.errors.messages.MESSAGE</tt>). The translated model
294
+ # name, translated attribute name and the value are available for
295
+ # interpolation.
296
+ #
297
+ # When using inheritance in your models, it will check all the inherited
298
+ # models too, but only if the model itself hasn't been found. Say you have
299
+ # <tt>class Admin < User; end</tt> and you wanted the translation for
300
+ # the <tt>:blank</tt> error message for the <tt>title</tt> attribute,
301
+ # it looks for these translations:
302
+ #
303
+ # * <tt>activemodel.errors.models.admin.attributes.title.blank</tt>
304
+ # * <tt>activemodel.errors.models.admin.blank</tt>
305
+ # * <tt>activemodel.errors.models.user.attributes.title.blank</tt>
306
+ # * <tt>activemodel.errors.models.user.blank</tt>
307
+ # * any default you provided through the +options+ hash (in the <tt>activemodel.errors</tt> scope)
308
+ # * <tt>activemodel.errors.messages.blank</tt>
309
+ # * <tt>errors.attributes.title.blank</tt>
310
+ # * <tt>errors.messages.blank</tt>
311
+ def generate_message(attribute, type = :invalid, options = {})
312
+ type = options.delete(:message) if options[:message].is_a?(Symbol)
313
+
314
+ if !@base.class.respond_to?(:model_name)
315
+ return options.key?(:message) ? options[:message] : type
316
+ end
317
+
318
+ if @base.class.respond_to?(:i18n_scope)
319
+ defaults = @base.class.lookup_ancestors.map do |klass|
320
+ [ :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.attributes.#{attribute}.#{type}",
321
+ :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.#{type}" ]
322
+ end
323
+ else
324
+ defaults = []
325
+ end
326
+
327
+ defaults << options.delete(:message)
328
+ defaults << :"#{@base.class.i18n_scope}.errors.messages.#{type}" if @base.class.respond_to?(:i18n_scope)
329
+ defaults << :"errors.attributes.#{attribute}.#{type}"
330
+ defaults << :"errors.messages.#{type}"
331
+
332
+ defaults.compact!
333
+ defaults.flatten!
334
+
335
+ key = defaults.shift
336
+ value = (attribute != :base ? @base.send(attribute) : nil)
337
+
338
+ options = {
339
+ default: defaults,
340
+ model: @base.class.model_name.human,
341
+ attribute: @base.class.human_attribute_name(attribute),
342
+ value: value
343
+ }.merge!(options)
344
+
345
+ I18n.translate(key, options)
346
+ end
347
+
348
+ private
349
+ def normalize_message(attribute, message, options)
350
+ message ||= :invalid
351
+
352
+ case message
353
+ when Symbol
354
+ generate_message(attribute, message, Helpers._except(options, *CALLBACKS_OPTIONS))
355
+ when Proc
356
+ message.call
357
+ else
358
+ message
359
+ end
360
+ end
361
+
362
+ end
363
+
364
+ end
@@ -0,0 +1,50 @@
1
+ module UseCaseValidations
2
+
3
+ module Helpers
4
+
5
+ extend self
6
+
7
+ def _blank?(object)
8
+ if object.is_a?(String)
9
+ object !~ /[^[:space:]]/
10
+ else
11
+ object.respond_to?(:empty?) ? object.empty? : !object
12
+ end
13
+ end
14
+
15
+ def _marked_for_destruction?(object)
16
+ object.respond_to?(:marked_for_destruction?) ? object.marked_for_destruction? : false
17
+ end
18
+
19
+ def _extract_options!(array)
20
+ if array.last.is_a?(Hash) && array.last.instance_of?(Hash)
21
+ array.pop
22
+ else
23
+ {}
24
+ end
25
+ end
26
+
27
+ def _symbolyze_keys(hash)
28
+ hash.keys.reduce({ }) do |acc, key|
29
+ acc[key.to_sym] = hash[key]
30
+ acc
31
+ end
32
+ end
33
+
34
+ def _except(hash, *keys)
35
+ _hash = hash.dup
36
+ keys.each { |key| _hash.delete(key) }
37
+ _hash
38
+ end
39
+
40
+ def _call_proc_or_method(base, proc_or_method, object = nil)
41
+ if object.nil?
42
+ proc_or_method.is_a?(Proc) ? base.instance_exec(&proc_or_method) : base.send(proc_or_method)
43
+ else
44
+ proc_or_method.is_a?(Proc) ? base.instance_exec(object, &proc_or_method) : base.send(proc_or_method, object)
45
+ end
46
+ end
47
+
48
+ end
49
+
50
+ end
@@ -0,0 +1,44 @@
1
+ module UseCaseValidations
2
+
3
+ module Target
4
+
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ def target_sym
10
+ self.class.target_sym
11
+ end
12
+
13
+ def parent_target_sym
14
+ self.class.options.key?(:in) ? self.class.options[:in] : nil
15
+ end
16
+
17
+ def target
18
+ send(target_sym)
19
+ end
20
+
21
+ def parent_target
22
+ parent_target_sym ? send(parent_target_sym) : nil
23
+ end
24
+
25
+ module ClassMethods
26
+
27
+ attr_reader :target_sym, :options
28
+
29
+ def target(target_sym, options = {})
30
+ @target_sym, @options = target_sym, options
31
+
32
+ if options.key?(:in)
33
+ define_method(options[:in]) { context.send(options[:in]) }
34
+ define_method(target_sym) { send(options[:in]).send(target_sym) }
35
+ else
36
+ define_method(target_sym) { context.send(target_sym) }
37
+ end
38
+ end
39
+
40
+ end
41
+
42
+ end
43
+
44
+ end