aspect 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +23 -8
  3. data/README.md +41 -0
  4. data/Rakefile +53 -7
  5. data/VERSION +1 -1
  6. data/aspect.gemspec +12 -15
  7. data/lib/aspect/has_attributes.rb +31 -22
  8. data/lib/aspect/has_registry.rb +28 -0
  9. data/lib/aspect/message_transform.rb +97 -0
  10. data/lib/aspect/validator.rb +112 -0
  11. data/lib/aspect/verifier/check.rb +125 -0
  12. data/lib/aspect/verifier.rb +212 -0
  13. data/lib/aspect/version.rb +3 -0
  14. data/lib/aspect.rb +1 -2
  15. data/spec/coverage/assets/0.10.0/application.css +799 -0
  16. data/spec/coverage/assets/0.10.0/application.js +1707 -0
  17. data/spec/coverage/assets/0.10.0/colorbox/border.png +0 -0
  18. data/spec/coverage/assets/0.10.0/colorbox/controls.png +0 -0
  19. data/spec/coverage/assets/0.10.0/colorbox/loading.gif +0 -0
  20. data/spec/coverage/assets/0.10.0/colorbox/loading_background.png +0 -0
  21. data/spec/coverage/assets/0.10.0/favicon_green.png +0 -0
  22. data/spec/coverage/assets/0.10.0/favicon_red.png +0 -0
  23. data/spec/coverage/assets/0.10.0/favicon_yellow.png +0 -0
  24. data/spec/coverage/assets/0.10.0/loading.gif +0 -0
  25. data/spec/coverage/assets/0.10.0/magnify.png +0 -0
  26. data/spec/coverage/assets/0.10.0/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  27. data/spec/coverage/assets/0.10.0/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  28. data/spec/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  29. data/spec/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  30. data/spec/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  31. data/spec/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  32. data/spec/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  33. data/spec/coverage/assets/0.10.0/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  34. data/spec/coverage/assets/0.10.0/smoothness/images/ui-icons_222222_256x240.png +0 -0
  35. data/spec/coverage/assets/0.10.0/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
  36. data/spec/coverage/assets/0.10.0/smoothness/images/ui-icons_454545_256x240.png +0 -0
  37. data/spec/coverage/assets/0.10.0/smoothness/images/ui-icons_888888_256x240.png +0 -0
  38. data/spec/coverage/assets/0.10.0/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
  39. data/spec/coverage/index.html +72 -0
  40. data/spec/lib/aspect/has_attributes_spec.rb +247 -67
  41. data/spec/mutants.txt +12676 -0
  42. data/spec/spec_helper.rb +1 -0
  43. metadata +63 -19
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c759e692a1f7aaf002e2c474f45339003979c186
4
- data.tar.gz: fad55ab8916d008d522556282a9bbf44539a38da
3
+ metadata.gz: ccb1a9fd117eb2ba52d9b95d31236dbfa549632d
4
+ data.tar.gz: 8b15bc652b3c3a495db45af32b8b6db68229f310
5
5
  SHA512:
6
- metadata.gz: 83a6b3c76a8963b1301a44dab87dd2176570d27bb939f40e62f0dcc8df411994f511817611e291e0ff6506190dd0c41f8ffa4dcbd9f6cb2ae8f16557aec8b460
7
- data.tar.gz: 2d5bba3bd952490bfb20ffbdf109a64d23f0c31923fe1a73eee2ebf6394e34650ee4a97309ec264724e02dafdd9fa984012bf6681e00ae490aaa238b00471050
6
+ metadata.gz: 2bf5e84b1503b8fd5a14faaae36b058e1557918c560e4c0a72d077fd0d0a9593704a062e855885f644667485f963980ee828fb29e5925ca3dcf443b6bc6885f4
7
+ data.tar.gz: 20eecb67017a3b55db952eb7c4172e378138ad724e5b2e1d455c8f3df6e20b00cd79a995d8e9057705d0393eef08c56cb10b5cdf9b0b92a62c490ac07eba91cb
data/Gemfile CHANGED
@@ -3,13 +3,28 @@ source "https://rubygems.org"
3
3
  gemspec
4
4
 
5
5
  group :development do
6
- gem "rake"
6
+ gem "rake" # Project tasks
7
+ gem "version" # Version management
8
+ gem "rspec" # BDD Testing
9
+ gem "fuubar" # RSpec formatter
10
+ gem "mutant-rspec" # RSpec mutation testing
11
+ gem "reek" # Code smell analyzer
12
+ gem "rubocop" # Static code analyzer
13
+ gem "ruby-prof" # Code profiler # TODO: No guard or rake. For use in integration tests.
14
+ gem "simplecov" # Test coverage # TODO: No guard or rake. For use in integration tests.
15
+ gem "yard" # Code documentation
16
+ gem "yardstick" # Documentation coverage
17
+ gem "redcarpet" # Markdown parser (for markdown within documentation)
18
+ gem "github-markup" # Github Flavored Markdown (GFM) parsing
19
+ gem "guard" # Filesystem event watching
7
20
 
8
- gem "yard"
9
-
10
- gem "rspec"
11
- gem "fuubar"
12
-
13
- gem "redcarpet"
14
- gem "github-markup"
21
+ gem "guard-bundler"
22
+ gem "guard-shell"
23
+ gem "guard-rake"
24
+ gem "guard-rspec"
25
+ # gem "guard-mutant" # TODO: Broken, custom guard
26
+ gem "guard-reek"
27
+ gem "guard-rubocop"
28
+ gem "guard-yard"
29
+ gem "guard-yardstick"
15
30
  end
data/README.md CHANGED
@@ -30,6 +30,12 @@ require "aspect" # Would all files
30
30
 
31
31
  ## Usage
32
32
 
33
+ ### General
34
+
35
+ No `Aspect` module will never define the `#initialize` or `.new` methods to avoid any strange errors
36
+ when implementing your own functionality. At most, a module may *suggest* you call some kind of method
37
+ within `#initialize` or `.new`.
38
+
33
39
  ### Aspect::HasAttributes
34
40
 
35
41
  **[Documentation](http://www.rubydoc.info/gems/aspect/Aspect/HasAttributes)**
@@ -66,6 +72,41 @@ p user.moderator? # => true
66
72
  p user.admin? # => true
67
73
  ```
68
74
 
75
+ You can change the method names if you're including into an object which already
76
+ defines `.attribute` and `#update_attributes`:
77
+
78
+ ```rb
79
+ require "aspect/has_attributes"
80
+
81
+ class User
82
+ include Aspect::HasAttributes(method: { define: :atr, update: :mass_assign })
83
+
84
+ atr(:name) { |value| value.to_s.strip }
85
+ atr(:moderator, query: true)
86
+ atr(:admin, query: true) { |value| @moderator && value }
87
+
88
+ def initialize(attributes={})
89
+ mass_assign(attributes)
90
+ end
91
+ end
92
+ ```
93
+
94
+ You may omit either method from being defined by passing `false` to it's `:method` option:
95
+
96
+ ```rb
97
+ require "aspect/has_attributes"
98
+
99
+ class User
100
+ include Aspect::HasAttributes(method: { define: false })
101
+
102
+ def initialize(attributes={})
103
+ update_attributes(attributes)
104
+ end
105
+ end
106
+
107
+ p User.respond_to?(:attribute) # => false
108
+ ```
109
+
69
110
  ## Copyright
70
111
 
71
112
  Copyright © 2016 Ryan Scott Lewis <ryan@rynet.us>.
data/Rakefile CHANGED
@@ -1,20 +1,66 @@
1
+ # Package task
2
+
1
3
  require "pathname"
2
4
  require "rubygems/package_task"
3
- require "rake/version_task"
4
- require "yard"
5
- require "rspec/core/rake_task"
6
5
 
7
- gemspec = Pathname.glob(Pathname.new(__FILE__).join("..", "*.gemspec")).first
6
+ PROJECT_ROOT = Pathname.new(__FILE__).join("..").expand_path
7
+
8
+ gemspec = Pathname.glob(PROJECT_ROOT.join("*.gemspec")).first
8
9
  spec = Gem::Specification.load(gemspec.to_s)
9
10
 
10
11
  Gem::PackageTask.new(spec) do |task|
11
12
  task.need_zip = false
12
13
  end
13
14
 
14
- Rake::VersionTask.new do |task|
15
- task.with_git_tag = true
15
+ # RSpec
16
+
17
+ require "rspec/core/rake_task"
18
+
19
+ RSpec::Core::RakeTask.new(:spec)
20
+
21
+ # Mutant
22
+
23
+ desc "Mutation testing"
24
+ task :mutant do
25
+ exec("bundle exec mutant --include lib --require aspect --use rspec Aspect* | tee spec/mutants.txt")
26
+ end
27
+
28
+ # Reek
29
+
30
+ require "reek/rake/task"
31
+
32
+ Reek::Rake::Task.new do |t|
33
+ t.fail_on_error = false
16
34
  end
17
35
 
36
+ # Rubocop
37
+
38
+ require "rubocop/rake_task"
39
+
40
+ RuboCop::RakeTask.new do |task|
41
+ task.patterns = ["lib/**/*.rb"]
42
+ # task.formatters = ["fuubar"]
43
+ end
44
+
45
+ # YARD
46
+
47
+ require "yard"
48
+
18
49
  YARD::Rake::YardocTask.new
19
50
 
20
- RSpec::Core::RakeTask.new
51
+ # Yardstick
52
+
53
+ require "yardstick/rake/measurement"
54
+
55
+ options = YAML.load_file(".yardstick.yml")
56
+ Yardstick::Rake::Measurement.new(:yardstick, options) do |measurement|
57
+ measurement.output = PROJECT_ROOT.join("doc", "coverage.txt")
58
+ end
59
+
60
+ # Version
61
+
62
+ require "rake/version_task"
63
+
64
+ Rake::VersionTask.new do |task|
65
+ task.with_git_tag = true
66
+ end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.1
1
+ 0.0.2
data/aspect.gemspec CHANGED
@@ -1,21 +1,18 @@
1
1
  require "pathname"
2
2
 
3
- Gem::Specification.new do |s|
3
+ Gem::Specification.new do |spec|
4
4
  # Variables
5
- s.summary = "A small collection of useful classes, modules, and mixins for plain old Ruby objects."
6
- s.license = "MIT"
7
-
8
- # Dependencies
9
- s.add_dependency "version", "~> 1.0.0"
5
+ spec.summary = "A small collection of useful classes, modules, and mixins for plain old Ruby objects."
6
+ spec.license = "MIT"
10
7
 
11
8
  # Pragmatically set variables and constants
12
- s.author = "Ryan Scott Lewis"
13
- s.email = "ryan@rynet.us"
14
- s.homepage = "http://github.com/RyanScottLewis/#{s.name}"
15
- s.version = Pathname.glob("VERSION*").first.read rescue "0.0.0"
16
- s.description = s.summary
17
- s.name = Pathname.new(__FILE__).basename(".gemspec").to_s
18
- s.require_paths = ["lib"]
19
- s.files = Dir["{Rakefile,Gemfile,README*,VERSION,LICENSE,*.gemspec,{lib,bin,examples,spec,test}/**/*}"]
20
- s.test_files = Dir["{examples,spec,test}/**/*"]
9
+ spec.author = "Ryan Scott Lewis"
10
+ spec.email = "ryan@rynet.us"
11
+ spec.homepage = "http://github.com/RyanScottLewis/#{spec.name}"
12
+ spec.version = Pathname.glob("VERSION*").first.read rescue "0.0.0"
13
+ spec.description = spec.summary
14
+ spec.name = Pathname.new(__FILE__).basename(".gemspec").to_s
15
+ spec.require_paths = ["lib"]
16
+ spec.files = Dir["{Rakefile,Gemfile,README*,VERSION,LICENSE,*.gemspec,{lib,bin,examples,spec,test}/**/*}"]
17
+ spec.test_files = Dir["{examples,spec,test}/**/*"]
21
18
  end
@@ -10,7 +10,7 @@ module Aspect
10
10
  #
11
11
  # Here's how I do it with [YARD](http://yardoc.org):
12
12
  #
13
- # ```
13
+ # ```rb
14
14
  # class User
15
15
  # include Aspect::HasAttributes
16
16
  #
@@ -42,7 +42,6 @@ module Aspect
42
42
  module HasAttributes
43
43
  # The class methods to extend into the object HasAttributes was included in.
44
44
  module ClassMethods
45
- # @method attribute
46
45
  # Define an attribute on the object.
47
46
  #
48
47
  # @example Simple accessor
@@ -138,41 +137,53 @@ module Aspect
138
137
  # user.admin = true
139
138
  # user.moderator? # => true
140
139
  # user.admin? # => true
140
+ # @param [#to_sym] name The name of the attribute and instance variable.
141
141
  # @param [Hash, #to_hash] options The options for defining and passing to the block.
142
142
  # @option options [Boolean] :getter (true) Determines whether to define an attribute getter.
143
143
  # @option options [Boolean] :setter (true) Determines whether to define an attribute setter.
144
144
  # @option options [Boolean] :query (false)
145
145
  # Determines whether to define as a query attribute, with the getter having a question mark appended to the
146
146
  # method name and the setter converting the value or block into a boolean using bang-bang (`!!`).
147
+ # @option options [#to_sym] :method (nil) The method name to send to the value after executing
147
148
  # @yieldparam [Object] value The value given to the setter method.
148
149
  # @yieldparam [Hash] options The options given when defining, given to the setter method.
149
150
  # @yieldreturn [Object] The value to set the instance variable as.
150
151
  # @return [Object]
151
- def attribute(name, options={}, &block)
152
+ def attribute(name, options={}, &block) # TODO: Should break out into protected methods?
153
+ name = name.to_sym
154
+
152
155
  options = options.to_h unless options.is_a?(Hash)
153
- options = { getter: true, setter: true, query: false }.merge(options)
156
+ options = { getter: true, setter: true }.merge(options)
154
157
 
155
158
  if options[:getter]
156
- if options[:query]
157
- define_method("#{name}?") { !!instance_variable_get("@#{name}") }
158
- else
159
- attr_reader(name)
159
+ options[:getter] = options[:getter] == true ? {} : options[:getter].to_h
160
+
161
+ options[:getter][:method] = options[:getter][:method].to_sym if options[:getter][:method]
162
+ options[:getter][:instance_method] = options[:getter][:instance_method].to_sym if options[:getter][:instance_method]
163
+
164
+ method_name = options[:query] ? "#{name}?" : name
165
+ define_method(method_name) do
166
+ value = instance_variable_get("@#{name}")
167
+ value = value.send(options[:getter][:method]) if options[:getter][:method]
168
+ value = method(options[:getter][:instance_method]).call(value) if options[:getter][:instance_method]
169
+
170
+ options[:query] ? !!value : value
160
171
  end
161
172
  end
162
173
 
163
174
  if options[:setter]
164
- if options[:query]
165
- define_method("#{name}=") do |value|
166
- value = instance_exec(value, options, &block) unless block.nil?
175
+ options[:setter] = options[:setter] == true ? {} : options[:setter].to_h
176
+
177
+ options[:setter][:method] = options[:setter][:method].to_sym if options[:setter][:method]
178
+ options[:setter][:instance_method] = options[:setter][:instance_method].to_sym if options[:setter][:instance_method]
167
179
 
168
- instance_variable_set("@#{name}", !!value)
169
- end
170
- else
171
- define_method("#{name}=") do |value|
172
- value = instance_exec(value, options, &block) unless block.nil?
180
+ define_method("#{name}=") do |value|
181
+ value = instance_exec(value, options, &block) unless block.nil?
182
+ value = value.send(options[:setter][:method]) if options[:setter][:method]
183
+ value = method(options[:setter][:instance_method]).call(value) if options[:setter][:instance_method]
184
+ value = options[:query] ? !!value : value
173
185
 
174
- instance_variable_set("@#{name}", value)
175
- end
186
+ instance_variable_set("@#{name}", value)
176
187
  end
177
188
  end
178
189
 
@@ -181,7 +192,7 @@ module Aspect
181
192
  end
182
193
 
183
194
  class << self
184
- # On include hook.
195
+ # On include hook to extend `ClassMethods`.
185
196
  def included(base)
186
197
  base.send(:extend, ClassMethods)
187
198
  end
@@ -231,9 +242,7 @@ module Aspect
231
242
  # @param [Hash, #to_h] attributes
232
243
  # @return [Object] This object.
233
244
  def update_attributes(attributes={})
234
- attributes = attributes.to_h unless attributes.is_a?(Hash)
235
-
236
- attributes.each { |name, value| send("#{name}=", value) }
245
+ attributes.to_h.each { |name, value| send("#{name}=", value) }
237
246
  end
238
247
  end
239
248
  end
@@ -0,0 +1,28 @@
1
+ module Aspect
2
+ # Extend in a `Class` to register new instances with a name in a collection named `.registry`.
3
+ module HasRegistry # TODO: Allow to register Classes, not just instances
4
+ # All defined instances.
5
+ #
6
+ # @return [{Symbol => Object}]
7
+ def registry
8
+ @registry ||= {}
9
+ end
10
+
11
+ # Register an instance.
12
+ #
13
+ # @param [#to_sym] name
14
+ # @yield The new instance's scope.
15
+ # @return [Object]
16
+ def register(name, *arguments, &block)
17
+ registry[name.to_sym] = new(*arguments, &block)
18
+ end
19
+
20
+ # Get whether an instance is registered.
21
+ #
22
+ # @param [#to_sym] name
23
+ # @return [Boolean]
24
+ def registered?(name)
25
+ @registry.keys.include?(name.to_sym)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,97 @@
1
+ module Aspect
2
+ # Transforms a `Hash` into human language.
3
+ #
4
+ # @example Getting using `Symbol`.
5
+ # require "aspect/message_transform"
6
+ #
7
+ # message = Aspect::MessageTransform.new(login: "Click to login", body: "Lorem ipsum")
8
+ #
9
+ # p message.to_a(:login, :body) # => ["Click to login", "Lorem ipsum"]
10
+ # p message.to_h(:login, :body) # => { login: "Click to login", body: "Lorem ipsum" }
11
+ # p message.to_s(:login, :body) # => "Click to login, Lorem ipsum"
12
+ #
13
+ # p message.to_a(:body) # => ["Lorem ipsum"]
14
+ # p message.to_h(:body) # => { body: "Lorem ipsum" }
15
+ # p message.to_s(:body) # => "Lorem ipsum"
16
+ # @example Getting using `Hash`.
17
+ # require "aspect/message_transform"
18
+ #
19
+ # message = Aspect::MessageTransform.new(login: "Click to login", body: "Lorem ipsum")
20
+ #
21
+ # p message.to_a(login: true, body: true) # => ["Click to login", "Lorem ipsum"]
22
+ # p message.to_h(login: true, body: true) # => { login: "Click to login", body: "Lorem ipsum" }
23
+ # p message.to_s(login: true, body: true) # => "Click to login, Lorem ipsum"
24
+ #
25
+ # p message.to_a(login: false, body: true) # => ["Lorem ipsum"]
26
+ # p message.to_h(login: false, body: true) # => { body: "Lorem ipsum" }
27
+ # p message.to_s(login: false, body: true) # => "Lorem ipsum"
28
+ #
29
+ # p message.to_a(body: true) # => ["Lorem ipsum"]
30
+ # p message.to_h(body: true) # => { body: "Lorem ipsum" }
31
+ # p message.to_s(body: true) # => "Lorem ipsum"
32
+ # @example Output formatting and separator.
33
+ # require "aspect/message_transform"
34
+ #
35
+ # message = Aspect::MessageTransform.new(login: "Click to login", body: "Lorem ipsum")
36
+ #
37
+ # message.separator = " - "
38
+ # message.formatter { |value| "<div>#{value}</div>" }
39
+ # # Or (can be anything responding to #call(value)):
40
+ # # message.formatter = -> { |value| "<div>#{value}</div>" }
41
+ #
42
+ # p message.to_a(:login, :body) # => ["<div>Click to login</div>", "<div>Lorem ipsum</div>"]
43
+ # p message.to_h(:login, :body) # => { login: "<div>Click to login</div>", body: "<div>Lorem ipsum</div>" }
44
+ # p message.to_s(:login, :body) # => "<div>Click to login</div> - <div>Lorem ipsum</div>"
45
+ # @example Message interpolation.
46
+ # require "aspect/message_transform"
47
+ #
48
+ # message = Aspect::MessageTransform.new(hello: "Hello %{hello}!")
49
+ #
50
+ # p message.to_s(hello: "world") # => "Hello world!"
51
+ # @example Getting with a multi-level `Hash`.
52
+ # require "aspect/message_transform"
53
+ #
54
+ # message = Aspect::MessageTransform.new(header: "My App", body: { title: "Some Page", hello: "Hello %{hello}!" })
55
+ #
56
+ # p message.to_s(header: true) # => "My App"
57
+ # p message.to_s(body: { title: true }) # => "Some Page"
58
+ # p message.to_s(body: { hello: "world" }) # => "Hello world!"
59
+ # @example Getting with `String` notation.
60
+ # require "aspect/message_transform"
61
+ #
62
+ # message = Aspect::MessageTransform.new(header: "My App", body: { title: "Some Page", hello: "Hello %{hello}!" })
63
+ #
64
+ # p message.to_s("header") # => "My App"
65
+ # p message.to_s("body.title") # => "Some Page"
66
+ # p message.to_s("body.hello" => "world") # => "Hello world!"
67
+ # p message.to_a("body.title", "body.hello" => "world") # => ["Some Page", "Hello world!"]
68
+ # @example Internationalization (I18N) using multi-level `Hash`, compatible with {Verifier} output.
69
+ # require "aspect/message_transform"
70
+ #
71
+ # dictionary = {
72
+ # en: {
73
+ # presence: "%{attribute} must not be blank",
74
+ # greater_than_or_equal_to: "%{attribute} must be greater than or equal to %{greater_than_or_equal_to}"
75
+ # },
76
+ # es: {
77
+ # presence: "%{attribute} no debe estar en blanco",
78
+ # greater_than_or_equal_to: "%{attribute} debe ser mayor que o igual a %{greater_than_or_equal_to}"
79
+ # }
80
+ # }
81
+ #
82
+ # error_message = Aspect::MessageTransform.new(dictionary)
83
+ #
84
+ # error_message.to_s(en: { attribute: "age", presence: true, greater_than_or_equal_to: 18 }) # => "age must not be blank, age must be greater than or equal to 18"
85
+ # error_message.to_s(es: { attribute: "edad", presence: true, greater_than_or_equal_to: 18 }) # => "edad no debe estar en blanco, edad debe ser mayor que o igual a 18"
86
+ class MessageTransform
87
+ def initialize(expectations={})
88
+ @expectations = expectations.to_h
89
+ end
90
+
91
+ def inspect
92
+ memory_location = (object_id << 1).to_s(16)
93
+
94
+ "#<#{self.class}>:0x#{memory_location} #{@expectations.inspect}>"
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,112 @@
1
+ require "aspect/verifier" if Kernel.respond_to?(:require)
2
+
3
+ module Aspect
4
+ # Verify a set of checks on a list of an object's attributes.
5
+ #
6
+ # Uses a {Verifier} for each attribute run the checks.
7
+ #
8
+ # @example Basic usage.
9
+ # require "aspect/validator"
10
+ #
11
+ # user = User.new(name: "Foo Bar")
12
+ # user_validator = Aspect::Validator.new
13
+ #
14
+ # user_validator.verify(:name, presence: true)
15
+ # user_validator.verify(:age, presence: true, integer: true, greater_than_or_equal_to: 18)
16
+ #
17
+ # user.age = nil
18
+ # user_validator.validate(user) # => { age: { presence: true, integer: true, greater_than_or_equal_to: 18 } }
19
+ #
20
+ # user.age = 17.98
21
+ # user_validator.validate(user) # => { age: { integer: true, greater_than_or_equal_to: 18 } }
22
+ #
23
+ # user.name = ""
24
+ # user.age = 17
25
+ # user_validator.validate(user) # => { name: { presence: true }, age: { greater_than_or_equal_to: 18 } }
26
+ #
27
+ # user.name = "Foo Bar"
28
+ # user.age = 25
29
+ # user_validator.validate(user) # => nil # Valid
30
+ # @example Initialize with `Hash`
31
+ # Aspect::Validator.new(name: { presence: true }, age: { presence: true, integer: true, greater_than_or_equal_to: 18 })
32
+ # @example Displaying validation errors with {MessageTransform}.
33
+ # require "aspect/validator"
34
+ # require "aspect/message_transform"
35
+ #
36
+ # user = User.new(name: "Foo Bar")
37
+ #
38
+ # error_message = Aspect::MessageTransform.new(
39
+ # presence: "Must be given.",
40
+ # integer: "Must give a number.",
41
+ # greater_than_or_equal_to: "Must be %{greater_than_or_equal_to} or greater."
42
+ # )
43
+ #
44
+ # user_validator = Aspect::Validator.new
45
+ # user_validator.verify(:name, presence: true)
46
+ # user_validator.verify(:age, presence: true, integer: true, greater_than_or_equal_to: 18)
47
+ #
48
+ # user_errors = user_validator.validate(user)
49
+ #
50
+ # puts error_message.to_s(user_errors) if user_errors
51
+ # @example Displaying validation errors with {MessageTransform} and Internationalization (I18N).
52
+ # require "aspect/validator"
53
+ # require "aspect/message_transform"
54
+ #
55
+ # user = User.new(name: "Foo Bar")
56
+ #
57
+ # dictionary_data = {
58
+ # en: {
59
+ # attributes: {
60
+ # age: "age"
61
+ # },
62
+ # errors: {
63
+ # presence: "must not be blank",
64
+ # greater_than_or_equal_to: "must be greater than or equal to %{value}"
65
+ # }
66
+ # },
67
+ # es: {
68
+ # attributes: {
69
+ # age: "edad"
70
+ # },
71
+ # errors: {
72
+ # presence: "no debe estar en blanco",
73
+ # greater_than_or_equal_to: "debe ser mayor que o igual a %{value}"
74
+ # }
75
+ # }
76
+ # }
77
+ #
78
+ # language = :en
79
+ # dictionary = Aspect::MessageTransform.new(dictionary_data)
80
+ # user_validator = Aspect::Validator.new(name: { presence: true }, age: { presence: true, greater_than_or_equal_to: 18 })
81
+ #
82
+ # user_errors = user_validator.validate(user)
83
+ #
84
+ # if user_errors
85
+ # user_errors.each do |attribute_name, error|
86
+ # attribute_name = dictionary.to_s("#{language}.attributes.#{attribute_name}")
87
+ #
88
+ # puts "#{attribute_name}: " + dictionary.to_s("#{language}.errors" => error)
89
+ # end
90
+ # end
91
+ class Validator
92
+ def initialize
93
+ @verifications = []
94
+ end
95
+
96
+ def verify(attributes={})
97
+ @verifications << Verifier.new(attributes)
98
+ end
99
+
100
+ def validate(object)
101
+ errors = Errors.new
102
+
103
+ @verifications.each do |constraint|
104
+ value = object.send(constraint.name)
105
+ errors = constraint.validate(value)
106
+ # errors.add() # TODO
107
+ end
108
+
109
+ errors
110
+ end
111
+ end
112
+ end