assertion 0.0.1 → 0.1.0
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/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
|