assertion 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -48,7 +48,7 @@ module Assertion
48
48
  # @return [String]
49
49
  #
50
50
  def message(state = nil)
51
- assertion.message(!state)
51
+ assertion.message !state
52
52
  end
53
53
 
54
54
  # Checks the current state of the assertion
@@ -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
@@ -52,7 +52,7 @@ module Assertion
52
52
  # @return [true]
53
53
  #
54
54
  # @raise [Assertion::InvalidError]
55
- # When a assertion is not satisfied (validation fails)
55
+ # When an assertion is not satisfied (validation fails)
56
56
  #
57
57
  def validate!
58
58
  invalid? ? fail(InvalidError.new messages) : true
@@ -5,6 +5,8 @@ module Assertion
5
5
  #
6
6
  # @api private
7
7
  #
8
+ # @todo Extract this module to the external gem `transproc-inflector`
9
+ #
8
10
  module Inflector
9
11
 
10
12
  extend ::Transproc::Registry
@@ -4,6 +4,6 @@ module Assertion
4
4
 
5
5
  # The semantic version of the module.
6
6
  # @see http://semver.org/ Semantic versioning 2.0
7
- VERSION = "0.0.1".freeze
7
+ VERSION = "0.1.0".freeze
8
8
 
9
9
  end # module Assertion
@@ -1,19 +1,10 @@
1
1
  # encoding: utf-8
2
2
 
3
- describe Assertion do
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
- example.run
5
+ describe Assertion do
13
6
 
14
- I18n.locale = old_locale
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
@@ -7,4 +7,4 @@ en:
7
7
  wrong: "%{name} is a child (age %{age})"
8
8
  is_male:
9
9
  right: "%{name} is a male"
10
- wrong: "%{name} is a female"
10
+ wrong: "%{name} is a female"
@@ -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
- describe ".attributes" do
8
+ it "can declare attributes" do
9
+ expect(klass).to be_kind_of Assertion::Attributes
10
+ end
9
11
 
10
- subject(:attributes) { klass.attributes }
11
-
12
- it { is_expected.to eql [] }
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