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