aspect 0.0.1 → 0.0.2

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 (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