dynamoid 0.0.3 → 0.0.4

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.
data/Dynamoid.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "dynamoid"
8
- s.version = "0.0.3"
8
+ s.version = "0.0.4"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Josh Symonds"]
12
- s.date = "2012-02-26"
12
+ s.date = "2012-02-27"
13
13
  s.description = "Dynamoid is an ORM for Amazon's DynamoDB that supports offline development, associations, querying, and everything else you'd expect from an ActiveRecord-style replacement."
14
14
  s.email = "josh@joshsymonds.com"
15
15
  s.extra_rdoc_files = [
@@ -40,6 +40,8 @@ Gem::Specification.new do |s|
40
40
  "lib/dynamoid/components.rb",
41
41
  "lib/dynamoid/config.rb",
42
42
  "lib/dynamoid/config/options.rb",
43
+ "lib/dynamoid/criteria.rb",
44
+ "lib/dynamoid/criteria/chain.rb",
43
45
  "lib/dynamoid/document.rb",
44
46
  "lib/dynamoid/errors.rb",
45
47
  "lib/dynamoid/fields.rb",
@@ -63,6 +65,8 @@ Gem::Specification.new do |s|
63
65
  "spec/dynamoid/associations_spec.rb",
64
66
  "spec/dynamoid/attributes_spec.rb",
65
67
  "spec/dynamoid/config_spec.rb",
68
+ "spec/dynamoid/criteria/chain_spec.rb",
69
+ "spec/dynamoid/criteria_spec.rb",
66
70
  "spec/dynamoid/document_spec.rb",
67
71
  "spec/dynamoid/fields_spec.rb",
68
72
  "spec/dynamoid/finders_spec.rb",
data/README.markdown CHANGED
@@ -4,7 +4,7 @@ Dynamoid is an ORM for Amazon's DynamoDB for Ruby applications. It provides simi
4
4
 
5
5
  ## Warning!
6
6
 
7
- I'm still working on this gem a lot. You can only use the old-school ActiveRecord style finders like ```find_all_by_<attribute_name>``` or directly finding by an ID.
7
+ I'm still working on this gem a lot. It only provides .where(arguments) in its criteria chaining so far. More is coming though!
8
8
 
9
9
  ## Installation
10
10
 
@@ -14,11 +14,11 @@ Installing Dynamoid is pretty simple. First include the Gem in your Gemfile:
14
14
  gem 'dynamoid'
15
15
  ```
16
16
 
17
- Then you need to initialize it to get it going, so put code similar to this somewhere (a Rails initializer would be a great place for this if you're using Rails):
17
+ Then you need to initialize it to get it going. Put code similar to this somewhere (a Rails initializer would be a great place for this if you're using Rails):
18
18
 
19
19
  ```ruby
20
20
  Dynamoid.configure do |config|
21
- config.adapter = 'local' # This adapter allows offline development without connecting to the DynamoDB servers.
21
+ config.adapter = 'local' # This adapter allows offline development without connecting to the DynamoDB servers. Data is NOT persisted.
22
22
  # config.adapter = 'aws_sdk' # This adapter establishes a connection to the DynamoDB servers using's Amazon's own awful AWS gem.
23
23
  # config.access_key = 'access_key' # If connecting to DynamoDB, your access key is required.
24
24
  # config.secret_key = 'secret_key' # So is your secret key.
@@ -52,25 +52,37 @@ class User
52
52
  end
53
53
  ```
54
54
 
55
- ### Usage
55
+ ## Usage
56
56
 
57
- Right now, you can only do a couple things with this amazing functionality:
57
+ Dynamoid's syntax is very similar to ActiveRecord's.
58
58
 
59
59
  ```ruby
60
60
  u = User.new(:name => 'Josh')
61
61
  u.email = 'josh@joshsymonds.com'
62
62
  u.save
63
+ ```
64
+
65
+ Save forces persistence to the datastore: a unique ID is also assigned, but it is a string and not an auto-incrementing number.
66
+
67
+ ```ruby
68
+ u.id # => "3a9f7216-4726-4aea-9fbc-8554ae9292cb"
69
+ ```
70
+
71
+ Along with persisting the model's attributes, indexes are automatically updated on save. To use associations, you use association methods very similar to ActiveRecord's:
63
72
 
73
+ ```ruby
64
74
  address = u.addresses.create
65
75
  address.city = 'Chicago'
66
76
  address.save
67
-
68
- u == User.find(u.id)
69
- u == User.find_by_name('Josh')
70
- u.addresses == User.find_by_name_and_email('Josh','josh@joshsymonds.com').addresses
71
77
  ```
72
78
 
73
- Not super exciting yet, true... but it's getting there!
79
+ Querying can be done in one of three ways:
80
+
81
+ ```ruby
82
+ Address.find(address.id) # Find directly by ID.
83
+ Address.where(:city => 'Chicago').all # Find by any number of matching criteria... though presently only "where" is supported.
84
+ Address.find_by_city('Chicago') # The same as above, but using ActiveRecord's older syntax.
85
+ ```
74
86
 
75
87
  ## Credits
76
88
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.3
1
+ 0.0.4
data/lib/dynamoid.rb CHANGED
@@ -14,6 +14,7 @@ require 'dynamoid/fields'
14
14
  require 'dynamoid/indexes'
15
15
  require 'dynamoid/associations'
16
16
  require 'dynamoid/persistence'
17
+ require 'dynamoid/criteria'
17
18
  require 'dynamoid/finders'
18
19
  require 'dynamoid/config'
19
20
  require 'dynamoid/components'
@@ -85,9 +85,8 @@ module Dynamoid
85
85
  table = @@connection.tables[table_name]
86
86
  table.load_schema
87
87
  results = []
88
- table.items.select do |data|
89
- attributes = data.attributes.symbolize_keys!
90
- results << attributes if scan_hash.all?{|k, v| !attributes[k].nil? && attributes[k] == v}
88
+ table.items.where(scan_hash).select do |data|
89
+ results << data.attributes.symbolize_keys!
91
90
  end
92
91
  results
93
92
  end
@@ -21,5 +21,6 @@ module Dynamoid #:nodoc
21
21
  include Dynamoid::Persistence
22
22
  include Dynamoid::Finders
23
23
  include Dynamoid::Associations
24
+ include Dynamoid::Criteria
24
25
  end
25
26
  end
@@ -0,0 +1,20 @@
1
+ require 'dynamoid/criteria/chain'
2
+
3
+ # encoding: utf-8
4
+ module Dynamoid #:nodoc:
5
+
6
+ # This module defines criteria and criteria chains.
7
+ module Criteria
8
+ extend ActiveSupport::Concern
9
+
10
+ module ClassMethods
11
+ [:where, :all, :first, :each].each do |meth|
12
+ define_method(meth) do |opts|
13
+ chain = Dynamoid::Criteria::Chain.new(self)
14
+ chain.send(meth, opts)
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ end
@@ -0,0 +1,68 @@
1
+ # encoding: utf-8
2
+ module Dynamoid #:nodoc:
3
+ module Criteria
4
+
5
+ # The class object that gets passed around indicating state of a building query.
6
+ # Also provides query execution.
7
+ class Chain
8
+ attr_accessor :query, :source, :index, :values
9
+ include Enumerable
10
+
11
+ def initialize(source)
12
+ @query = {}
13
+ @source = source
14
+ end
15
+
16
+ def where(args)
17
+ args.each {|k, v| query[k] = v}
18
+ self
19
+ end
20
+
21
+ def all
22
+ records
23
+ end
24
+
25
+ def first
26
+ records.first
27
+ end
28
+
29
+ def each(&block)
30
+ records.each(&block)
31
+ end
32
+
33
+ private
34
+
35
+ def records
36
+ return records_with_index unless index.empty?
37
+ records_without_index
38
+ end
39
+
40
+ def records_with_index
41
+ ids = Dynamoid::Adapter.get_item(source.index_table_name(index), source.key_for_index(index, values_for_index))
42
+ if ids.nil? || ids.empty?
43
+ []
44
+ else
45
+ Array(source.find(ids[:ids].to_a))
46
+ end
47
+ end
48
+
49
+ def records_without_index
50
+ if Dynamoid::Config.warn_on_scan
51
+ Dynamoid.logger.warn 'Queries without an index are forced to use scan and are generally much slower than indexed queries!'
52
+ Dynamoid.logger.warn "You can index this query by adding this to #{self.to_s.downcase}.rb: index [#{attributes.sort.collect{|attr| ":#{attr}"}.join(', ')}]"
53
+ end
54
+ Dynamoid::Adapter.scan(source.table_name, query).collect {|hash| source.new(hash)}
55
+ end
56
+
57
+ def values_for_index
58
+ [].tap {|arr| index.each{|i| arr << query[i]}}
59
+ end
60
+
61
+ def index
62
+ Array(source.indexes.find {|i| i == query.keys.sort.collect(&:to_sym)})
63
+ end
64
+ end
65
+
66
+ end
67
+
68
+ end
@@ -8,6 +8,8 @@ module Dynamoid #:nodoc:
8
8
  include Dynamoid::Components
9
9
 
10
10
  attr_accessor :new_record
11
+ alias :persisted? :new_record
12
+ alias :new_record? :new_record
11
13
 
12
14
  def initialize(attrs = {})
13
15
  @new_record = true
@@ -31,29 +31,17 @@ module Dynamoid #:nodoc:
31
31
  if method =~ /find/
32
32
  finder = method.to_s.split('_by_').first
33
33
  attributes = method.to_s.split('_by_').last.split('_and_')
34
-
35
- results = if index = self.indexes.find {|i| i == attributes.sort.collect(&:to_sym)}
36
- ids = Dynamoid::Adapter.get_item(self.index_table_name(index), self.key_for_index(index, args))
37
- if ids.nil? || ids.empty?
38
- []
39
- else
40
- self.find(ids[:ids].to_a)
41
- end
42
- else
43
- if Dynamoid::Config.warn_on_scan
44
- Dynamoid.logger.warn 'Queries without an index are forced to use scan and are generally much slower than indexed queries!'
45
- Dynamoid.logger.warn "You can index this query by adding this to #{self.to_s.downcase}.rb: index [#{attributes.sort.collect{|attr| ":#{attr}"}.join(', ')}]"
46
- end
47
- scan_hash = {}
48
- attributes.each_with_index {|attr, index| scan_hash[attr.to_sym] = args[index]}
49
- Dynamoid::Adapter.scan(self.table_name, scan_hash).collect {|hash| self.new(hash)}
50
- end
34
+
35
+ chain = Dynamoid::Criteria::Chain.new(self)
36
+ chain.query = Hash.new.tap {|h| attributes.each_with_index {|attr, index| h[attr.to_sym] = args[index]}}
51
37
 
52
38
  if finder =~ /all/
53
- return Array(results)
39
+ return chain.all
54
40
  else
55
- return results.first
41
+ return chain.first
56
42
  end
43
+ else
44
+ super
57
45
  end
58
46
  end
59
47
  end
@@ -111,6 +111,14 @@ describe Dynamoid::Adapter::AwsSdk do
111
111
  Dynamoid::Adapter.scan('dynamoid_tests_TestTable1', :name => 'Josh').should == [{:name=>"Josh", :id=>"2"}, {:name=>"Josh", :id=>"1"}]
112
112
  end
113
113
 
114
+ it 'performs scan on a table and returns all items if no criteria are specified' do
115
+ Dynamoid::Adapter.put_item('dynamoid_tests_TestTable1', {:id => '1', :name => 'Josh'})
116
+ Dynamoid::Adapter.put_item('dynamoid_tests_TestTable1', {:id => '2', :name => 'Josh'})
117
+
118
+ Dynamoid::Adapter.scan('dynamoid_tests_TestTable1', {}).should == [{:name=>"Josh", :id=>"2"}, {:name=>"Josh", :id=>"1"}]
119
+ end
120
+
121
+
114
122
  end
115
123
 
116
124
  # DescribeTable
@@ -135,6 +135,14 @@ describe Dynamoid::Adapter::Local do
135
135
 
136
136
  Dynamoid::Adapter.scan('Test Table', :name => 'Josh').should == [{ :id=> '1', :name=>"Josh" }, { :id=> '2', :name=>"Josh" }]
137
137
  end
138
+
139
+ it 'performs scan on a table and returns all items if no criteria are specified' do
140
+ Dynamoid::Adapter.create_table('Test Table', :id)
141
+ Dynamoid::Adapter.put_item('Test Table', {:id => '1', :name => 'Josh'})
142
+ Dynamoid::Adapter.put_item('Test Table', {:id => '2', :name => 'Josh'})
143
+
144
+ Dynamoid::Adapter.scan('Test Table', {}).should == [{ :id=> '1', :name=>"Josh" }, { :id=> '2', :name=>"Josh" }]
145
+ end
138
146
 
139
147
  # UpdateItem
140
148
 
@@ -0,0 +1,66 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe "Dynamoid::Associations::Chain" do
4
+
5
+ before(:each) do
6
+ @user = User.create(:name => 'Josh', :email => 'josh@joshsymonds.com', :password => 'Test123')
7
+ @chain = Dynamoid::Criteria::Chain.new(User)
8
+ end
9
+
10
+ it 'finds matching index for a query' do
11
+ @chain.query = {:name => 'Josh'}
12
+ @chain.send(:index).should == [:name]
13
+
14
+ @chain.query = {:email => 'josh@joshsymonds.com'}
15
+ @chain.send(:index).should == [:email]
16
+
17
+ @chain.query = {:name => 'Josh', :email => 'josh@joshsymonds.com'}
18
+ @chain.send(:index).should == [:email, :name]
19
+ end
20
+
21
+ it 'does not find an index if there is not an appropriate one' do
22
+ @chain.query = {:password => 'Test123'}
23
+ @chain.send(:index).should == []
24
+ end
25
+
26
+ it 'returns values for index for a query' do
27
+ @chain.query = {:name => 'Josh'}
28
+ @chain.send(:values_for_index).should == ['Josh']
29
+
30
+ @chain.query = {:email => 'josh@joshsymonds.com'}
31
+ @chain.send(:values_for_index).should == ['josh@joshsymonds.com']
32
+
33
+ @chain.query = {:name => 'Josh', :email => 'josh@joshsymonds.com'}
34
+ @chain.send(:values_for_index).should == ['josh@joshsymonds.com', 'Josh']
35
+ end
36
+
37
+ it 'finds records with an index' do
38
+ @chain.query = {:name => 'Josh'}
39
+ @chain.send(:records_with_index).should == [@user]
40
+
41
+ @chain.query = {:email => 'josh@joshsymonds.com'}
42
+ @chain.send(:records_with_index).should == [@user]
43
+
44
+ @chain.query = {:name => 'Josh', :email => 'josh@joshsymonds.com'}
45
+ @chain.send(:records_with_index).should == [@user]
46
+ end
47
+
48
+ it 'finds records without an index' do
49
+ @chain.query = {:password => 'Test123'}
50
+ @chain.send(:records_without_index).should == [@user]
51
+ end
52
+
53
+ it 'defines each' do
54
+ @chain.query = {:name => 'Josh'}
55
+ @chain.each {|u| u.update_attribute(:name, 'Justin')}
56
+
57
+ User.find(@user.id).name.should == 'Justin'
58
+ end
59
+
60
+ it 'includes Enumerable' do
61
+ @chain.query = {:name => 'Josh'}
62
+
63
+ @chain.collect {|u| u.name}.should == ['Josh']
64
+ end
65
+
66
+ end
@@ -0,0 +1,18 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe "Dynamoid::Criteria" do
4
+
5
+ before do
6
+ @user1 = User.create(:name => 'Josh', :email => 'josh@joshsymonds.com')
7
+ @user2 = User.create(:name => 'Justin', :email => 'justin@joshsymonds.com')
8
+ end
9
+
10
+ it 'finds first using where' do
11
+ User.where(:name => 'Josh').first.should == @user1
12
+ end
13
+
14
+ it 'finds all using where' do
15
+ User.where(:name => 'Josh').all.should == [@user1]
16
+ end
17
+
18
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dynamoid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-02-26 00:00:00.000000000 Z
12
+ date: 2012-02-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activemodel
16
- requirement: &70135836907680 !ruby/object:Gem::Requirement
16
+ requirement: &70219190581820 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70135836907680
24
+ version_requirements: *70219190581820
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: tzinfo
27
- requirement: &70135836906980 !ruby/object:Gem::Requirement
27
+ requirement: &70219190581300 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70135836906980
35
+ version_requirements: *70219190581300
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: aws-sdk
38
- requirement: &70135836906300 !ruby/object:Gem::Requirement
38
+ requirement: &70219190580760 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *70135836906300
46
+ version_requirements: *70219190580760
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: mocha
49
- requirement: &70135836905060 !ruby/object:Gem::Requirement
49
+ requirement: &70219190580120 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *70135836905060
57
+ version_requirements: *70219190580120
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: rake
60
- requirement: &70135836904480 !ruby/object:Gem::Requirement
60
+ requirement: &70219190579300 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: '0'
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *70135836904480
68
+ version_requirements: *70219190579300
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rspec
71
- requirement: &70135836903920 !ruby/object:Gem::Requirement
71
+ requirement: &70219190640280 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ! '>='
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: '0'
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *70135836903920
79
+ version_requirements: *70219190640280
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: bundler
82
- requirement: &70135836072500 !ruby/object:Gem::Requirement
82
+ requirement: &70219190639520 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - ! '>='
@@ -87,10 +87,10 @@ dependencies:
87
87
  version: '0'
88
88
  type: :development
89
89
  prerelease: false
90
- version_requirements: *70135836072500
90
+ version_requirements: *70219190639520
91
91
  - !ruby/object:Gem::Dependency
92
92
  name: jeweler
93
- requirement: &70135836071700 !ruby/object:Gem::Requirement
93
+ requirement: &70219190638920 !ruby/object:Gem::Requirement
94
94
  none: false
95
95
  requirements:
96
96
  - - ! '>='
@@ -98,10 +98,10 @@ dependencies:
98
98
  version: '0'
99
99
  type: :development
100
100
  prerelease: false
101
- version_requirements: *70135836071700
101
+ version_requirements: *70219190638920
102
102
  - !ruby/object:Gem::Dependency
103
103
  name: rcov
104
- requirement: &70135836071100 !ruby/object:Gem::Requirement
104
+ requirement: &70219190637920 !ruby/object:Gem::Requirement
105
105
  none: false
106
106
  requirements:
107
107
  - - ! '>='
@@ -109,7 +109,7 @@ dependencies:
109
109
  version: '0'
110
110
  type: :development
111
111
  prerelease: false
112
- version_requirements: *70135836071100
112
+ version_requirements: *70219190637920
113
113
  description: Dynamoid is an ORM for Amazon's DynamoDB that supports offline development,
114
114
  associations, querying, and everything else you'd expect from an ActiveRecord-style
115
115
  replacement.
@@ -143,6 +143,8 @@ files:
143
143
  - lib/dynamoid/components.rb
144
144
  - lib/dynamoid/config.rb
145
145
  - lib/dynamoid/config/options.rb
146
+ - lib/dynamoid/criteria.rb
147
+ - lib/dynamoid/criteria/chain.rb
146
148
  - lib/dynamoid/document.rb
147
149
  - lib/dynamoid/errors.rb
148
150
  - lib/dynamoid/fields.rb
@@ -166,6 +168,8 @@ files:
166
168
  - spec/dynamoid/associations_spec.rb
167
169
  - spec/dynamoid/attributes_spec.rb
168
170
  - spec/dynamoid/config_spec.rb
171
+ - spec/dynamoid/criteria/chain_spec.rb
172
+ - spec/dynamoid/criteria_spec.rb
169
173
  - spec/dynamoid/document_spec.rb
170
174
  - spec/dynamoid/fields_spec.rb
171
175
  - spec/dynamoid/finders_spec.rb
@@ -189,7 +193,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
189
193
  version: '0'
190
194
  segments:
191
195
  - 0
192
- hash: -1286880132188396224
196
+ hash: -353023235406473694
193
197
  required_rubygems_version: !ruby/object:Gem::Requirement
194
198
  none: false
195
199
  requirements: