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
@@ -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