Skip to content

Commit

Permalink
Add gateway
Browse files Browse the repository at this point in the history
* Update schema to use a thread safe Hash.
* Add #[] and #[]= methods to the adapter api to allow getting and
  setting the named relations.
* Add initial #update method to the relation/gateway api. Will need
  to add Axiom::Relation::Operation::Update as well as base #update
  method to Axiom relations matching this api.
  • Loading branch information
dkubb committed Jul 18, 2013
1 parent c6dfeb6 commit 8d45602
Show file tree
Hide file tree
Showing 17 changed files with 497 additions and 62 deletions.
3 changes: 2 additions & 1 deletion axiom-memory-adapter.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ Gem::Specification.new do |gem|
gem.test_files = `git ls-files -- spec/{unit,integration}`.split($/)
gem.extra_rdoc_files = %w[LICENSE README.md CONTRIBUTING.md TODO]

gem.add_runtime_dependency('axiom', '~> 0.1.0')
gem.add_runtime_dependency('axiom', '~> 0.1.0')
gem.add_runtime_dependency('thread_safe', '~> 0.1.0')

gem.add_development_dependency('bundler', '~> 1.3', '>= 1.3.5')
end
4 changes: 2 additions & 2 deletions config/flay.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
---
threshold: 14
total_score: 18
threshold: 11
total_score: 38
103 changes: 79 additions & 24 deletions lib/axiom/adapter/memory.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# encoding: utf-8

require 'axiom'
require 'thread_safe'

module Axiom
module Adapter
Expand All @@ -9,34 +10,64 @@ module Adapter
class Memory
include Adamantium::Flat

# The schema
#
# @return [Hash{String => Axiom::Adapter::Memory::Gateway}]
#
# @api private
attr_reader :schema
private :schema

# Initialize a Memory adapter
#
# @param [Hash{String => Relation::Materialized}] schema
# @param [Hash{String => Axiom::Adapter::Memory::Gateway}] schema
#
# @return [undefined]
#
# @api private
def initialize(schema = {})
@schema = ThreadSafe::Hash[schema]
end

# Get gateway in the schema
#
# @param [#to_str] name
#
# @return [Axiom::Adapter::Memory::Gateway]
#
# @api private
def [](name)
schema.fetch(name)
end

# Set the gateway in the schema
#
# @param [#to_str] name
# @param [Axiom::Relation] relation
#
# @return [undefined]
#
# @api private
def initialize(schema)
@schema = schema.dup
def []=(name, relation)
schema[name] = Gateway.new(self, name, relation)
end

# Insert a set of tuples into memory
#
# @example insert a new user
# header = Axiom::Header.coerce([ [ :name, String ] ])
# tuple = Axiom::Tuple.new(header, [ 'Dan Kubb' ])
# adapter.insert(users, [ tuple ])
# header = Axiom::Header.coerce([[:name, String]])
# tuple = Axiom::Tuple.new(header, ['Dan Kubb'])
# adapter.insert(users, [tuple])
#
# @param [Relation] relation
# @param [Axiom::Adapter::Memory::Gateway] gateway
# @param [Enumerable<Tuple>] tuples
# a set of tuples to insert into the relation
#
# @return [self]
#
# @api public
def insert(relation, tuples)
name = relation.name
@schema[name] = @schema.fetch(name).insert(tuples)
def insert(gateway, tuples)
self[gateway.name] = gateway.relation.insert(tuples)
self
end

Expand All @@ -45,7 +76,7 @@ def insert(relation, tuples)
# @example
# adapter.read(users) { |tuple| ... }
#
# @param [Relation] relation
# @param [Axiom::Adapter::Memory::Gateway] gateway
#
# @yield [tuple]
#
Expand All @@ -55,9 +86,9 @@ def insert(relation, tuples)
# @return [self]
#
# @api public
def read(relation, &block)
return to_enum(__method__, relation) unless block_given?
@schema.fetch(relation.name).each(&block)
def read(gateway, &block)
return to_enum(__method__, gateway) unless block_given?
self[gateway.name].each(&block)
self
end

Expand All @@ -72,16 +103,17 @@ def read(relation, &block)
# that represents something that accepts a tuple and returns a tuple,
# rather than simply extracting a value from a tuple attribute.
#
# @param [Relation] relation
# @param [#call] function
# @param [Axiom::Adapter::Memory::Gateway] gateway
#
# @yield [tuple]
# @yieldparam [Axiom::Relation::Tuple] tuple
# @yieldreturn [Axiom::Relation::Tuple]
#
# @return [self]
#
# @api public
def update(relation, function)
name = relation.name
base = @schema.fetch(name)
@schema[name] = base.replace(base.map(&function))
def update(gateway, &block)
self[gateway.name] = gateway.relation.update(&block)
self
end

Expand All @@ -90,19 +122,42 @@ def update(relation, function)
# @example
# adapter.delete(inactive_users)
#
# @param [Relation] relation
# @param [Axiom::Adapter::Memory::Gateway] gateway
# @param [Relation] other
#
# @return [self]
#
# @api public
def delete(relation)
name = relation.name
@schema[name] = @schema.fetch(name).delete(relation)
def delete(gateway, other)
self[gateway.name] = gateway.relation.delete(other)
self
end

end # Memory
end # Adapter

# XXX: patch axiom relations
class Relation

# XXX: patch #update into Materialized
class Materialized

# Return an updated materialized relation
#
# @example
# updated = materialized.update { |tuple| ... }
#
# @return [Axiom::Relation::Materialized]
#
# @api public
def update(&block)
replace(map(&block))
end

end # Materialized
end # Relation
end # Axiom

require 'axiom/adapter/memory/gateway'

require 'axiom/adapter/memory/version'
121 changes: 121 additions & 0 deletions lib/axiom/adapter/memory/gateway.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# encoding: utf-8

module Axiom
module Adapter
class Memory

# A gateway relation to the memory adapter
class Gateway < Relation
include Adamantium::Flat, Relation::Proxy
include Equalizer.new(:adapter, :name, :relation)

# The adapter the gateway will use to fetch results
#
# @return [Axiom:Adapter::Memory]
#
# @api private
attr_reader :adapter
protected :adapter

# The gateway name
#
# @example
# gateway.name # => "users"
#
# @return [String]
#
# @api public
attr_reader :name

# The gateway relation
#
# @example
# gateway.relation # => a set of tuples
#
# @return [Axiom::Relation]
#
# @api public
attr_reader :relation

# Initialize a memory gateway
#
# @param [Axiom::Adapter::Memory] adapter
# @param [#to_str] name
# @param [Axiom::Relation] relation
#
# @return [undefined]
#
# @api private
def initialize(adapter, name, relation)
@adapter = adapter
@name = name.to_str
@relation = relation.materialize
end

# Return a relation that represents an insertion into a relation
#
# @example
# insertion = relation.insert(other)
#
# @param [Enumerable] other
#
# @return [Axiom::Adapter::Memory::Gateway]
#
# @api public
def insert(other)
adapter.insert(self, other)
new
end

# Return a relation that represents a deletion from a relation
#
# @example
# deletion = relation.delete(other)
#
# @param [Enumerable] other
#
# @return [Axiom::Adapter::Memory::Gateway]
#
# @api public
def delete(other)
adapter.delete(self, other)
new
end

# Return a relation that represents an update of a relation
#
# @example with a hash
# update = relation.update(a: 1, b: 2)
#
# @example with a block
# update = relation.update do |r|
# r[:name] = r.concat(r[:first], r[:last])
# end
#
# @param [Enumerable] other
#
# @return [Axiom::Adapter::Memory::Gateway]
#
# @api public
def update(&block)
# TODO: implement logic to handle restriction-like input
adapter.update(self, &block)
new
end

private

# Return a new gateway for the relation
#
# @return [Axiom::Adapter::Memory::Gateway]
#
# @api private
def new
adapter[name]
end

end # Gateway

end # Memory
end # Adapter
end # Axiom
50 changes: 50 additions & 0 deletions spec/integration/axiom/adapter/memory/gateway_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# encoding: utf-8

require 'spec_helper'

describe Axiom::Adapter::Memory::Gateway do
subject { described_class.new(adapter, name, relation) }

include_context 'relation'

let(:adapter) { Axiom::Adapter::Memory.new(schema) }

describe '#insert' do
it 'inserts a new tuple' do
new_gateway = subject.insert([[3], [4]])
expect(new_gateway.to_a).to eq([[1], [2], [3], [4]])
end
end

describe '#replace' do
it 'replaces with new tuples' do
new_gateway = subject.replace([[2]])
expect(new_gateway.to_a).to eq([[2]])
end
end

describe '#delete' do
it 'deletes matching tuples' do
new_gateway = subject.delete([[3], [4]])
expect(new_gateway.to_a).to eq([[1], [2]])
end
end

describe '#update' do
pending 'Design interface' do
context 'with a Hash argument' do
it 'updates matching tuples' do
new_gateway = subject.update(id: 2)
expect(new_gateway.to_a).to eq([[2]])
end
end
end

context 'with a block' do
it 'updates matching tuples' do
new_gateway = subject.update { |tuple| [2] }
expect(new_gateway.to_a).to eq([[2]])
end
end
end
end
8 changes: 8 additions & 0 deletions spec/shared/base_relation_context.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
shared_context 'relation' do
let(:relation) { Relation.new(header, body) }
let(:body) { [[1], [2], [3]] }
let(:header) { [id_attribute] }
let(:id_attribute) { Attribute::Integer.new(:id) }
let(:schema) { {} }
let(:name) { 'users'.freeze }
end
19 changes: 19 additions & 0 deletions spec/unit/axiom/adapter/memory/class_methods/new_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# encoding: utf-8

require 'spec_helper'

describe Adapter::Memory, '.new' do
context 'with a schema' do
subject { described_class.new(schema) }

let(:schema) { {} }

it { should be_instance_of(described_class) }
end

context 'without a schema' do
subject { described_class.new }

it { should be_instance_of(described_class) }
end
end

0 comments on commit 8d45602

Please sign in to comment.