assertion 0.0.1

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 (55) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +2 -0
  3. data/.gitignore +9 -0
  4. data/.metrics +9 -0
  5. data/.rspec +2 -0
  6. data/.rubocop.yml +2 -0
  7. data/.travis.yml +19 -0
  8. data/.yardopts +3 -0
  9. data/Gemfile +9 -0
  10. data/Guardfile +18 -0
  11. data/LICENSE +21 -0
  12. data/README.md +222 -0
  13. data/Rakefile +29 -0
  14. data/assertion.gemspec +27 -0
  15. data/config/metrics/STYLEGUIDE +230 -0
  16. data/config/metrics/cane.yml +5 -0
  17. data/config/metrics/churn.yml +6 -0
  18. data/config/metrics/flay.yml +2 -0
  19. data/config/metrics/metric_fu.yml +15 -0
  20. data/config/metrics/reek.yml +1 -0
  21. data/config/metrics/roodi.yml +24 -0
  22. data/config/metrics/rubocop.yml +72 -0
  23. data/config/metrics/saikuro.yml +3 -0
  24. data/config/metrics/simplecov.yml +6 -0
  25. data/config/metrics/yardstick.yml +37 -0
  26. data/lib/assertion.rb +79 -0
  27. data/lib/assertion/base.rb +186 -0
  28. data/lib/assertion/exceptions/invalid_error.rb +36 -0
  29. data/lib/assertion/exceptions/name_error.rb +29 -0
  30. data/lib/assertion/exceptions/not_implemented_error.rb +29 -0
  31. data/lib/assertion/inversion.rb +64 -0
  32. data/lib/assertion/inverter.rb +62 -0
  33. data/lib/assertion/state.rb +79 -0
  34. data/lib/assertion/transprocs/i18n.rb +55 -0
  35. data/lib/assertion/transprocs/inflector.rb +39 -0
  36. data/lib/assertion/transprocs/list.rb +30 -0
  37. data/lib/assertion/version.rb +9 -0
  38. data/spec/integration/assertion_spec.rb +50 -0
  39. data/spec/integration/en.yml +10 -0
  40. data/spec/spec_helper.rb +12 -0
  41. data/spec/unit/assertion/base_spec.rb +221 -0
  42. data/spec/unit/assertion/exceptions/invalid_error_spec.rb +40 -0
  43. data/spec/unit/assertion/exceptions/name_error_spec.rb +26 -0
  44. data/spec/unit/assertion/exceptions/not_implemented_error_spec.rb +26 -0
  45. data/spec/unit/assertion/inversion_spec.rb +89 -0
  46. data/spec/unit/assertion/inverter_spec.rb +80 -0
  47. data/spec/unit/assertion/state_spec.rb +224 -0
  48. data/spec/unit/assertion/transprocs/i18n/to_scope_spec.rb +19 -0
  49. data/spec/unit/assertion/transprocs/i18n/translate_spec.rb +28 -0
  50. data/spec/unit/assertion/transprocs/inflector/to_path_spec.rb +19 -0
  51. data/spec/unit/assertion/transprocs/inflector/to_snake_path_spec.rb +19 -0
  52. data/spec/unit/assertion/transprocs/inflector/to_snake_spec.rb +19 -0
  53. data/spec/unit/assertion/transprocs/list/symbolize_spec.rb +19 -0
  54. data/spec/unit/assertion_spec.rb +65 -0
  55. metadata +171 -0
@@ -0,0 +1,5 @@
1
+ ---
2
+ abc_max: "10"
3
+ line_length: "80"
4
+ no_doc: "y"
5
+ no_readme: "y"
@@ -0,0 +1,6 @@
1
+ ---
2
+ ignore_files:
3
+ - spec
4
+ - config
5
+ minimum_churn_count: 0
6
+ start_date: "1 year ago"
@@ -0,0 +1,2 @@
1
+ ---
2
+ minimum_score: 6
@@ -0,0 +1,15 @@
1
+ ---
2
+ folders: # The list of folders to be used by any metric.
3
+ - lib
4
+ - app
5
+ metrics: # The list of allowed metrics. The other metrics are disabled.
6
+ - cane
7
+ - churn
8
+ - flay
9
+ - flog
10
+ - reek
11
+ - roodi
12
+ - saikuro
13
+ format: html
14
+ output: tmp/metric_fu
15
+ verbose: false
@@ -0,0 +1 @@
1
+ ---
@@ -0,0 +1,24 @@
1
+ ---
2
+ AssignmentInConditionalCheck:
3
+ CaseMissingElseCheck:
4
+ ClassLineCountCheck:
5
+ line_count: 300
6
+ ClassNameCheck:
7
+ pattern: !ruby/regexp /^[A-Z][a-zA-Z0-9]*$/
8
+ ClassVariableCheck:
9
+ CyclomaticComplexityBlockCheck:
10
+ complexity: 4
11
+ CyclomaticComplexityMethodCheck:
12
+ complexity: 8
13
+ EmptyRescueBodyCheck:
14
+ ForLoopCheck:
15
+ MethodLineCountCheck:
16
+ line_count: 20
17
+ MethodNameCheck:
18
+ pattern: !ruby/regexp /^[\||\^|\&|\!]$|^[_a-z<>=\[|+-\/\*`]+[_a-z0-9_<>=~@\[\]]*[=!\?]?$/
19
+ ModuleLineCountCheck:
20
+ line_count: 300
21
+ ModuleNameCheck:
22
+ pattern: !ruby/regexp /^[A-Z][a-zA-Z0-9]*$/
23
+ ParameterNumberCheck:
24
+ parameter_count: 5
@@ -0,0 +1,72 @@
1
+ ---
2
+ # settings added by the 'hexx-suit' module
3
+ # output: "tmp/rubocop"
4
+ # format: "html"
5
+
6
+ AllCops:
7
+ Exclude:
8
+ - '**/db/schema.rb'
9
+
10
+ Lint/HandleExceptions:
11
+ Exclude:
12
+ - '**/*_spec.rb'
13
+
14
+ Lint/RescueException:
15
+ Exclude:
16
+ - '**/*_spec.rb'
17
+
18
+ Style/AccessorMethodName:
19
+ Exclude:
20
+ - '**/*_spec.rb'
21
+
22
+ Style/AsciiComments:
23
+ Enabled: false
24
+
25
+ Style/ClassAndModuleChildren:
26
+ Enabled: false
27
+
28
+ Style/Documentation:
29
+ Enabled: false
30
+
31
+ Style/EmptyLinesAroundBlockBody:
32
+ Enabled: false
33
+
34
+ Style/EmptyLinesAroundClassBody:
35
+ Enabled: false
36
+
37
+ Style/EmptyLinesAroundMethodBody:
38
+ Enabled: false
39
+
40
+ Style/EmptyLinesAroundModuleBody:
41
+ Enabled: false
42
+
43
+ Style/EmptyLineBetweenDefs:
44
+ Enabled: false
45
+
46
+ Style/FileName:
47
+ Enabled: false
48
+
49
+ Style/RaiseArgs:
50
+ EnforcedStyle: compact
51
+
52
+ Style/SingleLineMethods:
53
+ Exclude:
54
+ - '**/*_spec.rb'
55
+
56
+ Style/SingleSpaceBeforeFirstArg:
57
+ Enabled: false
58
+
59
+ Style/SpecialGlobalVars:
60
+ Exclude:
61
+ - '**/Gemfile'
62
+ - '**/*.gemspec'
63
+
64
+ Style/StringLiterals:
65
+ EnforcedStyle: double_quotes
66
+
67
+ Style/StringLiteralsInInterpolation:
68
+ EnforcedStyle: double_quotes
69
+
70
+ Style/TrivialAccessors:
71
+ Exclude:
72
+ - '**/*_spec.rb'
@@ -0,0 +1,3 @@
1
+ ---
2
+ warn_cyclo: 4
3
+ error_cyclo: 6
@@ -0,0 +1,6 @@
1
+ ---
2
+ output: tmp/coverage
3
+ filters: # The list of paths to be excluded from coverage checkup
4
+ - "spec/"
5
+ - "config/"
6
+ groups: []
@@ -0,0 +1,37 @@
1
+ ---
2
+ # Settings added by the 'hexx-suit' gem
3
+ output: "tmp/yardstick/output.log"
4
+ path: "lib/**/*.rb"
5
+ rules:
6
+ ApiTag::Presence:
7
+ enabled: true
8
+ exclude: []
9
+ ApiTag::Inclusion:
10
+ enabled: true
11
+ exclude: []
12
+ ApiTag::ProtectedMethod:
13
+ enabled: true
14
+ exclude: []
15
+ ApiTag::PrivateMethod:
16
+ enabled: false
17
+ exclude: []
18
+ ExampleTag:
19
+ enabled: true
20
+ exclude: []
21
+ ReturnTag:
22
+ enabled: true
23
+ exclude: []
24
+ Summary::Presence:
25
+ enabled: true
26
+ exclude: []
27
+ Summary::Length:
28
+ enabled: true
29
+ exclude: []
30
+ Summary::Delimiter:
31
+ enabled: true
32
+ exclude: []
33
+ Summary::SingleLine:
34
+ enabled: true
35
+ exclude: []
36
+ threshold: 100
37
+ verbose: false
@@ -0,0 +1,79 @@
1
+ # encoding: utf-8
2
+
3
+ require "transproc"
4
+ require "i18n"
5
+
6
+ require_relative "assertion/transprocs/inflector"
7
+ require_relative "assertion/transprocs/i18n"
8
+ require_relative "assertion/transprocs/list"
9
+
10
+ require_relative "assertion/exceptions/name_error"
11
+ require_relative "assertion/exceptions/not_implemented_error"
12
+ require_relative "assertion/exceptions/invalid_error"
13
+
14
+ require_relative "assertion/state"
15
+ require_relative "assertion/base"
16
+ require_relative "assertion/inversion"
17
+ require_relative "assertion/inverter"
18
+
19
+ # The module allows declaring assertions (assertions) about various objects,
20
+ # and apply (validate) them to concrete data.
21
+ #
22
+ # @example
23
+ # # config/locales/en.yml
24
+ # # ---
25
+ # # en:
26
+ # # assertion:
27
+ # # adult:
28
+ # # right: "%{name} is an adult (age %{age})"
29
+ # # wrong: "%{name} is a child (age %{age})"
30
+ #
31
+ # Adult = Assertion.about :name, :age do
32
+ # age >= 18
33
+ # end
34
+ #
35
+ # joe = { name: 'Joe', age: 13 }
36
+ # Adult[joe].validate!
37
+ # # => #<Assertion::InvalidError @messages=["Joe is a child (age 13)"]>
38
+ #
39
+ # jane = { name: 'Jane', age: 22 }
40
+ # Adult.not[jane].validate!
41
+ # # => #<Assertion::InvalidError @messages=["Jane is an adult (age 22)"]
42
+ #
43
+ # @api public
44
+ #
45
+ module Assertion
46
+
47
+ # Builds the subclass of `Assertion::Base` with predefined `attributes`
48
+ # and implementation of the `#check` method.
49
+ #
50
+ # @example
51
+ # IsMan = Assertion.about :age, :gender do
52
+ # (age >= 18) && (gender == :male)
53
+ # end
54
+ #
55
+ # # This is the same as:
56
+ # class IsMan < Assertion::Base
57
+ # attribute :age, :gender
58
+ #
59
+ # def check
60
+ # (age >= 18) && (gender == :male)
61
+ # end
62
+ # end
63
+ #
64
+ # @param [Symbol, Array<Symbol>] attributes
65
+ # The list of attributes for the new assertion
66
+ # @param [Proc] block
67
+ # The content for the `check` method
68
+ #
69
+ # @return [Assertion::Base]
70
+ #
71
+ def self.about(*attributes, &block)
72
+ klass = Class.new(Base)
73
+ klass.public_send(:attribute, attributes)
74
+ klass.__send__(:define_method, :check, &block) if block_given?
75
+
76
+ klass
77
+ end
78
+
79
+ end # module Assertion
@@ -0,0 +1,186 @@
1
+ # encoding: utf-8
2
+
3
+ module Assertion
4
+
5
+ # The base class for assertions about some attributes
6
+ #
7
+ # Every assertion should define a list of attributes to be checked and
8
+ # the [#check] method to apply the assertion to those attributes
9
+ #
10
+ # The assertion `call` method provides the object, describing the state
11
+ # of the assertion applied to its attributes. The provided state carries
12
+ # the result of the checkup and a corresponding <error> message.
13
+ # Later it can be composed with other states to provide complex validation.
14
+ #
15
+ # The class DSL also defines shortcuts:
16
+ #
17
+ # * [.[]] can be used to initialize the assertion for given attributes and
18
+ # then apply it immediately with creation of the corresponding state.
19
+ # * [.not] can be used to provide the assertion opposite to the initial one.
20
+ #
21
+ # @example
22
+ # class Adult < Assertion::Base
23
+ # attribute :name, :age
24
+ #
25
+ # def check
26
+ # age >= 18
27
+ # end
28
+ # end
29
+ #
30
+ # child = Adult.not
31
+ #
32
+ # jane = { name: "Jane", age: 12 }
33
+ # Adult[jane].valid? # => false
34
+ # child[jane].valid? # => true
35
+ #
36
+ class Base
37
+
38
+ # Class DSL
39
+ #
40
+ class << self
41
+
42
+ # List of attributes, defined for the class
43
+ #
44
+ # @return [Array<Symbol>]
45
+ #
46
+ def attributes
47
+ @attributes ||= []
48
+ end
49
+
50
+ # Adds a new attribute or a list of attributes to the class
51
+ #
52
+ # @param [Symbol, Array<Symbol>] names
53
+ #
54
+ # @return [undefined]
55
+ #
56
+ # @raise [Assertion::NameError]
57
+ # When the name is already used by instance attribute
58
+ #
59
+ def attribute(*names)
60
+ @attributes = List[:symbolize][attributes + names]
61
+ __check__
62
+ end
63
+
64
+ # Initializes a assertion with some attributes (data) and then calls it
65
+ #
66
+ # @param [Hash] hash
67
+ #
68
+ # @return [Assertion::State]
69
+ # The object that describes the state of the assertion
70
+ # applied to given attributes
71
+ #
72
+ def [](hash = {})
73
+ new(hash).call
74
+ end
75
+
76
+ # Initializes the intermediate inverter with `new` and `[]` methods
77
+ #
78
+ # The inverter can be used to initialize the assertion, that describes
79
+ # just the opposite statement to the current one
80
+ #
81
+ # @example
82
+ # Adult = Assertion.about :name, :age do
83
+ # age >= 18
84
+ # end
85
+ #
86
+ # joe = { name: 'Joe', age: 19 }
87
+ #
88
+ # Adult[joe].valid? # => true
89
+ # Adult.not[joe].valid? # => false
90
+ #
91
+ # @return [Assertion::Inverter]
92
+ #
93
+ def not
94
+ Inverter.new(self)
95
+ end
96
+
97
+ private
98
+
99
+ # Checks if all the attributes have valid names
100
+ def __check__
101
+ wrong = attributes & instance_methods
102
+ fail(NameError.new wrong) if wrong.any?
103
+ end
104
+
105
+ end # eigenclass
106
+
107
+ # @!attribute [r] attributes
108
+ #
109
+ # @example
110
+ # Adult = Assertion.about :name, :age do
111
+ # age >= 18
112
+ # end
113
+ #
114
+ # adult = Adult[name: "Joe", age: 15, gender: :male]
115
+ # adult.attributes # => { name: "Joe", age: 15 }
116
+ #
117
+ # @return [Hash]
118
+ # The hash of the allowed attributes having been initialized
119
+ #
120
+ attr_reader :attributes
121
+
122
+ # @private
123
+ def initialize(args = {})
124
+ @attributes = {}
125
+ self.class.attributes.each { |name| __set_attribute__ name, args[name] }
126
+ freeze
127
+ end
128
+
129
+ # Returns the translated message about the current state of the assertion
130
+ # applied to its attributes
131
+ #
132
+ # @param [Symbol] state The state to be described
133
+ #
134
+ # @return [String] The message
135
+ #
136
+ def message(state = nil)
137
+ I18n[:translate, __scope__, attributes][state ? :right : :wrong]
138
+ end
139
+
140
+ # Checks whether the assertion is right for the current attributes
141
+ #
142
+ # @return [Boolean]
143
+ #
144
+ # @raise [Check::NotImplementedError]
145
+ # When the [#check] method hasn't been implemented
146
+ #
147
+ def check
148
+ fail NotImplementedError.new(self.class, :check)
149
+ end
150
+
151
+ # Calls the assertion checkup and returns the state of the assertion having
152
+ # been applied to the current attributes
153
+ #
154
+ # @return [Check::State]
155
+ # The state of the assertion being applied to its attributes
156
+ #
157
+ def call
158
+ state = check
159
+ State.new state, message
160
+ end
161
+
162
+ private
163
+
164
+ # The scope for translating messages using the `I18n` module
165
+ #
166
+ # @return [Array<Symbol>]
167
+ #
168
+ def __scope__
169
+ I18n[:scope][self.class.name]
170
+ end
171
+
172
+ # Defines the object attribute and assigns given value to it
173
+ #
174
+ # @param [Symbol] name The name of the attribute
175
+ # @param [Object] value The value of the attribute
176
+ #
177
+ # @return [undefined]
178
+ #
179
+ def __set_attribute__(name, value)
180
+ attributes[name] = value
181
+ singleton_class.__send__(:define_method, name) { value }
182
+ end
183
+
184
+ end # class Base
185
+
186
+ end # module Assertion