aspect 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +23 -8
- data/README.md +41 -0
- data/Rakefile +53 -7
- data/VERSION +1 -1
- data/aspect.gemspec +12 -15
- data/lib/aspect/has_attributes.rb +31 -22
- data/lib/aspect/has_registry.rb +28 -0
- data/lib/aspect/message_transform.rb +97 -0
- data/lib/aspect/validator.rb +112 -0
- data/lib/aspect/verifier/check.rb +125 -0
- data/lib/aspect/verifier.rb +212 -0
- data/lib/aspect/version.rb +3 -0
- data/lib/aspect.rb +1 -2
- data/spec/coverage/assets/0.10.0/application.css +799 -0
- data/spec/coverage/assets/0.10.0/application.js +1707 -0
- data/spec/coverage/assets/0.10.0/colorbox/border.png +0 -0
- data/spec/coverage/assets/0.10.0/colorbox/controls.png +0 -0
- data/spec/coverage/assets/0.10.0/colorbox/loading.gif +0 -0
- data/spec/coverage/assets/0.10.0/colorbox/loading_background.png +0 -0
- data/spec/coverage/assets/0.10.0/favicon_green.png +0 -0
- data/spec/coverage/assets/0.10.0/favicon_red.png +0 -0
- data/spec/coverage/assets/0.10.0/favicon_yellow.png +0 -0
- data/spec/coverage/assets/0.10.0/loading.gif +0 -0
- data/spec/coverage/assets/0.10.0/magnify.png +0 -0
- data/spec/coverage/assets/0.10.0/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/spec/coverage/assets/0.10.0/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/spec/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/spec/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/spec/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/spec/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/spec/coverage/assets/0.10.0/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- data/spec/coverage/assets/0.10.0/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/spec/coverage/assets/0.10.0/smoothness/images/ui-icons_222222_256x240.png +0 -0
- data/spec/coverage/assets/0.10.0/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
- data/spec/coverage/assets/0.10.0/smoothness/images/ui-icons_454545_256x240.png +0 -0
- data/spec/coverage/assets/0.10.0/smoothness/images/ui-icons_888888_256x240.png +0 -0
- data/spec/coverage/assets/0.10.0/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
- data/spec/coverage/index.html +72 -0
- data/spec/lib/aspect/has_attributes_spec.rb +247 -67
- data/spec/mutants.txt +12676 -0
- data/spec/spec_helper.rb +1 -0
- metadata +63 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ccb1a9fd117eb2ba52d9b95d31236dbfa549632d
|
4
|
+
data.tar.gz: 8b15bc652b3c3a495db45af32b8b6db68229f310
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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 "
|
9
|
-
|
10
|
-
gem "
|
11
|
-
gem "
|
12
|
-
|
13
|
-
gem "
|
14
|
-
gem "
|
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
|
-
|
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
|
-
|
15
|
-
|
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
|
-
|
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
|
+
0.0.2
|
data/aspect.gemspec
CHANGED
@@ -1,21 +1,18 @@
|
|
1
1
|
require "pathname"
|
2
2
|
|
3
|
-
Gem::Specification.new do |
|
3
|
+
Gem::Specification.new do |spec|
|
4
4
|
# Variables
|
5
|
-
|
6
|
-
|
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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
156
|
+
options = { getter: true, setter: true }.merge(options)
|
154
157
|
|
155
158
|
if options[:getter]
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
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
|
-
|
165
|
-
|
166
|
-
|
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
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
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
|
-
|
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
|
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
|