assertion 0.1.0 → 0.2.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.
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