-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Jeremy Boles
committed
Oct 2, 2008
0 parents
commit 4b95f91
Showing
8 changed files
with
362 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
.DS_Store | ||
aws_sdb.log | ||
coverage |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--colour | ||
--format | ||
progress | ||
--loadby | ||
mtime |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |