Skip to content

Commit

Permalink
Initial commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
Jeremy Boles committed Oct 2, 2008
0 parents commit 4b95f91
Show file tree
Hide file tree
Showing 8 changed files with 362 additions and 0 deletions.
Empty file added .autotest
Empty file.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.DS_Store
aws_sdb.log
coverage
Empty file added README
Empty file.
26 changes: 26 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
require 'rubygems'
require 'spec'
require 'spec/rake/spectask'
require 'pathname'

ROOT = Pathname(__FILE__).dirname.expand_path
require ROOT + 'lib/simpledb_adapter'

task :default => [ :spec ]

desc 'Run specifications'
Spec::Rake::SpecTask.new(:spec) do |t|
if File.exists?('spec/spec.opts')
t.spec_opts << '--options' << 'spec/spec.opts'
end
t.spec_files = Pathname.glob((ROOT + 'spec/**/*_spec.rb').to_s)

begin
t.rcov = ENV.has_key?('NO_RCOV') ? ENV['NO_RCOV'] != 'true' : true
t.rcov_opts << '--exclude' << 'spec'
t.rcov_opts << '--text-summary'
t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
rescue Exception
# rcov not installed
end
end
163 changes: 163 additions & 0 deletions lib/simpledb_adapter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
require 'rubygems'
require 'dm-core'
require 'aws_sdb'
require 'digest/sha1'

module DataMapper
module Adapters
class SimpleDBAdapter < AbstractAdapter

def create(resources)
created = 0
resources.each do |resource|
simpledb_type = simpledb_type(resource.model)
attributes = resource.attributes.merge(:simpledb_type => simpledb_type)

item_name = "#{simpledb_type}+"
keys = resource.model.key(self.name).sort {|a,b| a.name.to_s <=> b.name.to_s}
item_name += keys.map do |property|
resource.instance_variable_get(property.instance_variable_name)
end.join('-')
item_name = Digest::SHA1.hexdigest(item_name)

sdb.put_attributes(domain, item_name, attributes)

created += 1
end
created
end

def delete(query)
deleted = 0
simpledb_type = simpledb_type(query.model)

item_name = "#{simpledb_type}+"
keys = query.model.key(self.name).sort {|a,b| a.name.to_s <=> b.name.to_s }
conditions = query.conditions.sort {|a,b| a[1].name.to_s <=> b[1].name.to_s }
item_name += conditions.map do |property|
property[2].to_s
end.join('-')
item_name = Digest::SHA1.hexdigest(item_name)

sdb.delete_attributes(domain, item_name)

deleted += 1

# Curosity check to make sure we are only dealing with a delete
conditions = conditions.map {|c| c[0] }.uniq
operators = [ :gt, :gte, :lt, :lte, :not, :like, :in ]
raise NotImplementedError.new('Only :eql on delete at the moment') if (operators - conditions).size != operators.size

deleted
end

def read_many(query)
simpledb_type = simpledb_type(query.model)

conditions = ["['simpledb_type' = '#{simpledb_type}']"]
if query.conditions.size > 0
conditions += query.conditions.map do |condition|
operator = case condition[0]
when :eql then '='
when :not then '!='
when :gt then '>'
when :gte then '>='
when :lt then '<'
when :lte then '<='
else raise "Invalid query operator: #{operator.inspect}"
end
"['#{condition[1].name.to_s}' #{operator} '#{condition[2].to_s}']"
end
end

results = sdb.query(domain, conditions.compact.join(' intersection '))
results = results[0].map {|d| sdb.get_attributes(domain, d) }

Collection.new(query) do |collection|
results.each do |result|
data = query.fields.map do |property|
value = result[property.field.to_s]
if value.size > 1
value.map {|v| property.typecast(v) }
else
property.typecast(value[0])
end
end
collection.load(data)
end
end
end

def read_one(query)
simpledb_type = simpledb_type(query.model)

item_name = "#{simpledb_type}+"
keys = query.model.key(self.name).sort {|a,b| a.name.to_s <=> b.name.to_s }
conditions = query.conditions.sort {|a,b| a[1].name.to_s <=> b[1].name.to_s }
item_name += conditions.map do |property|
property[2].to_s
end.join('-')
item_name = Digest::SHA1.hexdigest(item_name)

data = sdb.get_attributes(domain, item_name)
unless data.empty?
data = query.fields.map do |property|
value = data[property.field.to_s]
if value.size > 1
value.map {|v| property.typecast(v) }
else
property.typecast(value[0])
end
end

query.model.load(data, query)
end
end

def update(attributes, query)
updated = 0

simpledb_type = simpledb_type(query.model)

item_name = "#{simpledb_type}+"
keys = query.model.key(self.name).sort {|a,b| a.name.to_s <=> b.name.to_s }
conditions = query.conditions.sort {|a,b| a[1].name.to_s <=> b[1].name.to_s }
item_name += conditions.map do |property|
property[2].to_s
end.join('-')
item_name = Digest::SHA1.hexdigest(item_name)

attributes = attributes.to_a.map {|a| [a.first.name.to_s, a.last]}.to_hash
sdb.put_attributes(domain, item_name, attributes, true)

updated += 1

# Curosity check to make sure we are only dealing with a delete
conditions = conditions.map {|c| c[0] }.uniq
selectors = [ :gt, :gte, :lt, :lte, :not, :like, :in ]
raise NotImplementedError.new('Only :eql on delete at the moment') if (selectors - conditions).size != selectors.size

updated
end
private

def domain
@uri[:domain]
end

def sdb
@sdb ||= AwsSdb::Service.new(:access_key_id => @uri[:access_key], :secret_access_key => @uri [:secret_key])
@sdb
end

def simpledb_type(model)
model.storage_name(model.repository.name)
end

end # class SimpleDBAdapter

# Required naming scheme.
SimpledbAdapter = SimpleDBAdapter

end # module Adapters
end # module DataMapper
131 changes: 131 additions & 0 deletions spec/simpledb_adapter_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
require 'pathname'
require Pathname(__FILE__).dirname.expand_path + 'spec_helper'

describe DataMapper::Adapters::SimpleDBAdapter do
before(:each) do
@person_attrs = { :id => "person-#{Time.now.to_f.to_s}", :name => 'Jeremy Boles', :age => 25,
:wealth => 25.00, :birthday => Date.today }
@person = Person.new(@person_attrs)
end

it 'should create a record' do
@person.save.should be_true
@person.id.should_not be_nil
@person.destroy
end

describe 'with a saved record' do
before(:each) { @person.save }
after(:each) { @person.destroy }

it 'should get a record' do
person = Person.get!(@person.id, @person.name)
person.should_not be_nil
person.wealth.should == @person.wealth
end

it 'should not get records of the wrong type by id' do
Company.get(@person.id, @person.name).should == nil
lambda { Company.get!(@person.id, @person.name) }.should raise_error(DataMapper::ObjectNotFoundError)
end

it 'should update a record' do
person = Person.get!(@person.id, @person.name)
person.wealth = 100.00
person.save

person = Person.get!(@person.id, @person.name)
person.wealth.should_not == @person.wealth
person.age.should == @person.age
person.id.should == @person.id
person.name.should == @person.name
end

it 'should destroy a record' do
@person.destroy.should be_true
end

end

describe 'with multiple records saved' do
before(:each) do
@jeremy = Person.create(@person_attrs.merge(:id => Time.now.to_f.to_s, :name => "Jeremy Boles", :age => 25))
@danielle = Person.create(@person_attrs.merge(:id => Time.now.to_f.to_s, :name => "Danille Boles", :age => 26))
@keegan = Person.create(@person_attrs.merge(:id => Time.now.to_f.to_s, :name => "Keegan Jones", :age => 20))
end

after(:each) do
@jeremy.destroy
@danielle.destroy
@keegan.destroy
end

it 'should get all records' do
Person.all.length.should == 3
end

it 'should get records by eql matcher' do
people = Person.all(:age => 25)
people.length.should == 1
end

it 'should get records by not matcher' do
people = Person.all(:age.not => 25)
people.length.should == 2
end

it 'should get records by gt matcher' do
people = Person.all(:age.gt => 25)
people.length.should == 1
end

it 'should get records by gte matcher' do
people = Person.all(:age.gte => 25)
people.length.should == 2
end

it 'should get records by lt matcher' do
people = Person.all(:age.lt => 25)
people.length.should == 1
end

it 'should get records by lte matcher' do
people = Person.all(:age.lte => 25)
people.length.should == 2
end

it 'should get records with multiple matchers' do
people = Person.all(:birthday => Date.today, :age.lte => 25)
people.length.should == 2
end

it 'should handle DateTime' do
pending 'Need to figure out how to coerce DateTime'
time = Time.now
@jeremy.created_at = time
@jeremy.save
person = Person.get!(@jeremy.id, @jeremy.name)
person.created_at.should == time
end

it 'should handle Date' do
person = Person.get!(@jeremy.id, @jeremy.name)
person.birthday.should == @jeremy.birthday
end

it 'should order records'
it 'should get records by the like matcher'
end


describe 'associations' do
it 'should work with belongs_to associations'
it 'should work with has n associations'
end

describe 'STI' do
it 'should override default type'
it 'should load descendents on parent.all'
it 'should raise an error if you have a column named couchdb_type'
end
end
5 changes: 5 additions & 0 deletions spec/spec.opts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
--colour
--format
progress
--loadby
mtime
34 changes: 34 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
require 'pathname'
require Pathname(__FILE__).dirname.parent.expand_path + 'lib/simpledb_adapter'

access_key = ENV['AMAZON_ACCESS_KEY_ID']
secret_key = ENV['AMAZON_SECRET_ACCESS_KEY']

DataMapper.setup(:default, {
:adapter => 'simpledb',
:access_key => access_key,
:secret_key => secret_key,
:domain => 'missionaries'
})

class Person
include DataMapper::Resource

property :id, String, :key => true
property :name, String, :key => true
property :age, Integer
property :wealth, Float
property :birthday, Date
property :created_at, DateTime

belongs_to :company
end

class Company
include DataMapper::Resource

property :id, String, :key => true
property :name, String, :key => true

has n, :people
end

0 comments on commit 4b95f91

Please sign in to comment.