assertion 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +17 -0
  3. data/Gemfile +0 -2
  4. data/Guardfile +6 -6
  5. data/README.md +8 -6
  6. data/assertion.gemspec +1 -1
  7. data/config/metrics/flay.yml +1 -1
  8. data/lib/assertion.rb +29 -80
  9. data/lib/assertion/base.rb +38 -64
  10. data/lib/assertion/base_dsl.rb +75 -0
  11. data/lib/assertion/dsl.rb +77 -0
  12. data/lib/assertion/guard.rb +1 -30
  13. data/lib/assertion/guard_dsl.rb +39 -0
  14. data/lib/assertion/{transprocs/inflector.rb → inflector.rb} +0 -0
  15. data/lib/assertion/inversion.rb +3 -3
  16. data/lib/assertion/inverter.rb +2 -2
  17. data/lib/assertion/translator.rb +95 -0
  18. data/lib/assertion/version.rb +1 -1
  19. data/spec/integration/guard_spec.rb +1 -1
  20. data/spec/shared/en.yml +4 -4
  21. data/spec/unit/assertion/base_spec.rb +124 -14
  22. data/spec/unit/assertion/guard_spec.rb +37 -13
  23. data/spec/unit/assertion/{transprocs/inflector → inflector}/to_path_spec.rb +0 -0
  24. data/spec/unit/assertion/{transprocs/inflector → inflector}/to_snake_path_spec.rb +0 -0
  25. data/spec/unit/assertion/{transprocs/inflector → inflector}/to_snake_spec.rb +0 -0
  26. data/spec/unit/assertion/{exceptions/invalid_error_spec.rb → invalid_error_spec.rb} +0 -0
  27. data/spec/unit/assertion/inversion_spec.rb +5 -5
  28. data/spec/unit/assertion/inverter_spec.rb +2 -2
  29. data/spec/unit/assertion/translator_spec.rb +57 -0
  30. metadata +23 -20
  31. data/lib/assertion/attributes.rb +0 -54
  32. data/lib/assertion/messages.rb +0 -65
  33. data/lib/assertion/transprocs/list.rb +0 -30
  34. data/spec/unit/assertion/attributes_spec.rb +0 -97
  35. data/spec/unit/assertion/messages_spec.rb +0 -41
  36. data/spec/unit/assertion/transprocs/list/symbolize_spec.rb +0 -19
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d9fbab813ababd1c0224226d00aa2144dc682b6d
4
- data.tar.gz: 30d48a47833d65670e480b29196caa1aec783040
3
+ metadata.gz: 5cd46bf51eb8357641668f5f3762bf0fb5a0dbd7
4
+ data.tar.gz: 048a9c8f663aed972a4ee8e0ded514a7310a6742
5
5
  SHA512:
6
- metadata.gz: 521a9d990f59e2236344f712df369f211e3635c9f6828da4883135962b2f6e51a0e006e0fc18612d9e3474c24a5bdf3681585982c665eec6e8caa21de58961e4
7
- data.tar.gz: 3e3dacb62cc68dd317e674cbad9424607d4b4a12638445e54a41534f747f9c0af7c07edf1a13728d9b2223349b713f72d716cb9f6cb7525dd7f8e332b46a8978
6
+ metadata.gz: ec1de6dade9383cd776f497fe0bfbaf452a1a60ae3ed5b341fe8741fbf891a4f43bf3ac08cbfa7fa1541aae1ce71937ba350cbd3932ae0f1975a01f7ac859e79
7
+ data.tar.gz: 7fe6666427458a4291988444e4a333219297cde70cd1aa592d2d3624625d8bc0c39bb647eb25bc1c49c8a1963e609f927443ef6ba51d4cd35ee45a0e0c4d5a81
@@ -1,3 +1,20 @@
1
+ ## v0.2.0 2015-06-22
2
+
3
+ ### Changed (backward-incompatible!)
4
+
5
+ * Renamed translation keys from `:right`/`:wrong` to `:truthy`/`:falsey` for consistency (nepalez)
6
+
7
+ ### Internal
8
+
9
+ * Attributes are added to `Base` and `Guard` at the moment of definition,
10
+ instead of the initializations (addresses efficiency issue #1) (nepalez)
11
+ * `Translator` replaced `Messages` and `List`(transproc) to invert dependency
12
+ of `Base` from translations. (nepalez)
13
+ * Class-level DSL features are extracted from `Assertion`, `Base` and `Guard`
14
+ to the separate modules `DSL`, `BaseDSL`, `GuardDSL` correspondingly (nepalez)
15
+
16
+ [Compare v0.1.0...v0.2.0](https://github.com/nepalez/assertion/compare/v0.1.0...v0.2.0)
17
+
1
18
  ## v0.1.0 2015-06-20
2
19
 
3
20
  ### Added
data/Gemfile CHANGED
@@ -5,5 +5,3 @@ gemspec
5
5
  group :metrics do
6
6
  gem "hexx-suit", "~> 2.2" if RUBY_ENGINE == "ruby"
7
7
  end
8
-
9
- gem "transproc", github: "solnic/transproc", branch: "registry"
data/Guardfile CHANGED
@@ -4,15 +4,15 @@ guard :rspec, cmd: "bundle exec rspec" do
4
4
 
5
5
  watch(%r{^spec/.+_spec\.rb$})
6
6
 
7
- watch(%r{^lib/assertion/(.+)\.rb}) do |m|
8
- "spec/unit/assertion/#{m[1]}_spec.rb"
7
+ watch(%r{^lib/(.+)\.rb}) do |m|
8
+ "spec/unit/#{m[1]}_spec.rb"
9
9
  end
10
10
 
11
- watch(%r{^lib/assertion/transproc.rb}) do
12
- "spec/unit/assertion/transproc"
13
- end
11
+ watch("lib/assertion/dsl.rb") { "spec/unit/assertion_spec.rb" }
12
+ watch("lib/assertion/base_dsl.rb") { "spec/unit/assertion/base_spec.rb" }
13
+ watch("lib/assertion/guard_dsl.rb") { "spec/unit/assertion/guard_spec.rb" }
14
+ watch("lib/assertion/inflector.rb") { "spec/unit/assertion/inflector" }
14
15
 
15
- watch("lib/assertion.rb") { "spec" }
16
16
  watch("spec/spec_helper.rb") { "spec" }
17
17
 
18
18
  end # guard :rspec
data/README.md CHANGED
@@ -24,6 +24,8 @@ The primary goal of the gem is to make assertions about <decoupled> objects.
24
24
 
25
25
  No `ActiveSupport`, no mutation of any instances.
26
26
 
27
+ [About](https://github.com/mbj/mutant/issues/356) 100% [mutant]-covered.
28
+
27
29
  ### Basic Usage
28
30
 
29
31
  Define an assertion by inheriting it from the `Assertion::Base` class with attributes to which it should be applied.
@@ -49,7 +51,7 @@ IsAdult = Assertion.about :age, :name do
49
51
  end
50
52
  ```
51
53
 
52
- Define translations to describe both the *right* and *wrong* states of the assertion.
54
+ Define translations to describe both the *truthy* and *falsey* states of the assertion.
53
55
 
54
56
  All the attributes are available in translations (that's why we declared the `name` as an attribute):
55
57
 
@@ -59,8 +61,8 @@ All the attributes are available in translations (that's why we declared the `na
59
61
  en:
60
62
  assertion:
61
63
  is_adult:
62
- right: "%{name} is already an adult (age %{age})"
63
- wrong: "%{name} is a child yet (age %{age})"
64
+ truthy: "%{name} is already an adult (age %{age})"
65
+ falsey: "%{name} is a child yet (age %{age})"
64
66
  ```
65
67
 
66
68
  Check a state of an assertion for some argument(s), using class method `[]`:
@@ -110,8 +112,8 @@ end
110
112
  en:
111
113
  assertion:
112
114
  is_male:
113
- right: "%{name} is a male"
114
- wrong: "%{name} is a female"
115
+ truthy: "%{name} is a male"
116
+ falsey: "%{name} is a female"
115
117
  ```
116
118
 
117
119
  Use method `&` (or its aliases `+` or `>>`) to compose assertion states:
@@ -166,7 +168,7 @@ Naming Convention
166
168
 
167
169
  This is not necessary, but for verbosity you could follow the rules:
168
170
 
169
- * use the prefix `Is` for assertions (like `IsAdult`)
171
+ * use the prefixex `Is` (`Are`) for assertions (like `IsAdult`, `AreConsistent` etc.)
170
172
  * use the suffix `Only` for guards (like `AdultOnly`)
171
173
 
172
174
  Edge Cases
@@ -18,7 +18,7 @@ Gem::Specification.new do |gem|
18
18
 
19
19
  gem.required_ruby_version = "~> 1.9"
20
20
 
21
- gem.add_runtime_dependency "transproc", "~> 0.2"
21
+ gem.add_runtime_dependency "transproc", "~> 0.2", "> 0.2.3"
22
22
  gem.add_runtime_dependency "i18n", "~> 0.7"
23
23
 
24
24
  gem.add_development_dependency "hexx-rspec", "~> 0.4"
@@ -1,2 +1,2 @@
1
1
  ---
2
- minimum_score: 6
2
+ minimum_score: 7
@@ -2,109 +2,58 @@
2
2
 
3
3
  require "transproc"
4
4
 
5
- require_relative "assertion/transprocs/inflector"
6
- require_relative "assertion/transprocs/list"
7
-
5
+ require_relative "assertion/inflector"
8
6
  require_relative "assertion/invalid_error"
9
- require_relative "assertion/attributes"
10
- require_relative "assertion/messages"
11
-
7
+ require_relative "assertion/translator"
12
8
  require_relative "assertion/state"
9
+ require_relative "assertion/base_dsl"
13
10
  require_relative "assertion/base"
14
11
  require_relative "assertion/inversion"
15
12
  require_relative "assertion/inverter"
13
+ require_relative "assertion/guard_dsl"
16
14
  require_relative "assertion/guard"
15
+ require_relative "assertion/dsl"
17
16
 
18
- # The module allows declaring assertions (assertions) about various objects,
19
- # and apply (validate) them to concrete data.
17
+ # The module declares:
18
+ #
19
+ # * assertions about objects
20
+ # * guards (validations) for objects
20
21
  #
21
- # @example
22
+ # @example Assertion
22
23
  # # config/locales/en.yml
23
24
  # # ---
24
25
  # # en:
25
26
  # # assertion:
26
- # # adult:
27
- # # right: "%{name} is an adult (age %{age})"
28
- # # wrong: "%{name} is a child (age %{age})"
27
+ # # is_adult:
28
+ # # truthy: "%{name} is an adult (age %{age})"
29
+ # # falsey: "%{name} is a child (age %{age})"
29
30
  #
30
- # Adult = Assertion.about :name, :age do
31
+ # IsAdult = Assertion.about :name, :age do
31
32
  # age >= 18
32
33
  # end
33
34
  #
34
- # joe = { name: 'Joe', age: 13 }
35
- # Adult[joe].validate!
35
+ # joe = OpenStruct.new(name: 'Joe', age: 13)
36
+ # IsAdult[joe.to_h].validate!
36
37
  # # => #<Assertion::InvalidError @messages=["Joe is a child (age 13)"]>
37
38
  #
38
- # jane = { name: 'Jane', age: 22 }
39
- # Adult.not[jane].validate!
39
+ # jane = OpenStruct.new(name: 'Jane', age: 22)
40
+ # IsAdult.not[jane.to_h].validate!
40
41
  # # => #<Assertion::InvalidError @messages=["Jane is an adult (age 22)"]
41
42
  #
43
+ # @example Guard
44
+ # AdultOnly = Assertion.guards :user do
45
+ # IsAdult[user.to_h]
46
+ # end
47
+ #
48
+ # AdultOnly[joe]
49
+ # # => #<Assertion::InvalidError @messages=["Joe is a child (age 13)"]>
50
+ # AdultOnly[jane]
51
+ # # => #<OpenStruct @name="Jane", @age=22>
52
+ #
42
53
  # @api public
43
54
  #
44
55
  module Assertion
45
56
 
46
- # Builds the subclass of `Assertion::Base` with predefined `attributes`
47
- # and implementation of the `#check` method.
48
- #
49
- # @example
50
- # IsMan = Assertion.about :age, :gender do
51
- # (age >= 18) && (gender == :male)
52
- # end
53
- #
54
- # # This is the same as:
55
- # class IsMan < Assertion::Base
56
- # attribute :age, :gender
57
- #
58
- # def check
59
- # (age >= 18) && (gender == :male)
60
- # end
61
- # end
62
- #
63
- # @param [Symbol, Array<Symbol>] attributes
64
- # The list of attributes for the new assertion
65
- # @param [Proc] block
66
- # The content for the `check` method
67
- #
68
- # @return [Class] The specific assertion class
69
- #
70
- def self.about(*attributes, &block)
71
- klass = Class.new(Base)
72
- klass.public_send(:attribute, attributes)
73
- klass.__send__(:define_method, :check, &block) if block_given?
74
-
75
- klass
76
- end
77
-
78
- # Builds the subclass of `Assertion::Guard` with given attribute
79
- # (alias for the `object`) and implementation of the `#state` method.
80
- #
81
- # @example
82
- # VoterOnly = Assertion.guards :user do
83
- # IsAdult[user.attributes] & IsCitizen[user.attributes]
84
- # end
85
- #
86
- # # This is the same as:
87
- # class VoterOnly < Assertion::Guard
88
- # alias_method :user, :object
89
- #
90
- # def state
91
- # IsAdult[user.attributes] & IsCitizen[user.attributes]
92
- # end
93
- # end
94
- #
95
- # @param [Symbol] attribute
96
- # The alias for the `object` attribute
97
- # @param [Proc] block
98
- # The content for the `state` method
99
- #
100
- # @return [Class] The specific guard class
101
- #
102
- def self.guards(attribute = nil, &block)
103
- klass = Class.new(Guard)
104
- klass.public_send(:attribute, attribute) if attribute
105
- klass.__send__(:define_method, :state, &block) if block_given?
106
-
107
- klass
108
- end
57
+ extend DSL
109
58
 
110
59
  end # module Assertion
@@ -19,7 +19,7 @@ module Assertion
19
19
  # * [.not] can be used to provide the assertion opposite to the initial one.
20
20
  #
21
21
  # @example
22
- # class Adult < Assertion::Base
22
+ # class IsAdult < Assertion::Base
23
23
  # attribute :name, :age
24
24
  #
25
25
  # def check
@@ -27,70 +27,32 @@ module Assertion
27
27
  # end
28
28
  # end
29
29
  #
30
- # child = Adult.not
30
+ # child = IsAdult.not
31
31
  #
32
32
  # jane = { name: "Jane", age: 12 }
33
- # Adult[jane].valid? # => false
33
+ # IsAdult[jane].valid? # => false
34
34
  # child[jane].valid? # => true
35
35
  #
36
36
  class Base
37
37
 
38
- extend Attributes
39
- include Messages
38
+ extend BaseDSL
40
39
 
41
- # Class DSL
40
+ # The translator of states for the current class
42
41
  #
43
- class << self
44
-
45
- # Initializes an assertion with some attributes (data) and then calls it
46
- #
47
- # @param [Hash] hash
48
- #
49
- # @return [Assertion::State]
50
- # The object that describes the state of the assertion
51
- # applied to given attributes
52
- #
53
- def [](hash = {})
54
- new(hash).call
55
- end
56
-
57
- # Initializes the intermediate inverter with `new` and `[]` methods
58
- #
59
- # The inverter can be used to initialize the assertion, that describes
60
- # just the opposite statement to the current one
61
- #
62
- # @example
63
- # Adult = Assertion.about :name, :age do
64
- # age >= 18
65
- # end
66
- #
67
- # joe = { name: 'Joe', age: 19 }
68
- #
69
- # Adult[joe].valid? # => true
70
- # Adult.not[joe].valid? # => false
71
- #
72
- # @return [Assertion::Inverter]
73
- #
74
- def not
75
- Inverter.new(self)
76
- end
77
-
78
- private
79
-
80
- def __forbidden_attributes__
81
- [:check]
82
- end
83
-
84
- end # eigenclass
42
+ # @return [Assertion::Translator]
43
+ #
44
+ def self.translator
45
+ @translator ||= Translator.new(self)
46
+ end
85
47
 
86
48
  # @!attribute [r] attributes
87
49
  #
88
50
  # @example
89
- # Adult = Assertion.about :name, :age do
51
+ # IsAdult = Assertion.about :name, :age do
90
52
  # age >= 18
91
53
  # end
92
54
  #
93
- # adult = Adult[name: "Joe", age: 15, gender: :male]
55
+ # adult = IsAdult[name: "Joe", age: 15, gender: :male]
94
56
  # adult.attributes # => { name: "Joe", age: 15 }
95
57
  #
96
58
  # @return [Hash]
@@ -98,29 +60,41 @@ module Assertion
98
60
  #
99
61
  attr_reader :attributes
100
62
 
63
+ # @!scope class
64
+ # @!method new(args = {})
65
+ # Initializes an assertion for the current object
66
+ #
67
+ # @param [Hash] args The arguments to check
68
+ #
69
+ # @return [Assertion::Base]
70
+
101
71
  # @private
102
72
  def initialize(args = {})
103
- @attributes = {}
104
- self.class.attributes.each { |name| __set_attribute__ name, args[name] }
73
+ keys = self.class.attributes
74
+ @attributes = Hash[keys.zip(args.values_at(*keys))]
105
75
  freeze
106
76
  end
107
77
 
108
- # Calls the assertion checkup and returns the state of the assertion having
109
- # been applied to the current attributes
78
+ # Returns the message describing the current state
110
79
  #
111
- # @return [Check::State]
112
- # The state of the assertion being applied to its attributes
80
+ # @param [Boolean] state The state to describe
113
81
  #
114
- def call
115
- state = check
116
- State.new state, message(false)
82
+ # @return [String]
83
+ #
84
+ def message(state)
85
+ self.class.translator.call(state, attributes)
117
86
  end
118
87
 
119
- private
120
-
121
- def __set_attribute__(name, value)
122
- attributes[name] = value
123
- singleton_class.__send__(:define_method, name) { value }
88
+ # Calls the assertion checkup and returns the resulting state
89
+ #
90
+ # The state is a unified composable object, unaware of the
91
+ # structure and attributes of the specific assertion.
92
+ #
93
+ # @return [Assertion::State]
94
+ # The state of the assertion being applied to its attributes
95
+ #
96
+ def call
97
+ State.new check, message(false)
124
98
  end
125
99
 
126
100
  end # class Base
@@ -0,0 +1,75 @@
1
+ # encoding: utf-8
2
+
3
+ module Assertion
4
+
5
+ # Provides methods to describe and apply assertions
6
+ #
7
+ module BaseDSL
8
+
9
+ # Initializes an assertion with some attributes (data) and then calls it
10
+ #
11
+ # @param (see Assertion::Base.new)
12
+ #
13
+ # @return (see Assertion::Base#call)
14
+ #
15
+ def [](args = nil)
16
+ new(args).call
17
+ end
18
+
19
+ # Initializes the intermediate inverter with `new` and `[]` methods
20
+ #
21
+ # The inverter can be used to initialize the assertion, that describes
22
+ # just the opposite statement to the current one
23
+ #
24
+ # @example
25
+ # IsAdult = Assertion.about :name, :age do
26
+ # age >= 18
27
+ # end
28
+ #
29
+ # joe = { name: 'Joe', age: 19 }
30
+ #
31
+ # IsAdult[joe].valid? # => true
32
+ # IsAdult.not[joe].valid? # => false
33
+ #
34
+ # @return [Assertion::Inverter]
35
+ #
36
+ def not
37
+ Inverter.new(self)
38
+ end
39
+
40
+ # List of attributes defined for the assertion
41
+ #
42
+ # @return [Array<Symbol>]
43
+ #
44
+ def attributes
45
+ @attributes ||= []
46
+ end
47
+
48
+ # Declares new attribute(s) by name(s)
49
+ #
50
+ # @param [#to_sym, Array<#to_sym>] names
51
+ #
52
+ # @return [undefined]
53
+ #
54
+ # @raise [NameError]
55
+ # When an instance method with one of given names is already exist.
56
+ #
57
+ def attribute(*names)
58
+ names.flatten.map(&:to_sym).each(&method(:__add_attribute__))
59
+ end
60
+
61
+ private
62
+
63
+ def __add_attribute__(name)
64
+ __check_attribute__(name)
65
+ attributes << define_method(name) { attributes[name] }
66
+ end
67
+
68
+ def __check_attribute__(name)
69
+ return unless (instance_methods << :check).include? name
70
+ fail NameError.new "#{self}##{name} is already defined"
71
+ end
72
+
73
+ end # module BaseDSL
74
+
75
+ end # module Assertion