dynamoid 0.3.2 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Dynamoid.gemspec +3 -2
- data/README.markdown +39 -3
- data/VERSION +1 -1
- data/lib/dynamoid.rb +2 -0
- data/lib/dynamoid/adapter.rb +9 -8
- data/lib/dynamoid/adapter/aws_sdk.rb +15 -9
- data/lib/dynamoid/adapter/local.rb +39 -14
- data/lib/dynamoid/associations.rb +5 -6
- data/lib/dynamoid/associations/association.rb +23 -1
- data/lib/dynamoid/associations/belongs_to.rb +0 -1
- data/lib/dynamoid/associations/has_and_belongs_to_many.rb +0 -1
- data/lib/dynamoid/associations/has_many.rb +0 -1
- data/lib/dynamoid/associations/many_association.rb +10 -7
- data/lib/dynamoid/associations/single_association.rb +2 -1
- data/lib/dynamoid/components.rb +1 -0
- data/lib/dynamoid/config.rb +1 -0
- data/lib/dynamoid/criteria.rb +2 -2
- data/lib/dynamoid/criteria/chain.rb +118 -43
- data/lib/dynamoid/document.rb +58 -6
- data/lib/dynamoid/fields.rb +18 -3
- data/lib/dynamoid/finders.rb +14 -7
- data/lib/dynamoid/indexes.rb +3 -2
- data/lib/dynamoid/indexes/index.rb +2 -2
- data/lib/dynamoid/persistence.rb +29 -14
- data/spec/app/models/address.rb +4 -0
- data/spec/app/models/camel_case.rb +13 -0
- data/spec/app/models/tweet.rb +9 -0
- data/spec/dynamoid/adapter/aws_sdk_spec.rb +5 -5
- data/spec/dynamoid/adapter/local_spec.rb +79 -79
- data/spec/dynamoid/adapter_spec.rb +6 -6
- data/spec/dynamoid/associations/association_spec.rb +26 -12
- data/spec/dynamoid/criteria/chain_spec.rb +64 -21
- data/spec/dynamoid/criteria_spec.rb +28 -0
- data/spec/dynamoid/document_spec.rb +29 -0
- data/spec/dynamoid/fields_spec.rb +5 -0
- data/spec/dynamoid/finders_spec.rb +6 -1
- data/spec/dynamoid/indexes/index_spec.rb +1 -1
- data/spec/dynamoid/persistence_spec.rb +9 -17
- data/spec/spec_helper.rb +1 -0
- metadata +4 -3
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.
|
8
|
+
s.version = "0.4.0"
|
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-04-
|
12
|
+
s.date = "2012-04-26"
|
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 = [
|
@@ -106,6 +106,7 @@ Gem::Specification.new do |s|
|
|
106
106
|
"spec/app/models/magazine.rb",
|
107
107
|
"spec/app/models/sponsor.rb",
|
108
108
|
"spec/app/models/subscription.rb",
|
109
|
+
"spec/app/models/tweet.rb",
|
109
110
|
"spec/app/models/user.rb",
|
110
111
|
"spec/dynamoid/adapter/aws_sdk_spec.rb",
|
111
112
|
"spec/dynamoid/adapter/local_spec.rb",
|
data/README.markdown
CHANGED
@@ -28,6 +28,7 @@ Then you need to initialize it to get it going. Put code similar to this somewhe
|
|
28
28
|
config.partition_size = 200 # Determine the key space size that writes are randomly spread across.
|
29
29
|
config.read_capacity = 100 # Read capacity for your tables
|
30
30
|
config.write_capacity = 20 # Write capacity for your tables
|
31
|
+
config.endpoint = 'dynamodb.us-east-1.amazonaws.com' # Set the regional endpoint
|
31
32
|
end
|
32
33
|
|
33
34
|
```
|
@@ -45,6 +46,20 @@ class User
|
|
45
46
|
end
|
46
47
|
```
|
47
48
|
|
49
|
+
### Table
|
50
|
+
|
51
|
+
Dynamoid has some sensible defaults for you when you create a new table, including the table name and the primary key column. But you can change those if you like on table creation.
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
class User
|
55
|
+
include Dynamoid::Document
|
56
|
+
|
57
|
+
table :name => :awesome_users, :key => :user_id, :read_capacity => 400, :write_capacity => 400
|
58
|
+
end
|
59
|
+
```
|
60
|
+
|
61
|
+
These fields will not change an existing table: so specifying a new read_capacity and write_capacity here only works correctly for entirely new tables. Similarly, while Dynamoid will look for a table named `awesome_users` in your namespace, it won't change any existing tables to use that name; and if it does find a table with the correct name, it won't change its hash key, which it expects will be user_id. If this table doesn't exist yet, however, Dynamoid will create it with these options.
|
62
|
+
|
48
63
|
### Fields
|
49
64
|
|
50
65
|
You'll have to define all the fields on the model and the data type of each field. Every field on the object must be included here; if you miss any they'll be completely bypassed during DynamoDB's initialization and will not appear on the model objects.
|
@@ -79,7 +94,7 @@ class User
|
|
79
94
|
index :email
|
80
95
|
index [:name, :email]
|
81
96
|
index :created_at, :range => true
|
82
|
-
index :name, :
|
97
|
+
index :name, :range_key => :joined_at
|
83
98
|
|
84
99
|
end
|
85
100
|
```
|
@@ -152,7 +167,9 @@ end
|
|
152
167
|
|
153
168
|
## Usage
|
154
169
|
|
155
|
-
|
170
|
+
### Object Creation
|
171
|
+
|
172
|
+
Dynamoid's syntax is generally very similar to ActiveRecord's. Making new objects is simple:
|
156
173
|
|
157
174
|
```ruby
|
158
175
|
u = User.new(:name => 'Josh')
|
@@ -174,6 +191,8 @@ address.city = 'Chicago'
|
|
174
191
|
address.save
|
175
192
|
```
|
176
193
|
|
194
|
+
### Querying
|
195
|
+
|
177
196
|
Querying can be done in one of three ways:
|
178
197
|
|
179
198
|
```ruby
|
@@ -190,6 +209,23 @@ u.addresses.where(:city => 'Chicago').all
|
|
190
209
|
|
191
210
|
But keep in mind Dynamoid -- and document-based storage systems in general -- are not drop-in replacements for existing relational databases. The above query does not efficiently perform a conditional join, but instead finds all the user's addresses and naively filters them in Ruby. For large associations this is a performance hit compared to relational database engines.
|
192
211
|
|
212
|
+
You can also limit returned results, or select a record from which to start, to support pagination:
|
213
|
+
|
214
|
+
```ruby
|
215
|
+
Address.limit(5).start(address) # Only 5 addresses.
|
216
|
+
```
|
217
|
+
|
218
|
+
### Consistent Reads
|
219
|
+
|
220
|
+
Querying supports consistent reading. By default, DynamoDB reads are eventually consistent: if you do a write and then a read immediately afterwards, the results of the previous write may not be reflected. If you need to do a consistent read (that is, you need to read the results of a write immediately) you can do so, but keep in mind that consistent reads are twice as expensive as regular reads for DynamoDB.
|
221
|
+
|
222
|
+
```ruby
|
223
|
+
Address.find(address.id, :consistent_read => true) # Find an address, ensure the read is consistent.
|
224
|
+
Address.where(:city => 'Chicago').consistent.all # Find all addresses where the city is Chicago, with a consistent read.
|
225
|
+
```
|
226
|
+
|
227
|
+
### Range Finding
|
228
|
+
|
193
229
|
If you have a range index, Dynamoid provides a number of additional other convenience methods to make your life a little easier:
|
194
230
|
|
195
231
|
```ruby
|
@@ -205,7 +241,7 @@ DynamoDB achieves much of its speed by relying on a random pattern of writes and
|
|
205
241
|
|
206
242
|
Dynamoid attempts to obviate this problem transparently by employing a partitioning strategy to divide up keys randomly across DynamoDB's servers. Each ID is assigned an additional number (by default 0 to 199, but you can increase the partition size in Dynamoid's configuration) upon save; when read, all 200 hashes are retrieved simultaneously and the most recently updated one is returned to the application. This results in a significant net performance increase, and is usually invisible to the application itself. It does, however, bring up the important issue of provisioning your DynamoDB tables correctly.
|
207
243
|
|
208
|
-
When your read or write
|
244
|
+
When your read or write throughput exceed your table's allowed provisioning, DynamoDB will wait on connections until throughput is available again. This will appear as very, very slow requests and can be somewhat frustrating. Partitioning significantly increases the amount of throughput tables will experience; though DynamoDB will ignore keys that don't exist, if you have 20 partitioned keys representing one object, all will be retrieved every time the object is requested. Ensure that your tables are set up for this kind of throughput, or turn provisioning off, to make sure that DynamoDB doesn't throttle your requests.
|
209
245
|
|
210
246
|
## Credits
|
211
247
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.4.0
|
data/lib/dynamoid.rb
CHANGED
data/lib/dynamoid/adapter.rb
CHANGED
@@ -67,7 +67,8 @@ module Dynamoid
|
|
67
67
|
# @param [Number] range_key the range key of the record
|
68
68
|
#
|
69
69
|
# @since 0.2.0
|
70
|
-
def read(table, ids,
|
70
|
+
def read(table, ids, options = {})
|
71
|
+
range_key = options[:range_key]
|
71
72
|
if ids.respond_to?(:each)
|
72
73
|
ids = ids.collect{|id| range_key ? [id, range_key] : id}
|
73
74
|
if Dynamoid::Config.partitioning?
|
@@ -82,7 +83,7 @@ module Dynamoid
|
|
82
83
|
results = benchmark('Partitioned Get Item', ids) {batch_get_item(table => id_with_partitions(ids))}
|
83
84
|
result_for_partition(results[table]).first
|
84
85
|
else
|
85
|
-
benchmark('Get Item', ids) {get_item(table, ids,
|
86
|
+
benchmark('Get Item', ids) {get_item(table, ids, options)}
|
86
87
|
end
|
87
88
|
end
|
88
89
|
end
|
@@ -94,13 +95,13 @@ module Dynamoid
|
|
94
95
|
# @param [Number] range_key the range key of the record
|
95
96
|
#
|
96
97
|
# @since 0.2.0
|
97
|
-
def delete(table, id,
|
98
|
+
def delete(table, id, options = {})
|
98
99
|
if Dynamoid::Config.partitioning?
|
99
100
|
benchmark('Delete Item', id) do
|
100
|
-
id_with_partitions(id).each {|i| delete_item(table, i,
|
101
|
+
id_with_partitions(id).each {|i| delete_item(table, i, options)}
|
101
102
|
end
|
102
103
|
else
|
103
|
-
benchmark('Delete Item', id) {delete_item(table, id,
|
104
|
+
benchmark('Delete Item', id) {delete_item(table, id, options)}
|
104
105
|
end
|
105
106
|
end
|
106
107
|
|
@@ -110,12 +111,12 @@ module Dynamoid
|
|
110
111
|
# @param [Hash] scan_hash a hash of attributes: matching records will be returned by the scan
|
111
112
|
#
|
112
113
|
# @since 0.2.0
|
113
|
-
def scan(table, query)
|
114
|
+
def scan(table, query, opts = {})
|
114
115
|
if Dynamoid::Config.partitioning?
|
115
|
-
results = benchmark('Scan', table, query) {adapter.scan(table, query)}
|
116
|
+
results = benchmark('Scan', table, query) {adapter.scan(table, query, opts)}
|
116
117
|
result_for_partition(results)
|
117
118
|
else
|
118
|
-
adapter.scan(table, query)
|
119
|
+
adapter.scan(table, query, opts)
|
119
120
|
end
|
120
121
|
end
|
121
122
|
|
@@ -18,7 +18,7 @@ module Dynamoid
|
|
18
18
|
#
|
19
19
|
# @since 0.2.0
|
20
20
|
def connect!
|
21
|
-
@@connection = AWS::DynamoDB.new(:access_key_id => Dynamoid::Config.access_key, :secret_access_key => Dynamoid::Config.secret_key)
|
21
|
+
@@connection = AWS::DynamoDB.new(:access_key_id => Dynamoid::Config.access_key, :secret_access_key => Dynamoid::Config.secret_key, :dynamo_db_endpoint => Dynamoid::Config.endpoint)
|
22
22
|
end
|
23
23
|
|
24
24
|
# Return the established connection.
|
@@ -64,7 +64,6 @@ module Dynamoid
|
|
64
64
|
# @since 0.2.0
|
65
65
|
def create_table(table_name, key = :id, options = {})
|
66
66
|
options[:hash_key] ||= {key.to_sym => :string}
|
67
|
-
options[:range_key] = {options[:range_key].to_sym => :number} if options[:range_key]
|
68
67
|
read_capacity = options[:read_capacity] || Dynamoid::Config.read_capacity
|
69
68
|
write_capacity = options[:write_capacity] || Dynamoid::Config.write_capacity
|
70
69
|
table = @@connection.tables.create(table_name, read_capacity, write_capacity, options)
|
@@ -79,7 +78,8 @@ module Dynamoid
|
|
79
78
|
# @param [Number] range_key the range key of the item to delete, required if the table has a composite key
|
80
79
|
#
|
81
80
|
# @since 0.2.0
|
82
|
-
def delete_item(table_name, key,
|
81
|
+
def delete_item(table_name, key, options = {})
|
82
|
+
range_key = options.delete(:range_key)
|
83
83
|
table = get_table(table_name)
|
84
84
|
result = if table.composite_key?
|
85
85
|
table.items.at(key, range_key)
|
@@ -111,13 +111,18 @@ module Dynamoid
|
|
111
111
|
# @return [Hash] a hash representing the raw item in DynamoDB
|
112
112
|
#
|
113
113
|
# @since 0.2.0
|
114
|
-
|
114
|
+
|
115
|
+
|
116
|
+
|
117
|
+
def get_item(table_name, key, options = {})
|
118
|
+
range_key = options.delete(:range_key)
|
115
119
|
table = get_table(table_name)
|
120
|
+
|
116
121
|
result = if table.composite_key?
|
117
122
|
table.items.at(key, range_key)
|
118
123
|
else
|
119
124
|
table.items[key]
|
120
|
-
end.attributes.to_h
|
125
|
+
end.attributes.to_h(options)
|
121
126
|
if result.empty?
|
122
127
|
nil
|
123
128
|
else
|
@@ -161,10 +166,11 @@ module Dynamoid
|
|
161
166
|
# @since 0.2.0
|
162
167
|
def query(table_name, opts = {})
|
163
168
|
table = get_table(table_name)
|
164
|
-
|
169
|
+
|
170
|
+
consistent_opts = { :consistent_read => opts[:consistent_read] || false }
|
165
171
|
if table.composite_key?
|
166
172
|
results = []
|
167
|
-
table.items.query(opts).each {|data| results << data.attributes.to_h.symbolize_keys!}
|
173
|
+
table.items.query(opts).each {|data| results << data.attributes.to_h(consistent_opts).symbolize_keys!}
|
168
174
|
results
|
169
175
|
else
|
170
176
|
get_item(table_name, opts[:hash_value])
|
@@ -180,10 +186,10 @@ module Dynamoid
|
|
180
186
|
# @return [Array] an array of all matching items
|
181
187
|
#
|
182
188
|
# @since 0.2.0
|
183
|
-
def scan(table_name, scan_hash)
|
189
|
+
def scan(table_name, scan_hash, select_opts)
|
184
190
|
table = get_table(table_name)
|
185
191
|
results = []
|
186
|
-
table.items.where(scan_hash).select do |data|
|
192
|
+
table.items.where(scan_hash).select(select_opts) do |data|
|
187
193
|
results << data.attributes.symbolize_keys!
|
188
194
|
end
|
189
195
|
results
|
@@ -38,7 +38,7 @@ module Dynamoid
|
|
38
38
|
table = data[table_name]
|
39
39
|
if table[:range_key]
|
40
40
|
Array(keys).each do |hash_key, range_key|
|
41
|
-
hash[table_name] << get_item(table_name, hash_key, range_key)
|
41
|
+
hash[table_name] << get_item(table_name, hash_key, :range_key => range_key)
|
42
42
|
end
|
43
43
|
else
|
44
44
|
Array(keys).each do |key|
|
@@ -57,7 +57,8 @@ module Dynamoid
|
|
57
57
|
#
|
58
58
|
# @since 0.2.0
|
59
59
|
def create_table(table_name, key, options = {})
|
60
|
-
|
60
|
+
range_key = options[:range_key] && options[:range_key].keys.first
|
61
|
+
data[table_name] = {:hash_key => key, :range_key => range_key, :data => {}}
|
61
62
|
end
|
62
63
|
|
63
64
|
# Removes an item from the hash.
|
@@ -67,7 +68,8 @@ module Dynamoid
|
|
67
68
|
# @param [Number] range_key the range key of the item to delete, required if the table has a composite key
|
68
69
|
#
|
69
70
|
# @since 0.2.0
|
70
|
-
def delete_item(table_name, key,
|
71
|
+
def delete_item(table_name, key, options = {})
|
72
|
+
range_key = options.delete(:range_key)
|
71
73
|
data[table_name][:data].delete("#{key}.#{range_key}")
|
72
74
|
end
|
73
75
|
|
@@ -91,7 +93,8 @@ module Dynamoid
|
|
91
93
|
# @return [Hash] a hash representing the raw item
|
92
94
|
#
|
93
95
|
# @since 0.2.0
|
94
|
-
def get_item(table_name, key,
|
96
|
+
def get_item(table_name, key, options = {})
|
97
|
+
range_key = options[:range_key]
|
95
98
|
if data[table_name][:data]
|
96
99
|
data[table_name][:data]["#{key}.#{range_key}"]
|
97
100
|
else
|
@@ -116,6 +119,8 @@ module Dynamoid
|
|
116
119
|
table = data[table_name]
|
117
120
|
table[:data][object[table[:hash_key]]]
|
118
121
|
table[:data]["#{object[table[:hash_key]]}.#{object[table[:range_key]]}"] = object.delete_if{|k, v| v.nil? || (v.respond_to?(:empty?) && v.empty?)}
|
122
|
+
rescue
|
123
|
+
raise data.inspect
|
119
124
|
end
|
120
125
|
|
121
126
|
# Query the hash.
|
@@ -134,20 +139,26 @@ module Dynamoid
|
|
134
139
|
# @since 0.2.0
|
135
140
|
def query(table_name, opts = {})
|
136
141
|
id = opts[:hash_value]
|
142
|
+
hash_key = data[table_name][:hash_key]
|
137
143
|
range_key = data[table_name][:range_key]
|
138
|
-
|
139
|
-
|
144
|
+
|
145
|
+
results = if opts[:range_value]
|
146
|
+
data[table_name][:data].values.find_all{|v| v[hash_key] == id && !v[range_key].nil? && opts[:range_value].include?(v[range_key])}
|
140
147
|
elsif opts[:range_greater_than]
|
141
|
-
data[table_name][:data].values.find_all{|v| v[
|
148
|
+
data[table_name][:data].values.find_all{|v| v[hash_key] == id && !v[range_key].nil? && v[range_key] > opts[:range_greater_than]}
|
142
149
|
elsif opts[:range_less_than]
|
143
|
-
data[table_name][:data].values.find_all{|v| v[
|
150
|
+
data[table_name][:data].values.find_all{|v| v[hash_key] == id && !v[range_key].nil? && v[range_key] < opts[:range_less_than]}
|
144
151
|
elsif opts[:range_gte]
|
145
|
-
data[table_name][:data].values.find_all{|v| v[
|
152
|
+
data[table_name][:data].values.find_all{|v| v[hash_key] == id && !v[range_key].nil? && v[range_key] >= opts[:range_gte]}
|
146
153
|
elsif opts[:range_lte]
|
147
|
-
data[table_name][:data].values.find_all{|v| v[
|
154
|
+
data[table_name][:data].values.find_all{|v| v[hash_key] == id && !v[range_key].nil? && v[range_key] <= opts[:range_lte]}
|
148
155
|
else
|
149
|
-
|
156
|
+
data[table_name][:data].values.find_all{|v| v[hash_key] == id}
|
150
157
|
end
|
158
|
+
|
159
|
+
results = drop_till_start(results, opts[:next_token], range_key, hash_key)
|
160
|
+
results = results.take(opts[:limit]) if opts[:limit]
|
161
|
+
results
|
151
162
|
end
|
152
163
|
|
153
164
|
# Scan the hash.
|
@@ -158,9 +169,23 @@ module Dynamoid
|
|
158
169
|
# @return [Array] an array of all matching items
|
159
170
|
#
|
160
171
|
# @since 0.2.0
|
161
|
-
def scan(table_name, scan_hash)
|
172
|
+
def scan(table_name, scan_hash, opts = {})
|
162
173
|
return [] if data[table_name].nil?
|
163
|
-
data[table_name][:data].values.flatten.select{|d| scan_hash.all?{|k, v| !d[k].nil? && d[k] == v}}
|
174
|
+
results = data[table_name][:data].values.flatten.select{|d| scan_hash.all?{|k, v| !d[k].nil? && d[k] == v}}
|
175
|
+
results = drop_till_start(results, opts[:next_token], data[table_name][:range_key], data[table_name][:hash_key])
|
176
|
+
results = results.take(opts[:limit]) if opts[:limit]
|
177
|
+
results
|
178
|
+
end
|
179
|
+
|
180
|
+
def drop_till_start(results, next_token, range_key, hash_key)
|
181
|
+
return results unless next_token
|
182
|
+
|
183
|
+
hash_value = next_token[:hash_key_element].values.first
|
184
|
+
range_value = next_token[:range_key_element].values.first if next_token[:range_key_element]
|
185
|
+
|
186
|
+
results = results.drop_while do |r|
|
187
|
+
(r[hash_key] != hash_value or r[range_key] != range_value)
|
188
|
+
end.drop(1)
|
164
189
|
end
|
165
190
|
|
166
191
|
# @todo Add an UpdateItem method.
|
@@ -168,4 +193,4 @@ module Dynamoid
|
|
168
193
|
# @todo Add an UpdateTable method.
|
169
194
|
end
|
170
195
|
end
|
171
|
-
end
|
196
|
+
end
|
@@ -91,17 +91,16 @@ module Dynamoid
|
|
91
91
|
field "#{name}_ids".to_sym, :set
|
92
92
|
self.associations[name] = options.merge(:type => type)
|
93
93
|
define_method(name) do
|
94
|
-
@associations ||=
|
95
|
-
@associations[name] ||= Dynamoid::Associations.const_get(type.to_s.camelcase).new(self, name, options)
|
94
|
+
@associations[:"#{name}_ids"] ||= Dynamoid::Associations.const_get(type.to_s.camelcase).new(self, name, options)
|
96
95
|
end
|
97
96
|
define_method("#{name}=".to_sym) do |objects|
|
98
|
-
@associations ||=
|
99
|
-
@associations[name]
|
100
|
-
@associations[name].setter(objects)
|
97
|
+
@associations[:"#{name}_ids"] ||= Dynamoid::Associations.const_get(type.to_s.camelcase).new(self, name, options)
|
98
|
+
@associations[:"#{name}_ids"].setter(objects)
|
101
99
|
end
|
102
100
|
end
|
103
101
|
end
|
104
|
-
|
102
|
+
|
103
|
+
|
105
104
|
end
|
106
105
|
|
107
106
|
end
|
@@ -6,7 +6,7 @@ module Dynamoid #:nodoc:
|
|
6
6
|
# The target is the object which is referencing by this association.
|
7
7
|
module Associations
|
8
8
|
module Association
|
9
|
-
attr_accessor :name, :options, :source
|
9
|
+
attr_accessor :name, :options, :source, :loaded
|
10
10
|
|
11
11
|
# Create a new association.
|
12
12
|
#
|
@@ -24,6 +24,28 @@ module Dynamoid #:nodoc:
|
|
24
24
|
@name = name
|
25
25
|
@options = options
|
26
26
|
@source = source
|
27
|
+
@loaded = false
|
28
|
+
end
|
29
|
+
|
30
|
+
def loaded?
|
31
|
+
@loaded
|
32
|
+
end
|
33
|
+
|
34
|
+
def find_target
|
35
|
+
end
|
36
|
+
|
37
|
+
def target
|
38
|
+
unless loaded?
|
39
|
+
@target = find_target
|
40
|
+
@loaded = true
|
41
|
+
end
|
42
|
+
|
43
|
+
@target
|
44
|
+
end
|
45
|
+
|
46
|
+
def reset
|
47
|
+
@target = nil
|
48
|
+
@loaded = false
|
27
49
|
end
|
28
50
|
|
29
51
|
private
|
@@ -3,6 +3,7 @@ module Dynamoid #:nodoc:
|
|
3
3
|
|
4
4
|
module Associations
|
5
5
|
module ManyAssociation
|
6
|
+
include Association
|
6
7
|
|
7
8
|
attr_accessor :query
|
8
9
|
|
@@ -20,13 +21,15 @@ module Dynamoid #:nodoc:
|
|
20
21
|
# @return the association records; depending on which association this is, either a single instance or an array
|
21
22
|
#
|
22
23
|
# @since 0.2.0
|
23
|
-
def
|
24
|
-
|
24
|
+
def find_target
|
25
|
+
Array(target_class.find(source_ids.to_a))
|
26
|
+
end
|
25
27
|
|
28
|
+
def records
|
26
29
|
if query.empty?
|
27
|
-
|
30
|
+
target
|
28
31
|
else
|
29
|
-
results_with_query(
|
32
|
+
results_with_query(target)
|
30
33
|
end
|
31
34
|
end
|
32
35
|
|
@@ -78,7 +81,7 @@ module Dynamoid #:nodoc:
|
|
78
81
|
#
|
79
82
|
# @since 0.2.0
|
80
83
|
def setter(object)
|
81
|
-
|
84
|
+
target.each {|o| delete(o)}
|
82
85
|
self << (object)
|
83
86
|
object
|
84
87
|
end
|
@@ -120,7 +123,7 @@ module Dynamoid #:nodoc:
|
|
120
123
|
#
|
121
124
|
# @since 0.2.0
|
122
125
|
def destroy_all
|
123
|
-
objs =
|
126
|
+
objs = target
|
124
127
|
source.update_attribute(source_attribute, nil)
|
125
128
|
objs.each(&:destroy)
|
126
129
|
end
|
@@ -129,7 +132,7 @@ module Dynamoid #:nodoc:
|
|
129
132
|
#
|
130
133
|
# @since 0.2.0
|
131
134
|
def delete_all
|
132
|
-
objs =
|
135
|
+
objs = target
|
133
136
|
source.update_attribute(source_attribute, nil)
|
134
137
|
objs.each(&:delete)
|
135
138
|
end
|