dynamoid 0.1.1 → 0.1.2

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.
@@ -12,14 +12,14 @@ module Dynamoid #:nodoc:
12
12
  if id.count == 1
13
13
  self.find_by_id(id.first)
14
14
  else
15
- items = Dynamoid::Adapter.batch_get_item(self.table_name => id)
15
+ items = Dynamoid::Adapter.read(self.table_name, id)
16
16
  items[self.table_name].collect{|i| o = self.build(i); o.new_record = false; o}
17
17
  end
18
18
  end
19
19
 
20
20
  def find_by_id(id)
21
- if item = Dynamoid::Adapter.get_item(self.table_name, id)
22
- obj = self.new(Dynamoid::Adapter.get_item(self.table_name, id))
21
+ if item = Dynamoid::Adapter.read(self.table_name, id)
22
+ obj = self.new(item)
23
23
  obj.new_record = false
24
24
  return obj
25
25
  else
@@ -1,5 +1,3 @@
1
- require 'digest/sha2'
2
-
3
1
  # encoding: utf-8
4
2
  module Dynamoid #:nodoc:
5
3
 
@@ -16,30 +14,23 @@ module Dynamoid #:nodoc:
16
14
  module ClassMethods
17
15
  def index(name, options = {})
18
16
  name = Array(name).collect(&:to_s).sort.collect(&:to_sym)
19
- raise Dynamoid::Errors::InvalidField, 'A key specified for an index is not a field' unless name.all?{|n| self.fields.include?(n)}
17
+ raise Dynamoid::Errors::InvalidField, 'A key specified for an index is not a field' unless name.all?{|n| self.attributes.include?(n)}
20
18
  self.indexes << name
21
19
  create_indexes
22
20
  end
23
21
 
24
22
  def create_indexes
25
23
  self.indexes.each do |index|
26
- self.create_table(index_table_name(index), index_key_name(index)) unless self.table_exists?(index_table_name(index))
24
+ self.create_table(index_table_name(index), :id) unless self.table_exists?(index_table_name(index))
27
25
  end
28
26
  end
29
27
 
30
28
  def index_table_name(index)
31
- "#{Dynamoid::Config.namespace}_index_#{index_key_name(index)}"
32
- end
33
-
34
- def index_key_name(index)
35
- "#{self.to_s.downcase}_#{index.collect(&:to_s).collect(&:pluralize).join('_and_')}"
29
+ "#{Dynamoid::Config.namespace}_index_#{self.to_s.downcase}_#{index.collect(&:to_s).collect(&:pluralize).join('_and_')}"
36
30
  end
37
31
 
38
32
  def key_for_index(index, values = [])
39
- values = values.collect(&:to_s).sort
40
- Digest::SHA2.new.tap do |sha|
41
- index.each_with_index {|i, index| sha << values[index] if values[index]}
42
- end.to_s
33
+ values = values.collect(&:to_s).sort.join('.')
43
34
  end
44
35
  end
45
36
 
@@ -49,17 +40,19 @@ module Dynamoid #:nodoc:
49
40
 
50
41
  def save_indexes
51
42
  self.class.indexes.each do |index|
52
- existing = Dynamoid::Adapter.get_item(self.class.index_table_name(index), self.key_for_index(index))
43
+ next if self.key_for_index(index).blank?
44
+ existing = Dynamoid::Adapter.read(self.class.index_table_name(index), self.key_for_index(index))
53
45
  ids = existing ? existing[:ids] : Set.new
54
- Dynamoid::Adapter.put_item(self.class.index_table_name(index), {self.class.index_key_name(index).to_sym => self.key_for_index(index), :ids => ids.merge([self.id])})
46
+ Dynamoid::Adapter.write(self.class.index_table_name(index), {:id => self.key_for_index(index), :ids => ids.merge([self.id])})
55
47
  end
56
48
  end
57
49
 
58
50
  def delete_indexes
59
51
  self.class.indexes.each do |index|
60
- existing = Dynamoid::Adapter.get_item(self.class.index_table_name(index), self.key_for_index(index))
52
+ next if self.key_for_index(index).blank?
53
+ existing = Dynamoid::Adapter.read(self.class.index_table_name(index), self.key_for_index(index))
61
54
  next unless existing && existing[:ids]
62
- Dynamoid::Adapter.put_item(self.class.index_table_name(index), {self.class.index_key_name(index).to_sym => self.key_for_index(index), :ids => existing[:ids] - [self.id]})
55
+ Dynamoid::Adapter.write(self.class.index_table_name(index), {:id => self.key_for_index(index), :ids => existing[:ids] - [self.id]})
63
56
  end
64
57
  end
65
58
  end
@@ -7,42 +7,118 @@ module Dynamoid #:nodoc:
7
7
  module Persistence
8
8
  extend ActiveSupport::Concern
9
9
 
10
+ attr_accessor :new_record
11
+ alias :new_record? :new_record
12
+
13
+ module ClassMethods
14
+ def table_name
15
+ "#{Dynamoid::Config.namespace}_#{self.to_s.downcase.pluralize}"
16
+ end
17
+
18
+ def create_table(table_name, id = :id)
19
+ Dynamoid::Adapter.tables << table_name if Dynamoid::Adapter.create_table(table_name, id.to_sym)
20
+ end
21
+
22
+ def table_exists?(table_name)
23
+ Dynamoid::Adapter.tables.include?(table_name)
24
+ end
25
+
26
+ def undump(incoming = {})
27
+ incoming.symbolize_keys!
28
+ Hash.new.tap do |hash|
29
+ self.attributes.each do |attribute, options|
30
+ value = incoming[attribute]
31
+ next if value.nil? || (value.respond_to?(:empty?) && value.empty?)
32
+ case options[:type]
33
+ when :string
34
+ hash[attribute] = value.to_s
35
+ when :integer
36
+ hash[attribute] = value.to_i
37
+ when :float
38
+ hash[attribute] = value.to_f
39
+ when :set, :array
40
+ if value.is_a?(Set) || value.is_a?(Array)
41
+ hash[attribute] = value
42
+ else
43
+ hash[attribute] = Set[value]
44
+ end
45
+ when :datetime
46
+ hash[attribute] = Time.at(value).to_datetime
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ end
53
+
10
54
  included do
11
55
  self.create_table(self.table_name) unless self.table_exists?(self.table_name)
12
56
  end
13
57
 
58
+ def persisted?
59
+ !new_record?
60
+ end
61
+
14
62
  def save
15
- run_callbacks(:save) do
16
- self.id = SecureRandom.uuid if self.id.nil? || self.id.blank?
17
- Dynamoid::Adapter.put_item(self.class.table_name, self.attributes)
18
- save_indexes
63
+ if self.new_record?
64
+ run_callbacks(:create) do
65
+ run_callbacks(:save) do
66
+ persist
67
+ end
68
+ end
69
+ else
70
+ run_callbacks(:save) do
71
+ persist
72
+ end
19
73
  end
74
+ self
20
75
  end
21
76
 
22
77
  def destroy
23
78
  run_callbacks(:destroy) do
24
79
  self.delete
25
80
  end
81
+ self
26
82
  end
27
83
 
28
84
  def delete
29
85
  delete_indexes
30
- Dynamoid::Adapter.delete_item(self.class.table_name, self.id)
86
+ Dynamoid::Adapter.delete(self.class.table_name, self.id)
31
87
  end
32
88
 
33
- module ClassMethods
34
- def table_name
35
- "#{Dynamoid::Config.namespace}_#{self.to_s.downcase.pluralize}"
36
- end
37
-
38
- def create_table(table_name, id = :id)
39
- Dynamoid::Adapter.create_table(table_name, id.to_sym)
40
- end
41
-
42
- def table_exists?(table_name)
43
- Dynamoid::Adapter.list_tables.include?(table_name)
89
+ def dump
90
+ Hash.new.tap do |hash|
91
+ self.class.attributes.each do |attribute, options|
92
+ value = self.read_attribute(attribute)
93
+ next if value.nil? || (value.respond_to?(:empty?) && value.empty?)
94
+ case options[:type]
95
+ when :string
96
+ hash[attribute] = value.to_s
97
+ when :integer
98
+ hash[attribute] = value.to_i
99
+ when :float
100
+ hash[attribute] = value.to_f
101
+ when :set, :array
102
+ if value.is_a?(Set) || value.is_a?(Array)
103
+ hash[attribute] = value
104
+ else
105
+ hash[attribute] = Set[value]
106
+ end
107
+ when :datetime
108
+ hash[attribute] = value.to_time.to_f
109
+ end
110
+ end
44
111
  end
45
112
  end
113
+
114
+ private
115
+
116
+ def persist
117
+ self.id = SecureRandom.uuid if self.id.nil? || self.id.blank?
118
+ Dynamoid::Adapter.write(self.class.table_name, self.dump)
119
+ save_indexes
120
+ end
121
+
46
122
  end
47
123
 
48
124
  end
@@ -1,6 +1,8 @@
1
1
  class Magazine
2
2
  include Dynamoid::Document
3
3
 
4
+ field :title
5
+
4
6
  has_many :subscriptions
5
7
  has_one :sponsor
6
8
  end
@@ -1,6 +1,8 @@
1
1
  class Subscription
2
2
  include Dynamoid::Document
3
3
 
4
+ field :length, :integer
5
+
4
6
  belongs_to :magazine
5
7
  has_and_belongs_to_many :users
6
8
  end
@@ -2,6 +2,10 @@ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
2
 
3
3
  describe "Dynamoid::Adapter" do
4
4
 
5
+ before(:all) do
6
+ Dynamoid::Adapter.create_table('dynamoid_tests_TestTable', :id) unless Dynamoid::Adapter.list_tables.include?('dynamoid_tests_TestTable')
7
+ end
8
+
5
9
  it 'extends itself automatically' do
6
10
  lambda {Dynamoid::Adapter.list_tables}.should_not raise_error
7
11
  end
@@ -9,5 +13,77 @@ describe "Dynamoid::Adapter" do
9
13
  it 'raises nomethod if we try a method that is not on the child' do
10
14
  lambda {Dynamoid::Adapter.foobar}.should raise_error
11
15
  end
16
+
17
+ context 'without partioning' do
18
+ before(:all) do
19
+ @previous_value = Dynamoid::Config.partitioning
20
+ Dynamoid::Config.partitioning = false
21
+ end
22
+
23
+ after(:all) do
24
+ Dynamoid::Config.partitioning = @previous_value
25
+ end
26
+
27
+ it 'writes through the adapter' do
28
+ Dynamoid::Adapter.expects(:put_item).with('dynamoid_tests_TestTable', {:id => '123'}).returns(true)
29
+
30
+ Dynamoid::Adapter.write('dynamoid_tests_TestTable', {:id => '123'})
31
+ end
32
+
33
+ it 'reads through the adapter for one ID' do
34
+ Dynamoid::Adapter.expects(:get_item).with('dynamoid_tests_TestTable', '123').returns(true)
35
+
36
+ Dynamoid::Adapter.read('dynamoid_tests_TestTable', '123')
37
+ end
38
+
39
+ it 'reads through the adapter for many IDs' do
40
+ Dynamoid::Adapter.expects(:batch_get_item).with({'dynamoid_tests_TestTable' => ['1', '2']}).returns(true)
41
+
42
+ Dynamoid::Adapter.read('dynamoid_tests_TestTable', ['1', '2'])
43
+ end
44
+ end
45
+
46
+ context 'with partitioning' do
47
+ before(:all) do
48
+ @previous_value = Dynamoid::Config.partitioning
49
+ Dynamoid::Config.partitioning = true
50
+ end
51
+
52
+ after(:all) do
53
+ Dynamoid::Config.partitioning = @previous_value
54
+ end
55
+
56
+ it 'writes through the adapter' do
57
+ Random.expects(:rand).with(Dynamoid::Config.partition_size).once.returns(0)
58
+ Dynamoid::Adapter.write('dynamoid_tests_TestTable', {:id => 'testid'})
59
+
60
+ Dynamoid::Adapter.get_item('dynamoid_tests_TestTable', 'testid.0')[:id].should == 'testid.0'
61
+ Dynamoid::Adapter.get_item('dynamoid_tests_TestTable', 'testid.0')[:updated_at].should_not be_nil
62
+ end
63
+
64
+ it 'reads through the adapter for one ID' do
65
+ Dynamoid::Adapter.expects(:batch_get_item).with('dynamoid_tests_TestTable' => (0...Dynamoid::Config.partition_size).collect{|n| "123.#{n}"}).returns({})
66
+
67
+ Dynamoid::Adapter.read('dynamoid_tests_TestTable', '123')
68
+ end
69
+
70
+ it 'reads through the adapter for many IDs' do
71
+ Dynamoid::Adapter.expects(:batch_get_item).with('dynamoid_tests_TestTable' => (0...Dynamoid::Config.partition_size).collect{|n| "1.#{n}"} + (0...Dynamoid::Config.partition_size).collect{|n| "2.#{n}"}).returns({})
72
+
73
+ Dynamoid::Adapter.read('dynamoid_tests_TestTable', ['1', '2'])
74
+ end
75
+
76
+ it 'returns an ID with all partitions' do
77
+ Dynamoid::Adapter.id_with_partitions('1').should =~ (0...Dynamoid::Config.partition_size).collect{|n| "1.#{n}"}
78
+ end
79
+
80
+ it 'returns a result for one partitioned element' do
81
+ @time = DateTime.now
82
+ @array =[{:id => '1.0', :updated_at => @time - 6.hours}, {:id => '1.1', :updated_at => @time - 3.hours}, {:id => '1.2', :updated_at => @time - 1.hour}, {:id => '1.3', :updated_at => @time - 6.hours}, {:id => '2.0', :updated_at => @time}]
83
+
84
+ Dynamoid::Adapter.result_for_partition(@array).should =~ [{:id => '1', :updated_at => @time - 1.hour}, {:id => '2', :updated_at => @time}]
85
+ end
86
+
87
+ end
12
88
 
13
89
  end
@@ -78,5 +78,27 @@ describe "Dynamoid::Associations::Association" do
78
78
  @magazine.subscriptions = nil
79
79
  @magazine.subscriptions.size.should == 0
80
80
  end
81
+
82
+ it 'uses where inside an association and returns a result' do
83
+ @included_subscription = @magazine.subscriptions.create(:length => 10)
84
+ @unincldued_subscription = @magazine.subscriptions.create(:length => 8)
85
+
86
+ @magazine.subscriptions.where(:length => 10).all.should == [@included_subscription]
87
+ end
88
+
89
+ it 'uses where inside an association and returns an empty set' do
90
+ @included_subscription = @magazine.subscriptions.create(:length => 10)
91
+ @unincldued_subscription = @magazine.subscriptions.create(:length => 8)
92
+
93
+ @magazine.subscriptions.where(:length => 6).all.should be_empty
94
+ end
95
+
96
+ it 'includes enumerable' do
97
+ @subscription1 = @magazine.subscriptions.create
98
+ @subscription2 = @magazine.subscriptions.create
99
+ @subscription3 = @magazine.subscriptions.create
100
+
101
+ @magazine.subscriptions.collect(&:id).should =~ [@subscription1.id, @subscription2.id, @subscription3.id]
102
+ end
81
103
 
82
104
  end
@@ -23,6 +23,14 @@ describe "Dynamoid::Associations::BelongsTo" do
23
23
  @magazine.subscriptions.size.should == 1
24
24
  @magazine.subscriptions.should include @subscription
25
25
  end
26
+
27
+ it 'behaves like the object it is trying to be' do
28
+ @magazine = @subscription.magazine.create
29
+
30
+ @subscription.magazine.update_attribute(:title, 'Test Title')
31
+
32
+ Magazine.first.title.should == 'Test Title'
33
+ end
26
34
  end
27
35
 
28
36
  context 'has one' do
@@ -27,5 +27,8 @@ describe "Dynamoid::Criteria" do
27
27
  Magazine.all.should == []
28
28
  end
29
29
 
30
+ it 'passes each to all members' do
31
+ User.each{|u| u.id.should == @user1.id || @user2.id}
32
+ end
30
33
 
31
34
  end
@@ -6,7 +6,7 @@ describe "Dynamoid::Document" do
6
6
  @address = Address.new
7
7
 
8
8
  @address.new_record.should be_true
9
- @address.attributes.should == {:id => nil, :city => nil}
9
+ @address.attributes.should == {:id=>nil, :created_at=>nil, :updated_at=>nil, :city=>nil}
10
10
  end
11
11
 
12
12
  it 'initializes a new document with attributes' do
@@ -14,7 +14,7 @@ describe "Dynamoid::Document" do
14
14
 
15
15
  @address.new_record.should be_true
16
16
 
17
- @address.attributes.should == {:id => nil, :city => 'Chicago'}
17
+ @address.attributes.should == {:id=>nil, :created_at=>nil, :updated_at=>nil, :city=>"Chicago"}
18
18
  end
19
19
 
20
20
  it 'creates a new document' do
@@ -45,4 +45,13 @@ describe "Dynamoid::Document" do
45
45
  @address.errors.should be_empty
46
46
  @address.errors.full_messages.should be_empty
47
47
  end
48
+
49
+ it 'reloads itself and sees persisted changes' do
50
+ @address = Address.create
51
+
52
+ Address.first.update_attributes(:city => 'Chicago')
53
+
54
+ @address.city.should be_nil
55
+ @address.reload.city.should == 'Chicago'
56
+ end
48
57
  end
@@ -23,4 +23,61 @@ describe "Dynamoid::Fields" do
23
23
  @address.city?.should be_true
24
24
  end
25
25
 
26
+ it 'automatically declares id' do
27
+ lambda {@address.id}.should_not raise_error
28
+ end
29
+
30
+ it 'automatically declares and fills in created_at and updated_at' do
31
+ @address.save
32
+
33
+ @address = @address.reload
34
+ @address.created_at.should_not be_nil
35
+ @address.created_at.class.should == DateTime
36
+ @address.updated_at.should_not be_nil
37
+ @address.updated_at.class.should == DateTime
38
+ end
39
+
40
+ context 'with a saved address' do
41
+ before do
42
+ @address = Address.create
43
+ @original_id = @address.id
44
+ end
45
+
46
+ it 'should write an attribute correctly' do
47
+ @address.write_attribute(:city, 'Chicago')
48
+ end
49
+
50
+ it 'should write an attribute with an alias' do
51
+ @address[:city] = 'Chicago'
52
+ end
53
+
54
+ it 'should read a written attribute' do
55
+ @address.write_attribute(:city, 'Chicago')
56
+ @address.read_attribute(:city).should == 'Chicago'
57
+ end
58
+
59
+ it 'should read a written attribute with the alias' do
60
+ @address.write_attribute(:city, 'Chicago')
61
+ @address[:city].should == 'Chicago'
62
+ end
63
+
64
+ it 'should update all attributes' do
65
+ @address.expects(:save).once.returns(true)
66
+ @address.update_attributes(:city => 'Chicago')
67
+ @address[:city].should == 'Chicago'
68
+ @address.id.should == @original_id
69
+ end
70
+
71
+ it 'should update one attribute' do
72
+ @address.expects(:save).once.returns(true)
73
+ @address.update_attribute(:city, 'Chicago')
74
+ @address[:city].should == 'Chicago'
75
+ @address.id.should == @original_id
76
+ end
77
+
78
+ it 'returns all attributes' do
79
+ Address.attributes.should == {:id=>{:type=>:string}, :created_at=>{:type=>:datetime}, :updated_at=>{:type=>:datetime}, :city=>{:type=>:string}}
80
+ end
81
+ end
82
+
26
83
  end