assertion 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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