veritas 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. data/.travis.yml +10 -4
  2. data/Gemfile +1 -1
  3. data/TODO +10 -0
  4. data/config/flay.yml +1 -1
  5. data/config/flog.yml +1 -1
  6. data/config/roodi.yml +2 -2
  7. data/config/site.reek +2 -1
  8. data/lib/veritas.rb +2 -2
  9. data/lib/veritas/algebra/join.rb +13 -91
  10. data/lib/veritas/algebra/product.rb +4 -1
  11. data/lib/veritas/attribute.rb +1 -1
  12. data/lib/veritas/function/connective/negation.rb +13 -1
  13. data/lib/veritas/function/numeric/absolute.rb +5 -19
  14. data/lib/veritas/function/numeric/unary.rb +3 -0
  15. data/lib/veritas/function/numeric/unary_minus.rb +5 -19
  16. data/lib/veritas/function/numeric/unary_plus.rb +5 -21
  17. data/lib/veritas/function/proposition.rb +3 -28
  18. data/lib/veritas/function/string/length.rb +0 -1
  19. data/lib/veritas/function/unary.rb +47 -0
  20. data/lib/veritas/relation.rb +16 -2
  21. data/lib/veritas/relation/header.rb +48 -41
  22. data/lib/veritas/relation/operation/order/direction.rb +13 -1
  23. data/lib/veritas/relation/operation/order/direction_set.rb +9 -198
  24. data/lib/veritas/relation/operation/reverse.rb +0 -2
  25. data/lib/veritas/support/comparator.rb +1 -1
  26. data/lib/veritas/support/evaluator.rb +3 -0
  27. data/lib/veritas/support/immutable.rb +33 -8
  28. data/lib/veritas/tuple.rb +8 -6
  29. data/lib/veritas/version.rb +1 -1
  30. data/spec/integration/veritas/algebra/projection_spec.rb +1 -1
  31. data/spec/integration/veritas/relation/efficient_enumerable_spec.rb +40 -15
  32. data/spec/rcov.opts +1 -0
  33. data/spec/shared/hash_method_behavior.rb +10 -5
  34. data/spec/spec_helper.rb +1 -1
  35. data/spec/unit/veritas/algebra/extension/class_methods/new_spec.rb +1 -1
  36. data/spec/unit/veritas/algebra/join/class_methods/new_spec.rb +1 -1
  37. data/spec/unit/veritas/algebra/join/each_spec.rb +36 -9
  38. data/spec/unit/veritas/aliasable/inheritable_alias_spec.rb +1 -1
  39. data/spec/unit/veritas/comparator/compare_spec.rb +4 -1
  40. data/spec/unit/veritas/evaluator/context/method_missing_spec.rb +7 -1
  41. data/spec/unit/veritas/function/connective/negation/class_methods/operation_spec.rb +11 -0
  42. data/spec/unit/veritas/function/numeric/absolute/class_methods/operation_spec.rb +11 -0
  43. data/spec/unit/veritas/function/numeric/unary_minus/class_methods/operation_spec.rb +11 -0
  44. data/spec/unit/veritas/function/numeric/unary_plus/class_methods/operation_spec.rb +11 -0
  45. data/spec/unit/veritas/function/unary/callable/call_spec.rb +23 -0
  46. data/spec/unit/veritas/function/unary/callable/included_spec.rb +23 -0
  47. data/spec/unit/veritas/function/unary/inspect_spec.rb +34 -0
  48. data/spec/unit/veritas/immutable/fixtures/classes.rb +3 -3
  49. data/spec/unit/veritas/immutable/module_methods/memoize_spec.rb +20 -10
  50. data/spec/unit/veritas/relation/class_methods/coerce_spec.rb +23 -0
  51. data/spec/unit/veritas/relation/each_spec.rb +1 -1
  52. data/spec/unit/veritas/relation/equal_value_spec.rb +12 -0
  53. data/spec/unit/veritas/relation/header/call_spec.rb +23 -0
  54. data/spec/unit/veritas/relation/header/class_methods/coerce_spec.rb +1 -1
  55. data/spec/unit/veritas/relation/header/class_methods/new_spec.rb +2 -2
  56. data/spec/unit/veritas/relation/header/each_spec.rb +1 -1
  57. data/spec/unit/veritas/relation/header/empty_spec.rb +5 -4
  58. data/spec/unit/veritas/relation/header/intersect_spec.rb +18 -4
  59. data/spec/unit/veritas/relation/operation/binary/class_methods/new_spec.rb +25 -25
  60. data/spec/unit/veritas/relation/operation/order/direction/name_spec.rb +13 -0
  61. data/spec/unit/veritas/relation/operation/order/direction_set/class_methods/new_spec.rb +1 -1
  62. data/spec/unit/veritas/relation/operation/order/direction_set/equal_value_spec.rb +13 -0
  63. data/spec/unit/veritas/tuple/call_spec.rb +25 -0
  64. data/spec/unit/veritas/tuple/class_methods/coerce_spec.rb +1 -1
  65. data/tasks/metrics/ci.rake +2 -2
  66. data/tasks/metrics/flay.rake +1 -1
  67. data/tasks/metrics/flog.rake +5 -3
  68. data/tasks/metrics/heckle.rake +18 -11
  69. data/tasks/metrics/reek.rake +1 -1
  70. data/tasks/spec.rake +23 -14
  71. data/veritas.gemspec +14 -8
  72. metadata +31 -25
  73. data/spec/unit/veritas/relation/header/element_reference_spec.rb +0 -21
  74. data/spec/unit/veritas/relation/operation/order/direction_set/each_spec.rb +0 -32
  75. data/spec/unit/veritas/relation/operation/order/direction_set/empty_spec.rb +0 -21
  76. data/spec/unit/veritas/relation/operation/order/direction_set/union_spec.rb +0 -18
  77. data/spec/unit/veritas/tuple/element_reference_spec.rb +0 -22
@@ -44,7 +44,6 @@ module Veritas
44
44
  end
45
45
 
46
46
  module Methods
47
- extend Aliasable
48
47
 
49
48
  # Return a length function
50
49
  #
@@ -10,6 +10,41 @@ module Veritas
10
10
 
11
11
  compare :operand
12
12
 
13
+ # Mixin for adding #call to unary function classes
14
+ module Callable
15
+
16
+ # Hook called when module is included
17
+ #
18
+ # @param [Module] descendant
19
+ # the module or class including Callable
20
+ #
21
+ # @return [self]
22
+ #
23
+ # @api private
24
+ def included(descendant)
25
+ super
26
+ descendant.extend(Callable)
27
+ self
28
+ end
29
+
30
+ # Return the response from the unary operation called on the value
31
+ #
32
+ # @example
33
+ # callable.call(value) # => any object
34
+ #
35
+ # @param [Object] value
36
+ #
37
+ # @return [Object]
38
+ #
39
+ # @api public
40
+ def call(value)
41
+ value.send(operation)
42
+ end
43
+
44
+ end # module Callable
45
+
46
+ extend Callable
47
+
13
48
  # Evaluate the unary connective using the tuple
14
49
  #
15
50
  # @example
@@ -70,6 +105,18 @@ module Veritas
70
105
  cmp?(__method__, other)
71
106
  end
72
107
 
108
+ # Return a string representing the unary function
109
+ #
110
+ # @example
111
+ # unary.inspect # => "+1"
112
+ #
113
+ # @return [String]
114
+ #
115
+ # @api public
116
+ def inspect
117
+ "#{self.class.operation.to_s.upcase.chomp('@')}(#{operand.inspect})"
118
+ end
119
+
73
120
  # Mixin for invertable unary functions
74
121
  module Invertible
75
122
 
@@ -112,7 +112,7 @@ module Veritas
112
112
  seen = {}
113
113
  tuples.each do |tuple|
114
114
  tuple = Tuple.coerce(header, tuple)
115
- yield(seen[tuple] = tuple) unless seen.key?(tuple)
115
+ yield seen[tuple] = tuple unless seen.key?(tuple)
116
116
  end
117
117
  self
118
118
  end
@@ -181,7 +181,21 @@ module Veritas
181
181
  #
182
182
  # @api private
183
183
  def coerce(object)
184
- project_relation(Relation.new(header, object))
184
+ self.class.coerce(header, object)
185
+ end
186
+
187
+ # Coerce an Enumerable into a Relation
188
+ #
189
+ # @param [Header] header
190
+ # the header to use when initializing a Relation
191
+ # @param [Enumerable] object
192
+ # the object to coerce
193
+ #
194
+ # @return [Relation]
195
+ #
196
+ # @api private
197
+ def self.coerce(header, object)
198
+ object.kind_of?(Relation) ? object : Relation.new(header, object)
185
199
  end
186
200
 
187
201
  end # class Relation
@@ -11,9 +11,10 @@ module Veritas
11
11
  compare :to_set
12
12
 
13
13
  inheritable_alias(
14
- :& => :intersect,
15
- :| => :union,
16
- :- => :difference
14
+ :[] => :call,
15
+ :& => :intersect,
16
+ :| => :union,
17
+ :- => :difference
17
18
  )
18
19
 
19
20
  # Instantiate a Header
@@ -24,12 +25,12 @@ module Veritas
24
25
  # @param [#to_ary] attributes
25
26
  # optional attributes
26
27
  #
27
- # @return [undefined]
28
+ # @return [Header]
28
29
  #
29
30
  # @api public
30
- def self.new(attributes = [])
31
- attributes = coerce_attributes(attributes.to_ary)
32
- assert_unique_attributes(attributes)
31
+ def self.new(attributes = nil)
32
+ attributes = coerce_attributes(attributes)
33
+ assert_unique_names(attributes.map { |attribute| attribute.name })
33
34
  super
34
35
  end
35
36
 
@@ -41,44 +42,53 @@ module Veritas
41
42
  #
42
43
  # @api private
43
44
  def self.coerce_attributes(attributes)
44
- attributes.map { |attribute| Attribute.coerce(attribute) }
45
+ Array(attributes).map { |attribute| coerce_attribute(attribute) }
45
46
  end
46
47
 
47
- # Assert the attributes are unique
48
+ # Coerce the attribute into an Attribute
48
49
  #
49
- # @param [Array<Attribute>] attributes
50
+ # @param [Object] attribute
51
+ #
52
+ # @return [Attribute]
53
+ #
54
+ # @api private
55
+ def self.coerce_attribute(attribute)
56
+ Attribute.coerce(attribute)
57
+ end
58
+
59
+ # Assert the names are unique
60
+ #
61
+ # @param [Array<Symbol>] names
50
62
  #
51
63
  # @return [undefined]
52
64
  #
53
- # @raise [DuplicateAttributeError]
54
- # raised if the attributes are not unique
65
+ # @raise [DuplicateNameError]
66
+ # raised if the names are not unique
55
67
  #
56
68
  # @api private
57
- def self.assert_unique_attributes(attributes)
58
- duplicates = duplicate_attributes(attributes)
69
+ def self.assert_unique_names(names)
70
+ duplicates = duplicate_names(names)
59
71
  if duplicates
60
- raise DuplicateAttributeError, "duplicate attributes: #{duplicates.join(', ')}"
72
+ raise DuplicateNameError, "duplicate names: #{duplicates.join(', ')}"
61
73
  end
62
74
  end
63
75
 
64
- # Returns the duplicate attribute names, if any
76
+ # Returns the duplicate names, if any
65
77
  #
66
- # @param [Array<Attribute>] attributes
78
+ # @param [Array<Symbol>] names
67
79
  #
68
80
  # @return [Array<Symbol>]
69
- # returns an array of duplicate attributes
81
+ # returns an array of duplicate names
70
82
  #
71
83
  # @return [nil]
72
- # returns nil if there are no duplicate attributes
84
+ # returns nil if there are no duplicate names
73
85
  #
74
86
  # @api private
75
- def self.duplicate_attributes(attributes)
76
- names = attributes.map { |attribute| attribute.name }
77
- names.select! { |name| names.count(name) > 1 }
78
- names.uniq!
87
+ def self.duplicate_names(names)
88
+ names.select { |name| names.count(name) > 1 }.uniq!
79
89
  end
80
90
 
81
- private_class_method :coerce_attributes, :assert_unique_attributes, :duplicate_attributes
91
+ private_class_method :coerce_attributes, :coerce_attribute, :assert_unique_names, :duplicate_names
82
92
 
83
93
  # Initialize a Header
84
94
  #
@@ -91,8 +101,8 @@ module Veritas
91
101
  #
92
102
  # @api public
93
103
  def initialize(attributes)
94
- @names = attributes.map { |attribute| attribute.name }
95
- @attributes = Hash[@names.zip(attributes)]
104
+ @attributes = Immutable.freeze_object(attributes)
105
+ @attribute_for = Hash[@attributes.map { |attribute| attribute.name }.zip(@attributes)]
96
106
  end
97
107
 
98
108
  # Iterate over each attribute in the header
@@ -111,25 +121,25 @@ module Veritas
111
121
  # @api public
112
122
  def each
113
123
  return to_enum unless block_given?
114
- @names.each { |name| yield @attributes.fetch(name) }
124
+ to_ary.each { |attribute| yield attribute }
115
125
  self
116
126
  end
117
127
 
118
128
  # Lookup an attribute in the header given a name
119
129
  #
120
130
  # @example
121
- # attribute = header[:id]
131
+ # attribute = header.call(:id)
122
132
  #
123
133
  # @param [Attribute, #to_ary, #to_sym] name
124
134
  #
125
135
  # @return [Attribute]
126
136
  # the attribute when the name is known
127
137
  # @return [nil]
128
- # nil when the attribute is unknown
138
+ # nil when the name is unknown
129
139
  #
130
140
  # @api public
131
- def [](name)
132
- @attributes[Attribute.name_from(name)]
141
+ def call(name)
142
+ @attribute_for[Attribute.name_from(name)]
133
143
  end
134
144
 
135
145
  # Return a header with only the attributes specified
@@ -142,7 +152,7 @@ module Veritas
142
152
  #
143
153
  # @return [Header]
144
154
  #
145
- # @api private
155
+ # @api public
146
156
  def project(attributes)
147
157
  new(attributes.map { |attribute| self[attribute] })
148
158
  end
@@ -204,7 +214,7 @@ module Veritas
204
214
  #
205
215
  # @api private
206
216
  def to_ary
207
- @attributes.values_at(*@names).freeze
217
+ @attributes
208
218
  end
209
219
 
210
220
  # Compare the header with other header for equivalency
@@ -231,7 +241,7 @@ module Veritas
231
241
  #
232
242
  # @api public
233
243
  def empty?
234
- @names.empty?
244
+ to_ary.empty?
235
245
  end
236
246
 
237
247
  # Return a string representing the header
@@ -250,14 +260,13 @@ module Veritas
250
260
 
251
261
  # Utility method to instantiate a Header
252
262
  #
253
- # @param [#to_ary] attributes
254
- # the header attributes
263
+ # @param [Array] *args
255
264
  #
256
265
  # @return [Header]
257
266
  #
258
267
  # @api private
259
- def new(attributes)
260
- self.class.new(attributes)
268
+ def new(*args)
269
+ self.class.new(*args)
261
270
  end
262
271
 
263
272
  # Coerce the object into a Header
@@ -281,11 +290,9 @@ module Veritas
281
290
  #
282
291
  # @api private
283
292
  def self.coerce(object)
284
- object.kind_of?(Header) ? object : new(object)
293
+ object.kind_of?(self) ? object : new(object)
285
294
  end
286
295
 
287
- memoize :to_ary
288
-
289
296
  end # class Header
290
297
  end # class Relation
291
298
  end # module Veritas
@@ -31,6 +31,18 @@ module Veritas
31
31
  @attribute = attribute
32
32
  end
33
33
 
34
+ # Return the attribute name
35
+ #
36
+ # @example
37
+ # direction.name # => :id
38
+ #
39
+ # @return [Symbol]
40
+ #
41
+ # @api public
42
+ def name
43
+ attribute.name
44
+ end
45
+
34
46
  # Compare the left and right Tuple attribute values
35
47
  #
36
48
  # @example
@@ -51,7 +63,7 @@ module Veritas
51
63
  # @api public
52
64
  def call(left, right)
53
65
  attribute = self.attribute
54
- self.class.call(left[attribute], right[attribute])
66
+ self.class.call(attribute.call(left), attribute.call(right))
55
67
  end
56
68
 
57
69
  # Rename the contained attribute with the provided aliases
@@ -6,108 +6,23 @@ module Veritas
6
6
  class Order
7
7
 
8
8
  # A class that represents a tuple sort order for a set of attributes
9
- class DirectionSet
10
- extend Aliasable, Comparator
11
- include Enumerable, Immutable
9
+ class DirectionSet < Header
10
+ EMPTY = new
12
11
 
13
12
  compare :to_ary
14
13
 
15
- inheritable_alias(:| => :union)
16
-
17
- # Instantiate a DirectionSet
18
- #
19
- # @example
20
- # directions = DirectionSet.new(directions)
21
- #
22
- # @param [#to_ary] directions
23
- # optional attributes
24
- #
25
- # @return [undefined]
26
- #
27
- # @api public
28
- def self.new(directions)
29
- directions = coerce_directions(directions)
30
- assert_unique_attributes(directions.map { |direction| direction.attribute })
31
- super
32
- end
33
-
34
- # Coerce the attributes into an Array of Direction objects
35
- #
36
- # @param [Array<Direction>] attributes
37
- #
38
- # @return [Array<Direction>]
39
- #
40
- # @api private
41
- def self.coerce_directions(directions)
42
- Array(directions).map { |direction| Ascending.coerce(direction) }
43
- end
44
-
45
- # Assert the attributes are unique
46
- #
47
- # @param [Array<Attribute>] attributes
48
- #
49
- # @return [undefined]
50
- #
51
- # @raise [DuplicateAttributeError]
52
- # raised if the attributes are not unique
53
- #
54
- # @api private
55
- def self.assert_unique_attributes(attributes)
56
- duplicates = duplicate_attributes(attributes)
57
- if duplicates
58
- raise DuplicateAttributeError, "duplicate attributes: #{duplicates.join(', ')}"
59
- end
60
- end
61
-
62
- # Returns the duplicate attribute names, if any
63
- #
64
- # @param [Array<Attribute>] attributes
65
- #
66
- # @return [Array<Symbol>]
67
- # returns an array of duplicate attributes
14
+ # Coerce the attribute into a Direction
68
15
  #
69
- # @return [nil]
70
- # returns nil if there are no duplicate attributes
16
+ # @param [Object] attribute
71
17
  #
72
- # @api private
73
- def self.duplicate_attributes(attributes)
74
- names = attributes.map { |attribute| attribute.name }
75
- names.select! { |name| names.count(name) > 1 }
76
- names.uniq!
77
- end
78
-
79
- private_class_method :coerce_directions, :assert_unique_attributes, :duplicate_attributes
80
-
81
- # Initialize a DirectionSet
82
- #
83
- # @param [Array<Direction>] directions
84
- # the directions to sort with
85
- #
86
- # @return [undefined]
18
+ # @return [Direction]
87
19
  #
88
20
  # @api private
89
- def initialize(directions)
90
- @directions = directions
21
+ def self.coerce_attribute(attribute)
22
+ Ascending.coerce(attribute)
91
23
  end
92
24
 
93
- EMPTY = new([])
94
-
95
- # Return a direction set with only the attributes specified
96
- #
97
- # @example
98
- # projected = directions.project([ attribute ])
99
- #
100
- # @param [Array<Attribute>] attributes
101
- # the attributes to keep in the direction set
102
- #
103
- # @return [DirectionSet]
104
- #
105
- # @api public
106
- def project(attributes)
107
- new select { |direction|
108
- attributes.include?(direction.attribute)
109
- }
110
- end
25
+ private_class_method :coerce_attribute
111
26
 
112
27
  # Rename the contained attributes with the provided aliases
113
28
  #
@@ -136,50 +51,6 @@ module Veritas
136
51
  new(map { |direction| direction.reverse })
137
52
  end
138
53
 
139
- # Iterate over each direction in the set
140
- #
141
- # @example
142
- # directions = DirectionSet.new(directions)
143
- # directions.each { |direction| ... }
144
- #
145
- # @yield [direction]
146
- #
147
- # @yieldparam [Direction] direction
148
- # each direction in the set
149
- #
150
- # @return [self]
151
- #
152
- # @api public
153
- def each
154
- return to_enum unless block_given?
155
- to_ary.each { |tuple| yield tuple }
156
- self
157
- end
158
-
159
- # Return the directions as an Array
160
- #
161
- # @return [Array]
162
- #
163
- # @api private
164
- def to_ary
165
- @directions
166
- end
167
-
168
- # Union the directions with another set of directions
169
- #
170
- # @example
171
- # union = directions.union(other)
172
- #
173
- # @param [DirectionSet] other
174
- # the directions to union with
175
- #
176
- # @return [DirectionSet]
177
- #
178
- # @api public
179
- def union(other)
180
- new(to_ary | other.to_ary)
181
- end
182
-
183
54
  # Return each attribute in an Array
184
55
  #
185
56
  # @return [Array]
@@ -202,46 +73,8 @@ module Veritas
202
73
  tuples.sort { |left, right| cmp_tuples(left, right) }
203
74
  end
204
75
 
205
- # Compare the directions with other directions for equivalency
206
- #
207
- # @example
208
- # directions == other # => true or false
209
- #
210
- # @param [DirectionSet] other
211
- # the other directions to compare with
212
- #
213
- # @return [Boolean]
214
- #
215
- # @api public
216
- def ==(other)
217
- cmp?(__method__, coerce(other))
218
- end
219
-
220
- # Test if there are no directions
221
- #
222
- # @example
223
- # directions.empty? # => true or false
224
- #
225
- # @return [Boolean]
226
- #
227
- # @api public
228
- def empty?
229
- @directions.empty?
230
- end
231
-
232
76
  private
233
77
 
234
- # Utility method for initializing a set of directions
235
- #
236
- # @param [Array<Direction>] directions
237
- #
238
- # @return [DirectionSet]
239
- #
240
- # @api private
241
- def new(directions)
242
- self.class.new(directions)
243
- end
244
-
245
78
  # Compare the attributes for each Tuple
246
79
  #
247
80
  # @param [Tuple] left
@@ -264,29 +97,7 @@ module Veritas
264
97
  end
265
98
  end
266
99
 
267
- # Coerce directions into a DirectionSet
268
- #
269
- # @param [DirectionSet, Array<Direction, Attribute>]
270
- #
271
- # @return [DirectionSet]
272
- #
273
- # @api private
274
- def coerce(object)
275
- self.class.coerce(object)
276
- end
277
-
278
- # Coerce directions into a DirectionSet
279
- #
280
- # @param [DirectionSet, Array<Direction, Attribute>]
281
- #
282
- # @return [DirectionSet]
283
- #
284
- # @api private
285
- def self.coerce(object)
286
- object.kind_of?(DirectionSet) ? object : new(object)
287
- end
288
-
289
- memoize :reverse
100
+ memoize :reverse, :attributes
290
101
 
291
102
  end # class DirectionSet
292
103
  end # class Order