dm-waztables-adapter 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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