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