veritas 0.0.6 → 0.0.7

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 (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
@@ -60,8 +60,6 @@ module Veritas
60
60
  self
61
61
  end
62
62
 
63
- private
64
-
65
63
  module Methods
66
64
 
67
65
  # Return the reversed relation
@@ -38,7 +38,7 @@ module Veritas
38
38
  # @api private
39
39
  def define_hash_method(methods)
40
40
  define_method(:hash) do
41
- self.class.hash ^ methods.map { |method| send(method).hash }.inject(:^)
41
+ self.class.hash ^ methods.map { |method| send(method).hash }.reduce(0, :^)
42
42
  end
43
43
  end
44
44
 
@@ -5,6 +5,9 @@ module Veritas
5
5
 
6
6
  # Provide a context to evaluate a Relation operation block
7
7
  class Context < BasicObject
8
+ extend Aliasable
9
+
10
+ inheritable_alias(:respond_to_missing? => :respond_to?)
8
11
 
9
12
  # The functions to evaluate
10
13
  #
@@ -29,7 +29,7 @@ module Veritas
29
29
  #
30
30
  # @api public
31
31
  def freeze
32
- @__memory = {} unless frozen?
32
+ memory # initialize memory
33
33
  super
34
34
  end
35
35
 
@@ -45,7 +45,7 @@ module Veritas
45
45
  #
46
46
  # @api public
47
47
  def memoized(name)
48
- @__memory[name]
48
+ memory[name]
49
49
  end
50
50
 
51
51
  # Sets a memoized value for a method
@@ -62,9 +62,7 @@ module Veritas
62
62
  #
63
63
  # @api public
64
64
  def memoize(name, value)
65
- unless @__memory.key?(name)
66
- @__memory[name] = Immutable.freeze_object(value)
67
- end
65
+ store_memory(name, value) unless memory.key?(name)
68
66
  self
69
67
  end
70
68
 
@@ -80,6 +78,33 @@ module Veritas
80
78
  self
81
79
  end
82
80
 
81
+ private
82
+
83
+ # The memoized method results
84
+ #
85
+ # @return [Hash]
86
+ #
87
+ # @api private
88
+ def memory
89
+ @__memory ||= {}
90
+ end
91
+
92
+ # Store the value in memory
93
+ #
94
+ # @param [Symbol] name
95
+ # the method name
96
+ # @param [Object] value
97
+ # the value to memoize
98
+ #
99
+ # @return [self]
100
+ #
101
+ # @return [value]
102
+ #
103
+ # @api private
104
+ def store_memory(name, value)
105
+ memory[name] = Immutable.freeze_object(value)
106
+ end
107
+
83
108
  # Attempt to freeze an object
84
109
  #
85
110
  # @example using a value object
@@ -179,10 +204,10 @@ module Veritas
179
204
  original = instance_method(method)
180
205
  undef_method(method)
181
206
  define_method(method) do |*args|
182
- if @__memory.key?(method)
183
- @__memory.fetch(method)
207
+ if memory.key?(method)
208
+ memoized(method)
184
209
  else
185
- @__memory[method] = Immutable.freeze_object(original.bind(self).call(*args))
210
+ store_memory(method, original.bind(self).call(*args))
186
211
  end
187
212
  end
188
213
  end
data/lib/veritas/tuple.rb CHANGED
@@ -4,9 +4,11 @@ module Veritas
4
4
 
5
5
  # A set of objects representing a unique fact in a relation
6
6
  class Tuple
7
- extend Comparator
7
+ extend Aliasable, Comparator
8
8
  include Immutable
9
9
 
10
+ inheritable_alias(:[] => :call)
11
+
10
12
  compare :data
11
13
 
12
14
  # The tuple header
@@ -41,15 +43,15 @@ module Veritas
41
43
  # Lookup a value in the tuple given an attribute
42
44
  #
43
45
  # @example
44
- # value = tuple[attribute]
46
+ # value = tuple.call(attribute)
45
47
  #
46
48
  # @param [Attribute] attribute
47
49
  #
48
50
  # @return [Object]
49
51
  #
50
52
  # @api public
51
- def [](attribute)
52
- data.fetch(header[attribute])
53
+ def call(attribute)
54
+ data.fetch(header.call(attribute))
53
55
  end
54
56
 
55
57
  # Return a tuple with only the specified attributes
@@ -111,7 +113,7 @@ module Veritas
111
113
  # @api private
112
114
  def predicate
113
115
  header.reduce(Function::Proposition::Tautology.instance) do |predicate, attribute|
114
- predicate.and(attribute.eq(self[attribute]))
116
+ predicate.and(attribute.eq(attribute.call(self)))
115
117
  end
116
118
  end
117
119
 
@@ -184,7 +186,7 @@ module Veritas
184
186
  object.kind_of?(Tuple) ? object : new(header, object)
185
187
  end
186
188
 
187
- memoize :hash, :predicate, :to_ary
189
+ memoize :predicate, :to_ary
188
190
 
189
191
  end # class Tuple
190
192
  end # module Veritas
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Veritas
4
- VERSION = '0.0.6'
4
+ VERSION = '0.0.7'
5
5
  end # module Veritas
@@ -6,7 +6,7 @@ describe Algebra::Projection do
6
6
  context 'remove attributes in predicate' do
7
7
  subject { restriction.project([ :name ]) }
8
8
 
9
- let(:left) { Relation.new([ [ :id, Integer ] ], (1..100).map { |n| [ n ] }) }
9
+ let(:left) { Relation.new([ [ :id, Integer ] ], (0..11).map { |n| [ n ] }) }
10
10
  let(:right) { Relation.new([ [ :name, String ] ], [ [ 'Dan Kubb' ], [ 'John Doe' ], [ 'Jane Doe' ] ]) }
11
11
  let(:relation) { left * right }
12
12
  let(:restriction) { relation.restrict { |r| r.id.gte(1).and(r.id.lte(10)).and(r.name.eq('Dan Kubb')) } }
@@ -1,18 +1,15 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  require 'spec_helper'
4
+ require 'timeout'
4
5
 
5
6
  # use an infinite list to simulate handling a large Array.
6
7
  # if any operation is inefficient, then the specs will never exit
7
8
  class InfiniteList
8
9
  include Enumerable
9
10
 
10
- def each
11
- index = 0
12
- loop do
13
- yield [ index ]
14
- index += 1
15
- end
11
+ def each(&block)
12
+ 0.upto(Float::INFINITY) { |index| yield [ index ] }
16
13
  self
17
14
  end
18
15
  end
@@ -22,7 +19,9 @@ describe Relation do
22
19
  let(:relation) { Relation.new([ [ :id, Integer ] ], InfiniteList.new) }
23
20
 
24
21
  def sample(relation)
25
- relation.to_enum.take(5)
22
+ Timeout.timeout(1) do
23
+ relation.to_enum.take(5)
24
+ end
26
25
  end
27
26
 
28
27
  it '#project should be efficient' do
@@ -35,9 +34,40 @@ describe Relation do
35
34
  sample(restricted).should == [ [ 6 ], [ 7 ], [ 8 ], [ 9 ], [ 10 ] ]
36
35
  end
37
36
 
38
- it '#product should be efficient' do
39
- product = relation.product(Relation.new([ [ :name, String ] ], [ [ 'Dan Kubb' ] ]))
40
- sample(product).should == [ [ 0, 'Dan Kubb' ], [ 1, 'Dan Kubb' ], [ 2, 'Dan Kubb' ], [ 3, 'Dan Kubb' ], [ 4, 'Dan Kubb' ] ]
37
+ it '#rename should be efficient' do
38
+ renamed = relation.rename(:id => :other_id)
39
+ sample(renamed).should == [ [ 0 ], [ 1 ], [ 2 ], [ 3 ], [ 4 ] ]
40
+ end
41
+
42
+ describe '#join should be efficient' do
43
+ let(:other) { Relation.new([ [ :id, Integer ], [ :name, String ] ], [ [ 1, 'Dan Kubb' ] ]) }
44
+
45
+ it 'has an infinite left relation' do
46
+ pending 'Change #join to handle infinite sets'
47
+ join = other.join(relation)
48
+ sample(join).should == [ [ 1, 'Dan Kubb' ] ]
49
+ end
50
+
51
+ it 'has an infinite right relation' do
52
+ pending 'Change #join to handle infinite sets'
53
+ join = relation.join(other)
54
+ sample(join).should == [ [ 1, 'Dan Kubb' ] ]
55
+ end
56
+ end
57
+
58
+ describe '#product should be efficient' do
59
+ let(:other) { Relation.new([ [ :name, String ] ], [ [ 'Dan Kubb' ] ]) }
60
+
61
+ it 'has an infinite left relation' do
62
+ product = relation.product(other)
63
+ sample(product).should == [ [ 0, 'Dan Kubb' ], [ 1, 'Dan Kubb' ], [ 2, 'Dan Kubb' ], [ 3, 'Dan Kubb' ], [ 4, 'Dan Kubb' ] ]
64
+ end
65
+
66
+ it 'has an infinite right relation' do
67
+ pending 'Change #product to handle infinite sets'
68
+ product = other.product(relation)
69
+ sample(product).should == [ [ 0, 'Dan Kubb' ], [ 1, 'Dan Kubb' ], [ 2, 'Dan Kubb' ], [ 3, 'Dan Kubb' ], [ 4, 'Dan Kubb' ] ]
70
+ end
41
71
  end
42
72
 
43
73
  it '#difference should be efficient' do
@@ -49,10 +79,5 @@ describe Relation do
49
79
  union = relation.union(Relation.new(relation.header, [ [ 1 ] ]))
50
80
  sample(union).should == [ [ 0 ], [ 1 ], [ 2 ], [ 3 ], [ 4 ] ]
51
81
  end
52
-
53
- it '#rename should be efficient' do
54
- renamed = relation.rename(:id => :other_id)
55
- sample(renamed).should == [ [ 0 ], [ 1 ], [ 2 ], [ 3 ], [ 4 ] ]
56
- end
57
82
  end
58
83
  end
data/spec/rcov.opts CHANGED
@@ -4,3 +4,4 @@
4
4
  --xrefs
5
5
  --profile
6
6
  --text-summary
7
+ --failure-threshold 100
@@ -3,11 +3,16 @@
3
3
  shared_examples_for 'a hash method' do
4
4
  it_should_behave_like 'an idempotent method'
5
5
 
6
- # TOOD: figure out if #hash under rbx should also always use Fixnum
7
- if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx'
8
- it { should be_kind_of(Integer) }
9
- else
10
- it { should be_instance_of(Fixnum) }
6
+ specification = proc do
7
+ should be_instance_of(Fixnum)
8
+ end
9
+
10
+ it 'is a fixnum' do
11
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx'
12
+ pending('Under rubinius #hash does not always use a Fixnum', &specification)
13
+ else
14
+ instance_eval(&specification)
15
+ end
11
16
  end
12
17
 
13
18
  it 'memoizes the hash code' do
data/spec/spec_helper.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'rubygems'
4
4
  require 'backports'
5
- require 'backports/basic_object'
5
+ require 'backports/basic_object' unless RUBY_VERSION >= '1.9.2' && (RUBY_PLATFORM.include?('java') || RUBY_ENGINE == 'rbx')
6
6
  require 'veritas'
7
7
  require 'spec'
8
8
  require 'spec/autorun'
@@ -17,6 +17,6 @@ describe Algebra::Extension, '.new' do
17
17
  context 'with a duplicate attribute name provided' do
18
18
  let(:extensions) { { :id => proc {}, :name => proc {} } }
19
19
 
20
- specify { expect { subject }.to raise_error(DuplicateAttributeError, 'duplicate attributes: id, name') }
20
+ specify { expect { subject }.to raise_error(DuplicateNameError, 'duplicate names: id, name') }
21
21
  end
22
22
  end
@@ -24,6 +24,6 @@ describe Algebra::Join, '.new' do
24
24
  context 'with relations having different headers' do
25
25
  let(:right) { Relation.new([ [ :name, String ] ], [ [ 'Dan Kubb' ] ]) }
26
26
 
27
- specify { expect { subject }.to raise_error(InvalidHeaderError, 'the headers must have common attributes') }
27
+ it { should be_instance_of(object) }
28
28
  end
29
29
  end
@@ -5,16 +5,43 @@ require 'spec_helper'
5
5
  describe Algebra::Join, '#each' do
6
6
  subject { object.each { |tuple| yields << tuple } }
7
7
 
8
- let(:left) { Relation.new([ [ :id, Integer ] ], [ [ 1 ], [ 2 ] ]) }
9
- let(:right) { Relation.new([ [ :id, Integer ], [ :name, String ] ], [ [ 2, 'Dan Kubb' ] ]) }
10
- let(:object) { described_class.new(left, right) }
11
- let(:yields) { [] }
8
+ let(:left) { Relation.new([ [ :id, Integer ] ], [ [ 1 ], [ 2 ] ]) }
9
+ let(:object) { described_class.new(left, right) }
10
+ let(:yields) { [] }
12
11
 
13
- it_should_behave_like 'an #each method'
12
+ context 'when the attributes are joined' do
13
+ let(:right) { Relation.new([ [ :id, Integer ], [ :name, String ] ], [ [ 2, 'Dan Kubb' ] ]) }
14
14
 
15
- it 'yields each tuple' do
16
- expect { subject }.to change { yields.dup }.
17
- from([]).
18
- to([ [ 2, 'Dan Kubb' ] ])
15
+ it_should_behave_like 'an #each method'
16
+
17
+ it 'yields each tuple in the join' do
18
+ expect { subject }.to change { yields.dup }.
19
+ from([]).
20
+ to([ [ 2, 'Dan Kubb' ] ])
21
+ end
22
+ end
23
+
24
+ context 'when the attributes are disjoint' do
25
+ let(:right) { Relation.new([ [ :name, String ] ], [ [ 'Dan Kubb' ] ]) }
26
+
27
+ it_should_behave_like 'an #each method'
28
+
29
+ it 'yields each tuple in the product' do
30
+ expect { subject }.to change { yields.dup }.
31
+ from([]).
32
+ to([ [ 1, 'Dan Kubb' ], [ 2, 'Dan Kubb' ] ])
33
+ end
34
+ end
35
+
36
+ context 'when the attributes are an intersection' do
37
+ let(:right) { Relation.new([ [ :id, Integer ] ], [ [ 1 ] ]) }
38
+
39
+ it_should_behave_like 'an #each method'
40
+
41
+ it 'yields each tuple in the intersection' do
42
+ expect { subject }.to change { yields.dup }.
43
+ from([]).
44
+ to([ [ 1 ] ])
45
+ end
19
46
  end
20
47
  end
@@ -41,7 +41,7 @@ describe Aliasable, '#inheritable_alias' do
41
41
  end
42
42
 
43
43
  it 'sets the file and line number properly' do
44
- if RUBY_PLATFORM[/java/]
44
+ if RUBY_PLATFORM.include?('java')
45
45
  pending('Kernel#caller returns the incorrect line number in JRuby', &specification)
46
46
  else
47
47
  instance_eval(&specification)
@@ -24,8 +24,11 @@ describe Comparator, '#compare' do
24
24
  end
25
25
 
26
26
  it 'defines a #hash method that uses the class and declared methods' do
27
+ # use Fixnum#eql? instead of Fixnum#equal? because the hash value may not
28
+ # always be the same object (i.e. on JRuby), and we mostly only care that
29
+ # the type and value match the expected Fixnum.
27
30
  subject
28
- instance.hash.should equal(object.hash ^ false.hash ^ instance.to_s.hash)
31
+ instance.hash.should eql(object.hash ^ false.hash ^ instance.to_s.hash)
29
32
  end
30
33
 
31
34
  it 'memoizes #hash' do
@@ -24,6 +24,12 @@ describe Evaluator::Context, '#method_missing' do
24
24
  context 'with an unknown attribute' do
25
25
  subject { object.unknown }
26
26
 
27
- specify { expect { subject }.to raise_error(NoMethodError) }
27
+ if RUBY_VERSION >= '1.9.2' && RUBY_ENGINE == 'rbx'
28
+ # Mark this spec as pending until the following rubinius 1.9 mode bug is
29
+ # resolved: https://github.com/rubinius/rubinius/issues/1194
30
+ specify { pending 'BasicObject#method_missing does not raise NoMethodError for unknown methods' }
31
+ else
32
+ specify { expect { subject }.to raise_error(NoMethodError) }
33
+ end
28
34
  end
29
35
  end
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Function::Connective::Negation, '.operation' do
6
+ subject { object.operation }
7
+
8
+ let(:object) { described_class }
9
+
10
+ it { should equal(:'!') }
11
+ end
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Function::Numeric::Absolute, '.operation' do
6
+ subject { object.operation }
7
+
8
+ let(:object) { described_class }
9
+
10
+ it { should equal(:abs) }
11
+ end
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Function::Numeric::UnaryMinus, '.operation' do
6
+ subject { object.operation }
7
+
8
+ let(:object) { described_class }
9
+
10
+ it { should equal(:-@) }
11
+ end