Skip to content
Browse files

Add Veritas::Relation::Keys

* Add keys support to Header.
  • Loading branch information...
1 parent 4469383 commit 86b11ee08b7be2b4b3edbbc2bef1fb2d7918619e @dkubb committed Dec 24, 2012
View
3 README.md
@@ -133,6 +133,9 @@ new_relation = relation.delete(other)
# replace a relation variable with another set
new_relation = relation.replace(other)
+
+# each subset of unique keys as header objects
+keys = header.keys
```
## Goals
View
2 TODO
@@ -1,5 +1,7 @@
* TODO:
+* Add key minimization
+
* Make Equalizer handle subclasses without having to specify the attribtues
again in each descendant.
View
2 config/flay.yml
@@ -1,3 +1,3 @@
---
threshold: 44
-total_score: 856
+total_score: 865
View
4 config/roodi.yml
@@ -2,7 +2,7 @@
AbcMetricMethodCheck: { score: 10.3 }
AssignmentInConditionalCheck: { }
CaseMissingElseCheck: { }
-ClassLineCountCheck: { line_count: 294 }
+ClassLineCountCheck: { line_count: 346 }
ClassNameCheck: { pattern: !ruby/regexp '/\A(?:[A-Z]+|[A-Z][a-z](?:[A-Z]?[a-z])+)\z/' }
ClassVariableCheck: { }
CyclomaticComplexityBlockCheck: { complexity: 3 }
@@ -12,7 +12,7 @@ ForLoopCheck: { }
# TODO: decrease line_count to 5 to 10
MethodLineCountCheck: { line_count: 14 }
MethodNameCheck: { pattern: !ruby/regexp '/\A(?:[a-z\d](?:_?[a-z\d])+[?!=]?|\[\]=?|==|<=>|<<|[+*&|-])\z/' }
-ModuleLineCountCheck: { line_count: 296 }
+ModuleLineCountCheck: { line_count: 348 }
ModuleNameCheck: { pattern: !ruby/regexp '/\A(?:[A-Z]+|[A-Z][a-z](?:[A-Z]?[a-z])+)\z/' }
# TODO: decrease parameter_count to 2 or less
ParameterNumberCheck: { parameter_count: 3 }
View
2 config/site.reek
@@ -8,7 +8,7 @@ UncommunicativeParameterName:
- !ruby/regexp /[0-9]$/
- !ruby/regexp /[A-Z]/
LargeClass:
- max_methods: 14 # TODO: decrease max_methods to 10-15 or less
+ max_methods: 15 # TODO: decrease max_methods to 10-15 or less
exclude: []
enabled: true
max_instance_variables: 4
View
1 lib/veritas.rb
@@ -66,6 +66,7 @@
require 'veritas/relation/proxy'
require 'veritas/relation/base'
+require 'veritas/relation/keys'
require 'veritas/relation/header'
require 'veritas/relation/materialized'
View
71 lib/veritas/relation/header.rb
@@ -7,7 +7,7 @@ class Relation
class Header
extend Aliasable
include Enumerable
- include Equalizer.new(:to_set)
+ include Equalizer.new(:to_set, :keys)
inheritable_alias(
:[] => :call,
@@ -16,10 +16,18 @@ class Header
:- => :difference
)
+ # The header keys
+ #
+ # @return [Keys]
+ #
+ # @api private
+ attr_reader :keys
+
# Coerce an Array-like object into a Header
#
# @param [Header, #to_ary] object
# the header or attributes
+ # @param [Hash] options
#
# @yield [attribute]
#
@@ -28,7 +36,7 @@ class Header
# @return [Header]
#
# @api private
- def self.coerce(object)
+ def self.coerce(object, options = {})
if object.kind_of?(self)
object
else
@@ -37,7 +45,7 @@ def self.coerce(object)
block = lambda do |attribute|
block_given? and yield(attribute) or coerce_attribute(attribute)
end
- new(Array(object).map(&block))
+ new(Array(object).map(&block), options)
end
end
@@ -48,11 +56,12 @@ def self.coerce(object)
#
# @param [Array<Attribute>] attributes
# optional attributes
+ # @param [Hash] options
#
# @return [Header]
#
# @api public
- def self.new(attributes = [])
+ def self.new(attributes = [], options = {})
assert_unique_names(attributes.map { |attribute| attribute.name })
super
end
@@ -105,16 +114,20 @@ def self.duplicate_names(names)
# Initialize a Header
#
# @example
- # header = Header.new(attributes)
+ # header = Header.new(attributes, :keys => [ [ :id ] ])
#
# @param [Array] attributes
#
+ # @param [Hash] options
+ #
# @return [undefined]
#
# @api public
- def initialize(attributes)
+ def initialize(attributes, options)
@attributes = freeze_object(attributes)
+ @options = freeze_object(options)
@attribute_for = Hash[@attributes.map { |attribute| attribute.name }.zip(@attributes)]
+ @keys = coerce_keys
end
# Iterate over each attribute in the header
@@ -157,6 +170,9 @@ def call(name)
# Return a header with only the attributes specified
#
+ # The unique keys intersected with the attributes become the new keys
+ # because a projection strengthens key constraints.
+ #
# @example
# projected = header.project(attributes)
#
@@ -167,11 +183,14 @@ def call(name)
#
# @api public
def project(attributes)
- coerce(attributes)
+ coerce(attributes, :keys => keys.project(attributes))
end
# Return a header with the new attributes added
#
+ # The original keys from the header are reused because
+ # an extension does not affect key constraints.
+ #
# @example
# extended = header.extend(attributes)
#
@@ -182,11 +201,14 @@ def project(attributes)
#
# @api public
def extend(attributes)
- new(to_ary + coerce(attributes))
+ new(to_ary + coerce(attributes), :keys => keys)
end
# Return a header with the attributes renamed
#
+ # The attributes in the original keys are renamed, but
+ # a rename does not otherwise affect the key constraints.
+ #
# @example
# renamed = header.rename(aliases)
#
@@ -197,11 +219,17 @@ def extend(attributes)
#
# @api public
def rename(aliases)
- new(map { |attribute| aliases[attribute] })
+ new(
+ map { |attribute| aliases[attribute] },
+ :keys => keys.rename(aliases)
+ )
end
# Return the intersection of the header with another header
#
+ # The unique keys from the headers become the new keys because
+ # an intersection strengthens key constraints.
+ #
# @example
# intersection = header.intersect(other)
#
@@ -211,11 +239,15 @@ def rename(aliases)
#
# @api public
def intersect(other)
- new(to_ary & coerce(other))
+ other = coerce(other)
+ new(to_ary & other, :keys => keys | other.keys)
end
# Return the union of the header with another header
#
+ # The common keys from the headers become the new keys because
+ # a union weakens key constraints.
+ #
# @example
# union = header.union(other)
#
@@ -225,11 +257,15 @@ def intersect(other)
#
# @api public
def union(other)
- new(to_ary | coerce(other))
+ other = coerce(other)
+ new(to_ary | other, :keys => keys & other.keys)
end
# Return the difference of the header with another header
#
+ # The original keys from the header are reused because
+ # a difference does not affect key constraints.
+ #
# @example
# difference = header.difference(other)
#
@@ -239,7 +275,7 @@ def union(other)
#
# @api public
def difference(other)
- new(to_ary - coerce(other))
+ new(to_ary - coerce(other), :keys => keys)
end
# Convert the Header into an Array
@@ -279,6 +315,17 @@ def new(*args)
self.class.new(*args)
end
+ # Coerce the keys into an Array of Headers
+ #
+ # @return [Array<Header>]
+ #
+ # @api private
+ def coerce_keys
+ Keys.coerce(@options.fetch(:keys, [])) do |attributes|
+ coerce(attributes)
+ end
+ end
+
# Coerce the object into a Header
#
# @param [Array] args
View
226 lib/veritas/relation/keys.rb
@@ -0,0 +1,226 @@
+# encoding: utf-8
+
+module Veritas
+ class Relation
+
+ # A set of keys in a Header
+ class Keys
+ extend Aliasable
+ include Enumerable
+ include Equalizer.new(:to_set)
+
+ inheritable_alias(
+ :& => :intersect,
+ :| => :union,
+ :- => :difference
+ )
+
+ # Coerce an Array-like object into Keys
+ #
+ # @param [Keys, #to_ary] object
+ #
+ # @yield [attributes]
+ #
+ # @yieldparam [Header, Array] attributes
+ # the attributes in each key
+ #
+ # @return [Keys]
+ #
+ # @api private
+ def self.coerce(object, &block)
+ if object.kind_of?(self)
+ object
+ else
+ block ||= method(:coerce_attributes)
+ new(Array(object).map(&block))
+ end
+ end
+
+ # Coerce the attributes into a Header
+ #
+ # @param [Object] attributes
+ #
+ # @return [Header]
+ #
+ # @api private
+ def self.coerce_attributes(attributes)
+ Header.coerce(attributes)
+ end
+
+ private_class_method :coerce_attributes
+
+ # Initialize Keys
+ #
+ # @example
+ # keys = Keys.new([ [ :id ] ])
+ #
+ # @param [#to_ary] keys
+ #
+ # @return [undefined]
+ #
+ # @api public
+ def initialize(keys = [])
+ @keys = freeze_object(keys.to_ary)
+ end
+
+ # Iterate over each key in the Keys
+ #
+ # @example
+ # keys = Keys.new(keys)
+ # keys.each { |key| ... }
+ #
+ # @yield [key]
+ #
+ # @yieldparam [Header] key
+ #
+ # @return [self]
+ #
+ # @api public
+ def each
+ return to_enum unless block_given?
+ to_ary.each { |key| yield key }
+ self
+ end
+
+ # Return keys with only the attributes specified
+ #
+ # When the attributes do not match any attributes in an existing
+ # key, then the key should be removed because it means the attributes
+ # it applied against no longer exist in the new header.
+ #
+ # @example
+ # projected = keys.project(attributes)
+ #
+ # @param [#map] attributes
+ # the attributes to keep in the keys
+ #
+ # @return [Keys]
+ #
+ # @api public
+ def project(attributes)
+ new(map { |key| key & attributes }.delete_if(&:empty?))
+ end
+
+ # Return keys with the new attributes added
+ #
+ # @example
+ # extended = keys.extend(attributes)
+ #
+ # @param [#to_ary] attributes
+ # the attributes to add to the keys
+ #
+ # @return [Keys]
+ #
+ # @api public
+ def extend(attributes)
+ new(map { |key| key | attributes })
+ end
+
+ # Return keys with the attributes renamed
+ #
+ # @example
+ # renamed = keys.rename(aliases)
+ #
+ # @param [Aliases] aliases
+ # the old and new attribute names
+ #
+ # @return [Keys]
+ #
+ # @api public
+ def rename(aliases)
+ new(map { |key| key.rename(aliases) })
+ end
+
+ # Return the intersection of the keys with other keys
+ #
+ # @example
+ # intersection = keys.intersect(other)
+ #
+ # @param [Keys] other
+ #
+ # @return [Keys]
+ #
+ # @api public
+ def intersect(other)
+ new(to_ary & other)
+ end
+
+ # Return the union of the keys with other keys
+ #
+ # @example
+ # union = keys.union(other)
+ #
+ # @param [Keys] other
+ #
+ # @return [Keys]
+ #
+ # @api public
+ def union(other)
+ new(to_ary | other)
+ end
+
+ # Return the difference of the keys with other keys
+ #
+ # @example
+ # difference = keys.difference(other)
+ #
+ # @param [Keys] other
+ #
+ # @return [Keys]
+ #
+ # @api public
+ def difference(other)
+ new(to_ary - other)
+ end
+
+ # Convert the Keys into an Array
+ #
+ # @example
+ # array = keys.to_ary
+ #
+ # @return [Array]
+ #
+ # @api public
+ def to_ary
+ @keys
+ end
+
+ # Test if there are no keys
+ #
+ # @example
+ # keys.empty? # => true or false
+ #
+ # @return [Boolean]
+ #
+ # @api public
+ def empty?
+ to_ary.empty?
+ end
+
+ private
+
+ # Utility method to instantiate Keys
+ #
+ # @param [Array] keys
+ #
+ # @return [Keys]
+ #
+ # @api private
+ def new(keys)
+ to_ary == keys ? self : self.class.new(keys)
+ end
+
+ # Coerce the object into Keys
+ #
+ # @param [Keys, #to_ary] object
+ #
+ # @return [Keys]
+ #
+ # @api private
+ def coerce(object)
+ self.class.coerce(object)
+ end
+
+ end # class Keys
+ end # class Relation
+end # module Veritas
View
12 spec/unit/veritas/relation/header/difference_spec.rb
@@ -6,24 +6,30 @@
describe Relation::Header, "##{method}" do
subject { object.send(method, other) }
- let(:object) { described_class.coerce([ [ :id, Integer ] ]) }
+ let(:object) { described_class.coerce([ [ :id, Integer ] ], :keys => keys) }
+ let(:other) { described_class.coerce(other_attributes, :keys => keys) }
+ let(:keys) { Relation::Keys.new([ described_class.new ]) }
context 'when the attributes overlap' do
- let(:other) { [ [ :id, Integer ] ] }
+ let(:other_attributes) { [ [ :id, Integer ] ] }
it { should be_instance_of(described_class) }
it { should be_empty }
+
+ its(:keys) { should equal(object.keys) }
end
context 'when the attributes do not overlap' do
- let(:other) { [ [ :name, String ] ] }
+ let(:other_attributes) { [ [ :name, String ] ] }
it { pending { should equal(object) } }
it { should be_instance_of(described_class) }
it { should == object }
+
+ its(:keys) { should eql(keys) }
end
end
end
View
17 spec/unit/veritas/relation/header/extend_spec.rb
@@ -5,26 +5,31 @@
describe Relation::Header, '#extend' do
subject { object.extend(attributes) }
- let(:object) { described_class.coerce([ [ :id, Integer ], [ :name, String ] ]) }
+ let(:object) { described_class.coerce([ [ :id, Integer ] ], :keys => keys) }
+ let(:keys) { Relation::Keys.new([ described_class.new ]) }
context 'with attribute objects' do
let(:attributes) { [ attribute ] }
- let(:attribute) { Attribute::Integer.new(:age) }
+ let(:attribute) { Attribute::String.new(:name) }
it { should be_instance_of(described_class) }
it 'uses the attribute object' do
- subject[:age].should equal(attribute)
+ subject[:name].should equal(attribute)
end
- its(:to_ary) { should == [ [ :id, Integer ], [ :name, String ], [ :age, Integer ] ] }
+ its(:to_ary) { should == [ [ :id, Integer ], [ :name, String ] ] }
+
+ its(:keys) { should equal(keys) }
end
context 'with Symbol attributes' do
- let(:attributes) { [ :age ] }
+ let(:attributes) { [ :name ] }
it { should be_instance_of(described_class) }
- its(:to_ary) { should == [ [ :id, Integer ], [ :name, String ], [ :age, Object ] ] }
+ its(:to_ary) { should == [ [ :id, Integer ], [ :name, Object ] ] }
+
+ its(:keys) { should equal(keys) }
end
end
View
7 spec/unit/veritas/relation/header/hash_spec.rb
@@ -5,10 +5,11 @@
describe Relation::Header, '#hash' do
subject { object.hash }
- let(:object) { described_class.new([ attribute ]) }
- let(:attribute) { Attribute::Integer.new(:id) }
+ let(:object) { described_class.new([ attribute ], :keys => keys) }
+ let(:attribute) { Attribute::Integer.new(:id) }
+ let(:keys) { Relation::Keys.new }
it_should_behave_like 'a hash method'
- it { should == described_class.hash ^ Set[attribute].hash }
+ it { should == described_class.hash ^ Set[attribute].hash ^ keys.hash }
end
View
14 spec/unit/veritas/relation/header/intersect_spec.rb
@@ -6,24 +6,30 @@
describe Relation::Header, "##{method}" do
subject { object.send(method, other) }
- let(:object) { described_class.coerce([ [ :id, Integer ] ]) }
+ let(:object) { described_class.coerce([ [ :id, Integer ] ], :keys => keys) }
+ let(:other) { described_class.coerce(other_attributes, :keys => keys) }
+ let(:keys) { Relation::Keys.new([ described_class.new ]) }
context 'when the attributes overlap' do
- let(:other) { [ [ :id, Integer ] ] }
+ let(:other_attributes) { [ [ :id, Integer ] ] }
it { pending { should equal(object) } }
it { should be_instance_of(described_class) }
- its(:to_ary) { should == other }
+ its(:to_ary) { should == other_attributes }
+
+ its(:keys) { should eql(keys) }
end
context 'when the attributes do not overlap' do
- let(:other) { [ [ :name, String ] ] }
+ let(:other_attributes) { [ [ :name, String ] ] }
it { should be_instance_of(described_class) }
it { should be_empty }
+
+ its(:keys) { should eql(keys) }
end
end
end
View
35 spec/unit/veritas/relation/header/keys_spec.rb
@@ -0,0 +1,35 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Relation::Header, '#keys' do
+ subject { object.keys }
+
+ let(:attributes) { [ [ :id, Integer ], [ :name, String ] ] }
+ let(:keys_class) { Relation::Keys }
+
+ context 'with explicit keys' do
+ let(:object) { described_class.coerce(attributes, :keys => keys) }
+ let(:keys) { [ [ :id ] ] }
+
+ it { should be_instance_of(keys_class) }
+
+ it 'has a set of keys' do
+ should == keys_class.new([ described_class.new([ object[:id] ]) ])
+ end
+
+ it { should be_frozen }
+ end
+
+ context 'with no explicit keys' do
+ let(:object) { described_class.coerce(attributes) }
+
+ it { should be_instance_of(keys_class) }
+
+ it 'has an empty set of keys' do
+ should be_empty
+ end
+
+ it { should be_frozen }
+ end
+end
View
5 spec/unit/veritas/relation/header/project_spec.rb
@@ -5,7 +5,8 @@
describe Relation::Header, '#project' do
subject { object.project(attributes) }
- let(:object) { described_class.coerce([ [ :id, Integer ], [ :name, String ] ]) }
+ let(:object) { described_class.coerce([ [ :id, Integer ], [ :name, String ] ], :keys => keys) }
+ let(:keys) { Relation::Keys.new([ described_class.coerce([ [ :id, Integer ], [ :name, String ] ]) ]) }
context 'with attribute objects' do
let(:attributes) { [ attribute ] }
@@ -26,5 +27,7 @@
it { should be_instance_of(described_class) }
its(:to_ary) { should == [ [ :id, Integer ] ] }
+
+ its(:keys) { should eql(Relation::Keys.new([ described_class.coerce([ [ :id, Integer ] ]) ])) }
end
end
View
7 spec/unit/veritas/relation/header/rename_spec.rb
@@ -5,12 +5,15 @@
describe Relation::Header, '#rename' do
subject { object.rename(aliases) }
- let(:object) { described_class.coerce([ [ :id, Integer ], [ :name, String ] ]) }
- let(:aliases) { Algebra::Rename::Aliases.coerce(object, :id => :other_id) }
+ let(:object) { described_class.coerce([ [ :id, Integer ], [ :name, String ] ], :keys => keys) }
+ let(:aliases) { Algebra::Rename::Aliases.coerce(object, :id => :other_id) }
+ let(:keys) { Relation::Keys.new([ described_class.coerce([ [ :id, Integer ] ]) ]) }
it { should be_instance_of(described_class) }
it { should_not equal(object) }
its(:to_ary) { should == [ [ :other_id, Integer ], [ :name, String ] ] }
+
+ its(:keys) { should eql(Relation::Keys.new([ described_class.coerce([ [ :other_id, Integer ] ]) ])) }
end
View
14 spec/unit/veritas/relation/header/union_spec.rb
@@ -6,24 +6,30 @@
describe Relation::Header, "##{method}" do
subject { object.send(method, other) }
- let(:object) { described_class.coerce([ [ :id, Integer ] ]) }
+ let(:object) { described_class.coerce([ [ :id, Integer ] ], :keys => keys) }
+ let(:other) { described_class.coerce(other_attributes, :keys => keys) }
+ let(:keys) { Relation::Keys.new([ described_class.new ]) }
context 'when the attributes overlap' do
- let(:other) { [ [ :id, Integer ] ] }
+ let(:other_attributes) { [ [ :id, Integer ] ] }
it { pending { should equal(object) } }
it { should be_instance_of(described_class) }
- its(:to_ary) { should == other }
+ its(:to_ary) { should == other_attributes }
+
+ its(:keys) { should eql(keys) }
end
context 'when the attributes do not overlap' do
- let(:other) { [ [ :name, String ] ] }
+ let(:other_attributes) { [ [ :name, String ] ] }
it { should be_instance_of(described_class) }
its(:to_ary) { should == [ [ :id, Integer ], [ :name, String ] ] }
+
+ its(:keys) { should eql(keys) }
end
end
end
View
29 spec/unit/veritas/relation/keys/class_methods/coerce_spec.rb
@@ -0,0 +1,29 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Relation::Keys, '.coerce' do
+ subject { object.coerce(argument) }
+
+ let(:object) { described_class }
+
+ context 'when the arguments are Keys' do
+ let(:argument) { object.new([ [ [ :id ] ] ]) }
+
+ it { should equal(argument) }
+ end
+
+ context 'when the argument responds to #to_ary' do
+ let(:argument) { [ [ [ :id ] ] ] }
+
+ it { should be_instance_of(object) }
+
+ it { should == argument }
+ end
+
+ context 'when the argument is not a Keys and does not respond to #to_ary' do
+ let(:argument) { Object.new }
+
+ specify { expect { subject }.to raise_error(NoMethodError) }
+ end
+end
View
27 spec/unit/veritas/relation/keys/difference_spec.rb
@@ -0,0 +1,27 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+[ :difference, :- ].each do |method|
+ describe Relation::Keys, "##{method}" do
+ subject { object.send(method, other) }
+
+ let(:object) { described_class.new([ header ]) }
+ let(:other) { described_class.new([ other_header ]) }
+ let(:header) { Relation::Header.coerce([ [ :id, Integer ] ]) }
+
+ context 'when the attributes overlap' do
+ let(:other_header) { Relation::Header.coerce([ [ :id, Integer ] ]) }
+
+ it { should be_instance_of(described_class) }
+
+ it { should be_empty }
+ end
+
+ context 'when the attributes do not overlap' do
+ let(:other_header) { Relation::Header.coerce([ [ :name, String ] ]) }
+
+ it { should equal(object) }
+ end
+ end
+end
View
31 spec/unit/veritas/relation/keys/each_spec.rb
@@ -0,0 +1,31 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Relation::Keys, '#each' do
+ subject { object.each { |key| yields << key } }
+
+ let(:object) { described_class.new([ header ]) }
+ let(:header) { Relation::Header.coerce([ [ :id ] ]) }
+ let(:yields) { [] }
+
+ it_should_behave_like 'an #each method'
+
+ it 'yields each attribute' do
+ expect { subject }.to change { yields.dup }.
+ from([]).
+ to([ header ])
+ end
+end
+
+describe Relation::Keys do
+ subject { object.new }
+
+ let(:object) { described_class }
+
+ it { should be_kind_of(Enumerable) }
+
+ it 'case matches Enumerable' do
+ (Enumerable === subject).should be(true)
+ end
+end
View
21 spec/unit/veritas/relation/keys/empty_spec.rb
@@ -0,0 +1,21 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Relation::Keys, '#empty?' do
+ subject { object.empty? }
+
+ let(:object) { described_class.new(keys) }
+
+ context 'with keys' do
+ let(:keys) { [ Relation::Header.coerce([ [ :id ] ]) ] }
+
+ it { should be(false) }
+ end
+
+ context 'without keys' do
+ let(:keys) { [] }
+
+ it { should be(true) }
+ end
+end
View
25 spec/unit/veritas/relation/keys/extend_spec.rb
@@ -0,0 +1,25 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Relation::Keys, '#extend' do
+ subject { object.extend(attributes) }
+
+ let(:object) { described_class.new([ header ]) }
+ let(:header) { Relation::Header.new }
+ let(:attribute) { Attribute::Integer.new(:id) }
+
+ context 'with attributes that do not change the keys' do
+ let(:attributes) { [] }
+
+ it { should equal(object) }
+ end
+
+ context 'with attributes that change the keys' do
+ let(:attributes) { [ attribute ] }
+
+ it { should be_instance_of(described_class) }
+
+ it { should == described_class.new([ Relation::Header.coerce([ attribute ]) ]) }
+ end
+end
View
27 spec/unit/veritas/relation/keys/intersect_spec.rb
@@ -0,0 +1,27 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+[ :intersect, :& ].each do |method|
+ describe Relation::Keys, "##{method}" do
+ subject { object.send(method, other) }
+
+ let(:object) { described_class.new([ header ]) }
+ let(:other) { described_class.new([ other_header ]) }
+ let(:header) { Relation::Header.coerce([ [ :id, Integer ] ]) }
+
+ context 'when the attributes overlap' do
+ let(:other_header) { Relation::Header.coerce([ [ :id, Integer ] ]) }
+
+ it { should equal(object) }
+ end
+
+ context 'when the attributes do not overlap' do
+ let(:other_header) { Relation::Header.coerce([ [ :name, String ] ]) }
+
+ it { should be_instance_of(described_class) }
+
+ it { should be_empty }
+ end
+ end
+end
View
34 spec/unit/veritas/relation/keys/project_spec.rb
@@ -0,0 +1,34 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Relation::Keys, '#project' do
+ subject { object.project(attributes) }
+
+ let(:object) { described_class.new([ header ]) }
+ let(:header) { Relation::Header.new([ id_attribute, name_attribute ]) }
+ let(:id_attribute) { Attribute::Integer.new(:id) }
+ let(:name_attribute) { Attribute::String.new(:name) }
+
+ context 'with attributes that do not change the keys' do
+ let(:attributes) { [ id_attribute, name_attribute ] }
+
+ it { should equal(object) }
+ end
+
+ context 'with attributes that change the keys' do
+ let(:attributes) { [ id_attribute ] }
+
+ it { should be_instance_of(described_class) }
+
+ it { should == described_class.new([ Relation::Header.coerce([ id_attribute ]) ]) }
+ end
+
+ context 'with attributes that clear the keys' do
+ let(:attributes) { [] }
+
+ it { should be_instance_of(described_class) }
+
+ it { should == described_class.new }
+ end
+end
View
16 spec/unit/veritas/relation/keys/rename_spec.rb
@@ -0,0 +1,16 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Relation::Keys, '#rename' do
+ subject { object.rename(aliases) }
+
+ let(:object) { described_class.new([ header1, header2 ]) }
+ let(:header1) { Relation::Header.coerce([ [ :id, Integer ] ]) }
+ let(:header2) { Relation::Header.coerce([ [ :name, String ] ]) }
+ let(:aliases) { Algebra::Rename::Aliases.coerce(header1, :id => :other_id) }
+
+ it { should be_instance_of(described_class) }
+
+ it { should == described_class.new([ Relation::Header.coerce([ [ :other_id, Integer ] ]), header2 ]) }
+end
View
16 spec/unit/veritas/relation/keys/to_ary_spec.rb
@@ -0,0 +1,16 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+describe Relation::Keys, '#to_ary' do
+ subject { object.to_ary }
+
+ let(:object) { described_class.new([ header ]) }
+ let(:header) { Relation::Header.coerce([ [ :id ] ]) }
+
+ it { should be_instance_of(Array) }
+
+ it { should == [ header ] }
+
+ it { should be_frozen }
+end
View
27 spec/unit/veritas/relation/keys/union_spec.rb
@@ -0,0 +1,27 @@
+# encoding: utf-8
+
+require 'spec_helper'
+
+[ :union, :| ].each do |method|
+ describe Relation::Keys, "##{method}" do
+ subject { object.send(method, other) }
+
+ let(:object) { described_class.new([ header ]) }
+ let(:other) { described_class.new([ other_header ]) }
+ let(:header) { Relation::Header.coerce([ [ :id, Integer] ]) }
+
+ context 'when the attributes overlap' do
+ let(:other_header) { Relation::Header.coerce([ [ :id, Integer ] ]) }
+
+ it { should equal(object) }
+ end
+
+ context 'when the attributes do not overlap' do
+ let(:other_header) { Relation::Header.coerce([ [ :name, String ] ]) }
+
+ it { should be_instance_of(described_class) }
+
+ it { should == described_class.new([ header, other_header ]) }
+ end
+ end
+end
View
1 tasks/metrics/heckle.rake
@@ -45,6 +45,7 @@ begin
%w[
Veritas::Relation::Header
+ Veritas::Relation::Keys
Veritas::Algebra::Difference::Methods
Veritas::Algebra::Intersection::Methods
Veritas::Algebra::Join::Methods
View
14 veritas.gemspec
@@ -9,7 +9,7 @@ Gem::Specification.new do |s|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Dan Kubb"]
- s.date = "2012-12-20"
+ s.date = "2012-12-24"
s.description = "Simplifies querying of structured data using relational algebra"
s.email = "dan.kubb@gmail.com"
s.extra_rdoc_files = [
@@ -114,6 +114,7 @@ Gem::Specification.new do |s|
"lib/veritas/relation/base.rb",
"lib/veritas/relation/empty.rb",
"lib/veritas/relation/header.rb",
+ "lib/veritas/relation/keys.rb",
"lib/veritas/relation/materialized.rb",
"lib/veritas/relation/operation/binary.rb",
"lib/veritas/relation/operation/combination.rb",
@@ -541,11 +542,22 @@ Gem::Specification.new do |s|
"spec/unit/veritas/relation/header/extend_spec.rb",
"spec/unit/veritas/relation/header/hash_spec.rb",
"spec/unit/veritas/relation/header/intersect_spec.rb",
+ "spec/unit/veritas/relation/header/keys_spec.rb",
"spec/unit/veritas/relation/header/project_spec.rb",
"spec/unit/veritas/relation/header/rename_spec.rb",
"spec/unit/veritas/relation/header/to_ary_spec.rb",
"spec/unit/veritas/relation/header/union_spec.rb",
"spec/unit/veritas/relation/header_spec.rb",
+ "spec/unit/veritas/relation/keys/class_methods/coerce_spec.rb",
+ "spec/unit/veritas/relation/keys/difference_spec.rb",
+ "spec/unit/veritas/relation/keys/each_spec.rb",
+ "spec/unit/veritas/relation/keys/empty_spec.rb",
+ "spec/unit/veritas/relation/keys/extend_spec.rb",
+ "spec/unit/veritas/relation/keys/intersect_spec.rb",
+ "spec/unit/veritas/relation/keys/project_spec.rb",
+ "spec/unit/veritas/relation/keys/rename_spec.rb",
+ "spec/unit/veritas/relation/keys/to_ary_spec.rb",
+ "spec/unit/veritas/relation/keys/union_spec.rb",
"spec/unit/veritas/relation/materialize_spec.rb",
"spec/unit/veritas/relation/materialized/class_methods/new_spec.rb",
"spec/unit/veritas/relation/materialized/directions_spec.rb",

0 comments on commit 86b11ee

Please sign in to comment.
Something went wrong with that request. Please try again.