assertion 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -0
- data/README.md +54 -43
- data/lib/assertion.rb +37 -6
- data/lib/assertion/attributes.rb +54 -0
- data/lib/assertion/base.rb +7 -65
- data/lib/assertion/guard.rb +99 -0
- data/lib/assertion/{exceptions/invalid_error.rb → invalid_error.rb} +5 -1
- data/lib/assertion/inversion.rb +1 -1
- data/lib/assertion/messages.rb +65 -0
- data/lib/assertion/state.rb +1 -1
- data/lib/assertion/transprocs/inflector.rb +2 -0
- data/lib/assertion/version.rb +1 -1
- data/spec/integration/assertion_spec.rb +3 -12
- data/spec/integration/guard_spec.rb +29 -0
- data/spec/{integration → shared}/en.yml +1 -1
- data/spec/shared/i18n.rb +21 -0
- data/spec/unit/assertion/attributes_spec.rb +97 -0
- data/spec/unit/assertion/base_spec.rb +6 -103
- data/spec/unit/assertion/exceptions/invalid_error_spec.rb +11 -10
- data/spec/unit/assertion/guard_spec.rb +82 -0
- data/spec/unit/assertion/messages_spec.rb +41 -0
- data/spec/unit/assertion_spec.rb +69 -12
- metadata +18 -15
- data/lib/assertion/exceptions/name_error.rb +0 -29
- data/lib/assertion/exceptions/not_implemented_error.rb +0 -29
- data/lib/assertion/transprocs/i18n.rb +0 -55
- data/spec/unit/assertion/exceptions/name_error_spec.rb +0 -26
- data/spec/unit/assertion/exceptions/not_implemented_error_spec.rb +0 -26
- data/spec/unit/assertion/transprocs/i18n/to_scope_spec.rb +0 -19
- data/spec/unit/assertion/transprocs/i18n/translate_spec.rb +0 -28
@@ -21,7 +21,6 @@ module Assertion
|
|
21
21
|
# @private
|
22
22
|
def initialize(*messages)
|
23
23
|
@messages = messages.flatten.freeze
|
24
|
-
super
|
25
24
|
freeze
|
26
25
|
end
|
27
26
|
|
@@ -31,6 +30,11 @@ module Assertion
|
|
31
30
|
#
|
32
31
|
attr_reader :messages
|
33
32
|
|
33
|
+
# @private
|
34
|
+
def inspect
|
35
|
+
"<#{self} @messages=#{messages}>"
|
36
|
+
end
|
37
|
+
|
34
38
|
end # class InvalidError
|
35
39
|
|
36
40
|
end # module Assertion
|
data/lib/assertion/inversion.rb
CHANGED
@@ -0,0 +1,65 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "i18n"
|
4
|
+
|
5
|
+
module Assertion
|
6
|
+
|
7
|
+
# Module Messages provides a feature for gem-specific translation of messages
|
8
|
+
# describing the desired state of the assertion
|
9
|
+
#
|
10
|
+
# You need to declare a hash of attributes to be added to the translation.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# class MyClass
|
14
|
+
# include Assertion::Messages
|
15
|
+
# def attributes
|
16
|
+
# {}
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# item = MyClass.new
|
21
|
+
# item.message(true)
|
22
|
+
# # => "translation missing: en.assertion.my_class.right"
|
23
|
+
# item.message(false)
|
24
|
+
# # => "translation missing: en.assertion.my_class.wrong"
|
25
|
+
#
|
26
|
+
# @author Andrew Kozin <Andrew.Kozin@gmail.com>
|
27
|
+
#
|
28
|
+
module Messages
|
29
|
+
|
30
|
+
# The gem-specific root scope for translations
|
31
|
+
#
|
32
|
+
# @return [Symbol]
|
33
|
+
#
|
34
|
+
ROOT = :assertion
|
35
|
+
|
36
|
+
# The states to be translated with their dictionary names
|
37
|
+
#
|
38
|
+
# @return [Hash<Object => Symbol>]
|
39
|
+
#
|
40
|
+
DICTIONARY = { true => :right, false => :wrong }
|
41
|
+
|
42
|
+
# Returns the message describing the desired state of assertion
|
43
|
+
#
|
44
|
+
# The translation is provided for the gem-specific scope for the
|
45
|
+
# current class
|
46
|
+
#
|
47
|
+
# @param [Boolean] state <description>
|
48
|
+
#
|
49
|
+
# @return [String] The translation
|
50
|
+
#
|
51
|
+
def message(state)
|
52
|
+
key = DICTIONARY[state]
|
53
|
+
scope = [ROOT, Inflector[:to_snake_path][self.class.name]]
|
54
|
+
|
55
|
+
I18n.translate key, attributes.merge(scope: scope)
|
56
|
+
end
|
57
|
+
|
58
|
+
# @private
|
59
|
+
def attributes
|
60
|
+
{}
|
61
|
+
end
|
62
|
+
|
63
|
+
end # module Messages
|
64
|
+
|
65
|
+
end # module Assertion
|
data/lib/assertion/state.rb
CHANGED
@@ -52,7 +52,7 @@ module Assertion
|
|
52
52
|
# @return [true]
|
53
53
|
#
|
54
54
|
# @raise [Assertion::InvalidError]
|
55
|
-
# When
|
55
|
+
# When an assertion is not satisfied (validation fails)
|
56
56
|
#
|
57
57
|
def validate!
|
58
58
|
invalid? ? fail(InvalidError.new messages) : true
|
data/lib/assertion/version.rb
CHANGED
@@ -1,19 +1,10 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
let(:load_path) { Dir[File.expand_path "../*.yml", __FILE__] }
|
6
|
-
|
7
|
-
around do |example|
|
8
|
-
old_locale, I18n.locale = I18n.locale, :en
|
9
|
-
old_path, I18n.load_path = I18n.load_path, load_path
|
10
|
-
I18n.backend.load_translations
|
3
|
+
require "shared/i18n"
|
11
4
|
|
12
|
-
|
5
|
+
describe Assertion do
|
13
6
|
|
14
|
-
|
15
|
-
I18n.load_path = old_path
|
16
|
-
end
|
7
|
+
include_context "preloaded translations"
|
17
8
|
|
18
9
|
it "works" do
|
19
10
|
IsMale = Assertion.about :name, :gender do
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "ostruct"
|
4
|
+
require "shared/i18n"
|
5
|
+
|
6
|
+
describe Assertion do
|
7
|
+
|
8
|
+
include_context "preloaded translations"
|
9
|
+
|
10
|
+
it "works" do
|
11
|
+
IsAdult = Assertion.about :name, :age do
|
12
|
+
age.to_i >= 18
|
13
|
+
end
|
14
|
+
|
15
|
+
AdultOnly = Assertion.guards :user do
|
16
|
+
IsAdult[user]
|
17
|
+
end
|
18
|
+
|
19
|
+
andrew = OpenStruct.new(name: "Andrew", age: 13, city: "Moscow")
|
20
|
+
andriy = OpenStruct.new(name: "Andriy", age: 28, city: "Kiev")
|
21
|
+
|
22
|
+
expect { AdultOnly[andrew] }.to raise_error Assertion::InvalidError
|
23
|
+
expect(AdultOnly[andriy]).to eql andriy
|
24
|
+
end
|
25
|
+
|
26
|
+
after { Object.send :remove_const, :AdultOnly }
|
27
|
+
after { Object.send :remove_const, :IsAdult }
|
28
|
+
|
29
|
+
end # describe Assertion
|
data/spec/shared/i18n.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
shared_context "preloaded translations" do
|
4
|
+
|
5
|
+
around do |example|
|
6
|
+
|
7
|
+
load_path = Dir[File.expand_path "../*.yml", __FILE__]
|
8
|
+
|
9
|
+
old_locale, I18n.locale = I18n.locale, :en
|
10
|
+
old_path, I18n.load_path = I18n.load_path, load_path
|
11
|
+
I18n.backend.load_translations
|
12
|
+
|
13
|
+
example.run
|
14
|
+
|
15
|
+
I18n.locale = old_locale
|
16
|
+
I18n.load_path = old_path
|
17
|
+
I18n.backend.reload!
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end # shared context
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
describe Assertion::Attributes do
|
4
|
+
|
5
|
+
subject(:klass) do
|
6
|
+
class Test
|
7
|
+
extend Assertion::Attributes
|
8
|
+
attr_reader :baz, :qux
|
9
|
+
end
|
10
|
+
|
11
|
+
Test
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#attributes" do
|
15
|
+
|
16
|
+
subject { klass.attributes }
|
17
|
+
|
18
|
+
it { is_expected.to eql [] }
|
19
|
+
|
20
|
+
end # describe #attributes
|
21
|
+
|
22
|
+
describe "#attribute" do
|
23
|
+
|
24
|
+
context "single attribute" do
|
25
|
+
|
26
|
+
subject do
|
27
|
+
klass.attribute "foo"
|
28
|
+
klass.attribute "bar"
|
29
|
+
end
|
30
|
+
|
31
|
+
it "register the attribute" do
|
32
|
+
expect { subject }.to change { klass.attributes }.to [:foo, :bar]
|
33
|
+
end
|
34
|
+
|
35
|
+
end # context
|
36
|
+
|
37
|
+
context "list of attributes" do
|
38
|
+
|
39
|
+
subject { klass.attribute :foo, :bar }
|
40
|
+
|
41
|
+
it "register the attributes" do
|
42
|
+
expect { subject }.to change { klass.attributes }.to [:foo, :bar]
|
43
|
+
end
|
44
|
+
|
45
|
+
end # context
|
46
|
+
|
47
|
+
context "array of attributes" do
|
48
|
+
|
49
|
+
subject { klass.attribute %w(foo bar) }
|
50
|
+
|
51
|
+
it "register the attributes" do
|
52
|
+
expect { subject }.to change { klass.attributes }.to [:foo, :bar]
|
53
|
+
end
|
54
|
+
|
55
|
+
end # context
|
56
|
+
|
57
|
+
context "name of instance method" do
|
58
|
+
|
59
|
+
subject { klass.attribute :baz, :qux }
|
60
|
+
|
61
|
+
it "raises NameError" do
|
62
|
+
expect { subject }.to raise_error do |error|
|
63
|
+
expect(error).to be_kind_of NameError
|
64
|
+
expect(error.message)
|
65
|
+
.to eql "Wrong name(s) for attribute(s): baz, qux"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end # context
|
70
|
+
|
71
|
+
context "forbidden attribute" do
|
72
|
+
|
73
|
+
before do
|
74
|
+
class Test
|
75
|
+
def self.__forbidden_attributes__
|
76
|
+
[:foo, :bar]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
subject { klass.attribute :foo, :bar }
|
82
|
+
|
83
|
+
it "raises NameError" do
|
84
|
+
expect { subject }.to raise_error do |error|
|
85
|
+
expect(error).to be_kind_of NameError
|
86
|
+
expect(error.message)
|
87
|
+
.to eql "Wrong name(s) for attribute(s): foo, bar"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
end # context
|
92
|
+
|
93
|
+
end # describe #attributes
|
94
|
+
|
95
|
+
after { Object.send :remove_const, :Test }
|
96
|
+
|
97
|
+
end # describe Assertion::Attributes
|
@@ -5,69 +5,13 @@ describe Assertion::Base do
|
|
5
5
|
let(:klass) { Class.new(described_class) }
|
6
6
|
before { allow(klass).to receive(:name) { "Test" } }
|
7
7
|
|
8
|
-
|
8
|
+
it "can declare attributes" do
|
9
|
+
expect(klass).to be_kind_of Assertion::Attributes
|
10
|
+
end
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
end # describe .attributes
|
15
|
-
|
16
|
-
describe ".attribute" do
|
17
|
-
|
18
|
-
subject(:attribute) { klass.attribute names }
|
19
|
-
|
20
|
-
context "with valid name" do
|
21
|
-
|
22
|
-
let(:names) { :foo }
|
23
|
-
|
24
|
-
it "defines the attribute" do
|
25
|
-
expect { subject }
|
26
|
-
.to change { klass.attributes }
|
27
|
-
.to [:foo]
|
28
|
-
end
|
29
|
-
|
30
|
-
end # context
|
31
|
-
|
32
|
-
context "with array of names" do
|
33
|
-
|
34
|
-
let(:names) { [:foo, "bar", "foo"] }
|
35
|
-
|
36
|
-
it "defines the attribute" do
|
37
|
-
expect { subject }
|
38
|
-
.to change { klass.attributes }
|
39
|
-
.to [:foo, :bar]
|
40
|
-
end
|
41
|
-
|
42
|
-
end # context
|
43
|
-
|
44
|
-
context "with a :check name" do
|
45
|
-
|
46
|
-
let(:names) { [:check] }
|
47
|
-
|
48
|
-
it "fails" do
|
49
|
-
expect { subject }.to raise_error do |error|
|
50
|
-
expect(error).to be_kind_of Assertion::NameError
|
51
|
-
expect(error.message).to include "check"
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
end # context
|
56
|
-
|
57
|
-
context "with a :call name" do
|
58
|
-
|
59
|
-
let(:names) { [:call] }
|
60
|
-
|
61
|
-
it "fails" do
|
62
|
-
expect { subject }.to raise_error do |error|
|
63
|
-
expect(error).to be_kind_of Assertion::NameError
|
64
|
-
expect(error.message).to include "call"
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
end # context
|
69
|
-
|
70
|
-
end # describe .attribute
|
12
|
+
it "can translate states" do
|
13
|
+
expect(klass).to include Assertion::Messages
|
14
|
+
end
|
71
15
|
|
72
16
|
describe ".new" do
|
73
17
|
|
@@ -139,47 +83,6 @@ describe Assertion::Base do
|
|
139
83
|
|
140
84
|
end # describe #attributes
|
141
85
|
|
142
|
-
describe "#message" do
|
143
|
-
|
144
|
-
let(:assertion) { klass.new }
|
145
|
-
|
146
|
-
context "for the truthy state" do
|
147
|
-
|
148
|
-
subject { assertion.message(true) }
|
149
|
-
it { is_expected.to eql "translation missing: en.assertion.test.right" }
|
150
|
-
|
151
|
-
end # context
|
152
|
-
|
153
|
-
context "for the falsey state" do
|
154
|
-
|
155
|
-
subject { assertion.message(false) }
|
156
|
-
it { is_expected.to eql "translation missing: en.assertion.test.wrong" }
|
157
|
-
|
158
|
-
end # context
|
159
|
-
|
160
|
-
context "by default" do
|
161
|
-
|
162
|
-
it "returns the message for the falsey state" do
|
163
|
-
expect(assertion.message).to eql assertion.message(false)
|
164
|
-
end
|
165
|
-
|
166
|
-
end # context
|
167
|
-
|
168
|
-
end # describe #message
|
169
|
-
|
170
|
-
describe "#check" do
|
171
|
-
|
172
|
-
subject { klass.new.check }
|
173
|
-
|
174
|
-
it "raises NotImplementedError" do
|
175
|
-
expect { subject }.to raise_error do |error|
|
176
|
-
expect(error).to be_kind_of Assertion::NotImplementedError
|
177
|
-
expect(error.message).to include("Test#check ")
|
178
|
-
end
|
179
|
-
end
|
180
|
-
|
181
|
-
end # describe #check
|
182
|
-
|
183
86
|
describe "#call" do
|
184
87
|
|
185
88
|
subject { assertion.call }
|
@@ -13,16 +13,6 @@ describe Assertion::InvalidError do
|
|
13
13
|
|
14
14
|
end # describe .new
|
15
15
|
|
16
|
-
describe "#message" do
|
17
|
-
|
18
|
-
subject { error.message }
|
19
|
-
|
20
|
-
it "returns a proper message" do
|
21
|
-
expect(subject).to include "#{messages.inspect}"
|
22
|
-
end
|
23
|
-
|
24
|
-
end # describe #message
|
25
|
-
|
26
16
|
describe "#messages" do
|
27
17
|
|
28
18
|
subject { error.messages }
|
@@ -37,4 +27,15 @@ describe Assertion::InvalidError do
|
|
37
27
|
|
38
28
|
end # describe #message
|
39
29
|
|
30
|
+
describe "#inspect" do
|
31
|
+
|
32
|
+
subject { error.inspect }
|
33
|
+
|
34
|
+
it "returns a verbose string" do
|
35
|
+
expect(subject)
|
36
|
+
.to eql "<Assertion::InvalidError @messages=[\"foo\", \"bar\"]>"
|
37
|
+
end
|
38
|
+
|
39
|
+
end # describe #inspect
|
40
|
+
|
40
41
|
end # describe Assertion::InvalidError
|