dm-waztables-adapter 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ require 'rubygems'
2
+
3
+ if defined?(gem)
4
+ gem 'dm-core', '~> 0.10.2'
5
+ gem 'waz-storage', '~> 1.0.0'
6
+ end
7
+
8
+ require 'dm-core'
9
+ require 'waz-tables'
10
+
11
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
12
+
13
+ require 'dm-waztables-adapter/version'
14
+ require 'dm-waztables-adapter/adapter'
15
+ require 'dm-waztables-adapter/migrations'
@@ -0,0 +1,85 @@
1
+ module DataMapper
2
+ module WAZTables
3
+ class Adapter < DataMapper::Adapters::AbstractAdapter
4
+ # @api semipublic
5
+ def initialize(name, options = {})
6
+ super
7
+ WAZ::Storage::Base.establish_connection!(:account_name => options[:account_name], :access_key => options[:access_key])
8
+ end
9
+
10
+ # @api semipublic
11
+ def create(resources)
12
+ service = WAZ::Tables::Table.service_instance
13
+ resources.each do |resource|
14
+ entity = {:row_key => resource.key.first}
15
+ entity.merge!({:partition_key => resource.model.storage_name})
16
+ resource.attributes.each { |a| entity.merge!({ a[0].to_sym => a[1] }) }
17
+ service.insert_entity(resource.model.storage_name, entity)
18
+ end
19
+ resources.size
20
+ end
21
+
22
+ # @api semipublic
23
+ def read(query)
24
+ service = WAZ::Tables::Table.service_instance
25
+ operator_mapping = {:gt => 'gt', :lt => 'lt', :gte => 'ge', :lte => 'le', :not => 'ne', :eql => 'eq'}
26
+
27
+ conditions = query.conditions.map do |c|
28
+ unless (c.class.name.eql?('Array'))
29
+ case c.slug
30
+ when :not then
31
+ "(#{c.operand.to_s.gsub(%r{=}, operator_mapping[c.slug]).gsub(%r{"}, "'")})"
32
+ when :like then
33
+ #
34
+ name, value = c.subject.name, c.value.delete('%')
35
+ (c.value =~ /^%/) ? "endswith(#{name}, '#{value}')" : ((c.value =~ /%$/) ? "startswith(#{name}, '#{value}')" : "(#{name} eq '#{value}')")
36
+ when :regexp then
37
+ else
38
+ "(#{c.subject.name} #{operator_mapping[c.slug]} #{c.value.class.name =~ /Fixnum|Float|Bignum/ ? c.value : "'#{c.value}'"})"
39
+ end
40
+ else
41
+ query.conditions
42
+ end
43
+ end
44
+
45
+ query_options = {:expression => conditions.sort.compact.join(' and ')}
46
+ query_options.merge!({:top => query.limit}) if query.limit
47
+
48
+ result = service.query(query.model.storage_name, query_options)
49
+
50
+ records = result.map do |item|
51
+ record = {}
52
+ item.each { |k,v| record.merge!( { k.to_s => v } ) }
53
+ record
54
+ end
55
+
56
+ query.filter_records(records)
57
+ end
58
+
59
+ # @api semipublic
60
+ def update(attributes, resources)
61
+ service = WAZ::Tables::Table.service_instance
62
+ resources.each do |resource|
63
+ entity = service.get_entity(resource.model.storage_name, resource.model.storage_name, resource.id)
64
+ attributes.each do |attribute|
65
+ entity[attribute[0].name] = attribute[1]
66
+ end
67
+ entity = service.update_entity(resource.model.storage_name, entity)
68
+ end
69
+ resources.size
70
+ end
71
+
72
+ # @api semipublic
73
+ def delete(resources)
74
+ service = WAZ::Tables::Table.service_instance
75
+ resources.each do |resource|
76
+ service.delete_entity(resource.model.storage_name, resource.model.storage_name, resource.id)
77
+ end
78
+ resources.size
79
+ end
80
+ end
81
+ end
82
+
83
+ Adapters::WAZTablesAdapter = DataMapper::WAZTables::Adapter
84
+ Adapters.const_added(:WAZTablesAdapter)
85
+ end
@@ -0,0 +1,32 @@
1
+ module DataMapper
2
+ module WAZTables
3
+ module Migrations
4
+ def self.included(base)
5
+ DataMapper.extend(DataMapper::Migrations::SingletonMethods)
6
+ [ :Repository, :Model ].each do |name|
7
+ DataMapper.const_get(name).send(:include, DataMapper::Migrations.const_get(name))
8
+ end
9
+ end
10
+
11
+ def storage_exists?(storage_name)
12
+ WAZ::Tables::Table.find(storage_name)
13
+ end
14
+
15
+ def create_model_storage(model)
16
+ WAZ::Tables::Table.create(model.storage_name)
17
+ end
18
+
19
+ def upgrade_model_storage(model)
20
+ WAZ::Tables::Table.create(model.storage_name) unless self.storage_exists?(model.storage_name)
21
+ end
22
+
23
+ def destroy_model_storage(model)
24
+ return unless (model_storage = self.storage_exists?(model.storage_name))
25
+ model_storage.destroy!
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ DataMapper::WAZTables.send(:include, DataMapper::WAZTables::Migrations)
32
+ DataMapper::WAZTables::Adapter.send(:include, DataMapper::WAZTables::Migrations)
@@ -0,0 +1,11 @@
1
+ module DataMapper
2
+ module WAZTables
3
+ module VERSION #:nodoc:
4
+ MAJOR = '0'
5
+ MINOR = '1'
6
+ TINY = '0'
7
+ end
8
+
9
+ Version = [VERSION::MAJOR, VERSION::MINOR, VERSION::TINY].compact * '.'
10
+ end
11
+ end
data/rakefile ADDED
@@ -0,0 +1,40 @@
1
+ require 'rake'
2
+ require 'rubygems'
3
+ require 'spec/rake/spectask'
4
+ require 'rake/gempackagetask'
5
+ require 'rake/rdoctask'
6
+
7
+ require 'lib/dm-waztables-adapter'
8
+
9
+ namespace :test do
10
+ Spec::Rake::SpecTask.new('run_with_rcov') do |t|
11
+ t.spec_files = FileList['spec/*.rb'].reject{|f| f.include?('functional')}
12
+ t.rcov = true
13
+ t.rcov_opts = ['--text-report', '--exclude', "exclude.*/.gem,spec,Library,#{ENV['GEM_HOME']}", '--sort', 'coverage' ]
14
+ t.spec_opts = ['-cfn']
15
+ end
16
+ end
17
+
18
+ namespace :dist do
19
+ spec = Gem::Specification.new do |s|
20
+ s.name = 'dm-waztables-adapter'
21
+ s.version = Gem::Version.new(DataMapper::WAZTables::Version)
22
+ s.summary = "DataMapper adapter for Windows Azure Table Services"
23
+ s.description = "Adapter created to use the Windows Azure Table Services leveraging the simplicity of DataMapper"
24
+ s.email = 'juanpablogarcia@gmail.com'
25
+ s.author = 'Juan Pablo Garcia Dalolla'
26
+ s.homepage = 'http://github.com/jpgarcia/dm-waztables-adapter'
27
+ s.require_paths = ["lib"]
28
+ s.files = FileList['rakefile', 'lib/**/*.rb']
29
+ s.test_files = Dir['spec/**/*']
30
+ s.has_rdoc = true
31
+ s.rdoc_options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
32
+
33
+ # Dependencies
34
+ s.add_dependency 'waz-storage', '>= 1.0.0'
35
+ end
36
+
37
+ Rake::GemPackageTask.new(spec) do |pkg|
38
+ pkg.need_tar = true
39
+ end
40
+ end
@@ -0,0 +1,132 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../')
2
+
3
+ require 'rubygems'
4
+ require 'spec'
5
+ require 'mocha'
6
+
7
+ require 'spec/spec_helpers'
8
+ require 'lib/dm-waztables-adapter'
9
+
10
+ describe "DataMapper adapter for Windows Azure Tables behavior" do
11
+ it "should setup datamapper with WAZTables adapter" do
12
+ WAZ::Storage::Base.expects(:establish_connection!).with({:account_name => 'account_name', :access_key => 'access_key'})
13
+ DataMapper.setup(:default, { :adapter => 'WAZTables',
14
+ :account_name => 'account_name',
15
+ :access_key => 'access_key'})
16
+
17
+ Guitarist.repository.adapter.options[:adapter].should == 'WAZTables'
18
+ end
19
+
20
+ it "should insert a new resource" do
21
+ mock_service = mock()
22
+ mock_entity = {:row_key => '1', :partition_key => 'guitarists', :id => '1', :name => 'Jimi Hendrix', :age => nil}
23
+ mock_service.expects(:insert_entity).with('guitarists', mock_entity)
24
+ WAZ::Tables::Table.expects(:service_instance).returns(mock_service)
25
+ guitarist = Guitarist.create({:id => '1', :name => 'Jimi Hendrix'})
26
+ guitarist.should_not be_nil
27
+ end
28
+
29
+ it "should update a resource" do
30
+ mock_service = mock()
31
+ original = {:row_key => '2', :partition_key => 'guitarists', :id => '2', :name => 'some_name', :age => 23}
32
+ updated = {:row_key => '2', :partition_key => 'guitarists', :id => '2', :name => 'Joe Satriani', :age => 33}
33
+ mock_service.expects(:get_entity).with('guitarists', 'guitarists', '2').returns(original)
34
+ mock_service.expects(:update_entity).with('guitarists', updated).returns(updated)
35
+ WAZ::Tables::Table.expects(:service_instance).returns(mock_service)
36
+
37
+ mock_guitarist = Guitarist.new({:id => '2', :name => 'some_name', :age => 23})
38
+ mock_guitarist.stubs(:saved?).returns(true)
39
+
40
+ Guitarist.stubs(:get).with('2').returns(mock_guitarist)
41
+
42
+ guitarist = Guitarist.get('2')
43
+ guitarist.name = 'Joe Satriani'
44
+ guitarist.age = 33
45
+ guitarist.save
46
+ end
47
+
48
+ it "should delete a resource" do
49
+ mock_service = mock()
50
+ mock_service.expects(:delete_entity).with('guitarists', 'guitarists', '3')
51
+ WAZ::Tables::Table.expects(:service_instance).returns(mock_service)
52
+
53
+ mock_guitarist = Guitarist.new({:id => '3', :name => 'Eric Clapton', :age => 23})
54
+ mock_guitarist.stubs(:saved?).returns(true)
55
+
56
+ Guitarist.stubs(:get).with('3').returns(mock_guitarist)
57
+
58
+ guitarist = Guitarist.get('3')
59
+ guitarist.destroy
60
+ end
61
+
62
+ it "should get only one record when calling get with a given id" do
63
+ mock_service = mock()
64
+ mock_result = [{ :id => '4', :name => 'Zakk Wylde', :age => 43 }]
65
+ mock_service.expects(:query).with('guitarists', {:expression => "(id eq '4')", :top => 1}).returns(mock_result)
66
+ WAZ::Tables::Table.expects(:service_instance).returns(mock_service)
67
+
68
+ guitarist = Guitarist.get('4')
69
+ guitarist.id.should == '4'
70
+ guitarist.name.should == 'Zakk Wylde'
71
+ guitarist.age.should == 43
72
+ end
73
+
74
+ it "should parse all conditions as well" do
75
+ mock_service = mock()
76
+ mock_result = [ { :id => '5', :name => 'Mark Knopfler', :age => 62 } ]
77
+ expected_expression = "(age ge 20) and (age gt 19) and (age le 70) and (age lt 71) and (name eq 'Mark Knopfler') and (name ne 'Mar Knofler')"
78
+ mock_service.expects(:query).with('guitarists', {:expression => expected_expression}).returns(mock_result)
79
+ WAZ::Tables::Table.expects(:service_instance).returns(mock_service)
80
+ guitarists = Guitarist.all({ :age.gte => 20, :age.lte => 70, :age.gt => 19, :age.lt => 71, :name => 'Mark Knopfler', :name.not => 'Mar Knofler'})
81
+ guitarists.length.should == 1
82
+
83
+ guitarists.first.id.should == '5'
84
+ guitarists.first.name.should == 'Mark Knopfler'
85
+ guitarists.first.age.should == 62
86
+ end
87
+
88
+ it "should query with startswith filters when using like" do
89
+ mock_service = mock()
90
+ mock_result = [ { :id => '6', :name => 'Glenn Tipton', :age => 62 } ]
91
+ mock_service.expects(:query).with('guitarists', { :expression => "startswith(name, 'Glen')" }).returns(mock_result)
92
+
93
+ WAZ::Tables::Table.expects(:service_instance).returns(mock_service)
94
+ Guitarist.all({ :name.like => 'Glen%' }).length.should == 1
95
+ end
96
+
97
+ it "should query with endswith filters when using like" do
98
+ mock_service = mock()
99
+ mock_result = [ { :id => '6', :name => 'Glenn Tipton', :age => 62 } ]
100
+ mock_service.expects(:query).with('guitarists', { :expression => "endswith(name, 'Tipton')" }).returns(mock_result)
101
+
102
+ WAZ::Tables::Table.expects(:service_instance).returns(mock_service)
103
+ Guitarist.all({ :name.like => '%Tipton' }).length.should == 1
104
+ end
105
+
106
+ it "should query with eq when using like without % sign" do
107
+ mock_service = mock()
108
+ mock_result = [ { :id => '6', :name => 'Glenn Tipton', :age => 62 } ]
109
+ mock_service.expects(:query).with('guitarists', { :expression => "(name eq 'Glenn Tipton')" }).returns(mock_result)
110
+
111
+ WAZ::Tables::Table.expects(:service_instance).returns(mock_service)
112
+ Guitarist.all({ :name.like => 'Glenn Tipton' }).length.should == 1
113
+ end
114
+
115
+ it "should include top option when providing :limit option" do
116
+ mock_service = mock()
117
+ mock_result = [ { :id => '6', :name => 'Glenn Tipton', :age => 62 }, { :id => '2', :name => 'K. K. Downing', :age => 58 } ]
118
+ mock_service.expects(:query).with('guitarists', { :expression => "(age ge 58)", :top => 10 }).returns(mock_result)
119
+
120
+ WAZ::Tables::Table.expects(:service_instance).returns(mock_service)
121
+ Guitarist.all({ :age.gte => 58, :limit => 10 }).length.should == 2
122
+ end
123
+
124
+ it "should recieve a raw query using :conditions option" do
125
+ mock_service = mock()
126
+ mock_result = [ { :id => '6', :name => 'Glenn Tipton', :age => 62 }, { :id => '2', :name => 'K. K. Downing', :age => 58 } ]
127
+ mock_service.expects(:query).with('guitarists', { :expression => "((age ge 50) and (age lt 100))" }).returns(mock_result)
128
+
129
+ WAZ::Tables::Table.expects(:service_instance).returns(mock_service)
130
+ Guitarist.all({ :conditions => ['(age ge 50) and (age lt 100)'] }).length.should == 2
131
+ end
132
+ end
@@ -0,0 +1,58 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../')
2
+
3
+ require 'lib/dm-waztables-adapter'
4
+
5
+ describe "WAZTables adapter functional tests" do
6
+ it "should satisfy my expectations" do
7
+ DataMapper.setup(:default, { :adapter => 'WAZTables',
8
+ :account_name => ENV['AZURE_ACCOUNT_NAME_JPGD'],
9
+ :access_key => ENV['AZURE_ACCESS_KEY_JPGD']})
10
+
11
+ # define a new model
12
+ class Guitarist
13
+ include DataMapper::Resource
14
+
15
+ property :id, String, :key => true
16
+ property :name, String
17
+ property :age, Integer
18
+ end
19
+
20
+ Guitarist.auto_upgrade!
21
+ Guitarist.all.destroy!
22
+
23
+ Guitarist.all.length.should == 0
24
+
25
+ # creating a new record
26
+ Guitarist.create(:id => '1', :name => 'Ritchie Blackmore', :age => 65)
27
+
28
+ # creating a new record
29
+ yngwie = Guitarist.new(:id => '2', :name => 'Yngwio Malmsteen', :age => 46)
30
+ yngwie.name = "Yngwie Malmsteen"
31
+ yngwie.save
32
+
33
+ # retrieving a unique record by its id
34
+ ritchie = Guitarist.get('1')
35
+ ritchie.age.should == 65
36
+ ritchie.name.should == "Ritchie Blackmore"
37
+
38
+ # updating records
39
+ ritchie.age = 66
40
+ ritchie.save
41
+
42
+ # retrieving all guitarists
43
+ guitar_players = Guitarist.all
44
+ guitar_players.length.should == 2
45
+ guitar_players.first.name.should == "Ritchie Blackmore"
46
+ guitar_players.first.age.should == 66
47
+ guitar_players.last.name.should == "Yngwie Malmsteen"
48
+ guitar_players.last.age.should == 46
49
+
50
+ # performing queries
51
+ older_guitar_players = Guitarist.all({ :age.gte => 50 })
52
+ older_guitar_players.length.should == 1
53
+
54
+ # deleting records
55
+ older_guitar_players.destroy!
56
+ Guitarist.all.length.should == 1
57
+ end
58
+ end
@@ -0,0 +1,62 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+ require 'mocha'
4
+
5
+ require 'spec/spec_helpers'
6
+ require 'lib/dm-waztables-adapter'
7
+
8
+ describe "DataMapper Migrations for Model level behavior" do
9
+ it "should remove an existing Azure Table and create a new one" do
10
+ WAZ::Tables::Table.expects(:find).with('guitarists').returns(nil)
11
+ WAZ::Tables::Table.expects(:destroy!).never()
12
+ WAZ::Tables::Table.expects(:create).with('guitarists').returns(mock())
13
+ Guitarist.auto_migrate!
14
+ end
15
+
16
+ it "should just create a new Azure Table" do
17
+ mock_table = WAZ::Tables::Table.new(:name => 'guitarists', :url => 'url')
18
+ mock_table.expects(:destroy!)
19
+ WAZ::Tables::Table.expects(:find).with('guitarists').returns(mock_table)
20
+ WAZ::Tables::Table.expects(:create).with('guitarists').returns(mock())
21
+ Guitarist.auto_migrate!
22
+ end
23
+
24
+ it "should create a new Azure Table only if it doesn't exist" do
25
+ WAZ::Tables::Table.expects(:find).with('guitarists').returns(nil)
26
+ WAZ::Tables::Table.expects(:destroy!).never()
27
+ WAZ::Tables::Table.expects(:create).with('guitarists').returns(mock())
28
+ Guitarist.auto_upgrade!
29
+ end
30
+
31
+ it "should create a new Azure Table when it doesn't exist" do
32
+ WAZ::Tables::Table.expects(:find).with('guitarists').returns(mock())
33
+ WAZ::Tables::Table.expects(:destroy!).never()
34
+ WAZ::Tables::Table.expects(:create).with('guitarists').never()
35
+ Guitarist.auto_upgrade!
36
+ end
37
+ end
38
+
39
+ describe "DataMapper Migrations for Repository level behavior" do
40
+ it "should remove existing Azure Tables and create new ones" do
41
+ WAZ::Tables::Table.expects(:find).with('guitarists').returns(nil)
42
+ WAZ::Tables::Table.expects(:destroy!).never()
43
+ WAZ::Tables::Table.expects(:create).with('guitarists').returns(mock())
44
+
45
+ mock_table = WAZ::Tables::Table.new(:name => 'guitars', :url => 'url')
46
+ mock_table.expects(:destroy!)
47
+ WAZ::Tables::Table.expects(:find).with('guitars').returns(mock_table)
48
+ WAZ::Tables::Table.expects(:create).with('guitars').returns(mock())
49
+ DataMapper.auto_migrate!
50
+ end
51
+
52
+ it "should create only if a Table doesn't exist" do
53
+ WAZ::Tables::Table.expects(:find).with('guitarists').returns(nil)
54
+ WAZ::Tables::Table.expects(:destroy!).never()
55
+ WAZ::Tables::Table.expects(:create).with('guitarists').returns(mock())
56
+
57
+ WAZ::Tables::Table.expects(:find).with('guitars').returns(mock())
58
+ WAZ::Tables::Table.expects(:destroy!).never()
59
+ WAZ::Tables::Table.expects(:create).never()
60
+ DataMapper.auto_upgrade!
61
+ end
62
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,4 @@
1
+ --colour
2
+ --loadby random
3
+ --format progress
4
+ --backtrace
@@ -0,0 +1,25 @@
1
+ Spec::Runner.configure do |config|
2
+ config.mock_with :mocha
3
+
4
+ config.before :each do
5
+ class Guitarist
6
+ include DataMapper::Resource
7
+
8
+ property :id, String, :key => true
9
+ property :name, String
10
+ property :age, Integer
11
+ end
12
+
13
+ class Guitar
14
+ include DataMapper::Resource
15
+
16
+ property :id, String, :key => true
17
+ property :brand, String
18
+ end
19
+ DataMapper.setup(:default, { :adapter => 'WAZTables', :name => 'db', :account_name => 'n', :access_key => 'k'})
20
+ end
21
+
22
+ config.after :each do
23
+ DataMapper::Repository.adapters.delete(:default)
24
+ end
25
+ end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dm-waztables-adapter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Juan Pablo Garcia Dalolla
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-02-08 00:00:00 -03:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: waz-storage
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.0.0
24
+ version:
25
+ description: Adapter created to use the Windows Azure Table Services leveraging the simplicity of DataMapper
26
+ email: juanpablogarcia@gmail.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files: []
32
+
33
+ files:
34
+ - rakefile
35
+ - lib/dm-waztables-adapter/adapter.rb
36
+ - lib/dm-waztables-adapter/migrations.rb
37
+ - lib/dm-waztables-adapter/version.rb
38
+ - lib/dm-waztables-adapter.rb
39
+ has_rdoc: true
40
+ homepage: http://github.com/jpgarcia/dm-waztables-adapter
41
+ licenses: []
42
+
43
+ post_install_message:
44
+ rdoc_options:
45
+ - --line-numbers
46
+ - --inline-source
47
+ - -A cattr_accessor=object
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ version:
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: "0"
61
+ version:
62
+ requirements: []
63
+
64
+ rubyforge_project:
65
+ rubygems_version: 1.3.5
66
+ signing_key:
67
+ specification_version: 3
68
+ summary: DataMapper adapter for Windows Azure Table Services
69
+ test_files:
70
+ - spec/adapter_spec.rb
71
+ - spec/functional_spec.rb
72
+ - spec/migrations_spec.rb
73
+ - spec/spec.opts
74
+ - spec/spec_helpers.rb