ocean-dynamo 0.3.4 → 0.3.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8e2c646cd685be098d298f74ceab69c47f2902f9
4
- data.tar.gz: 3739a76a1112bd433280cce959a2072c48ea33b6
3
+ metadata.gz: 07c46f74bde0bbf2b2b84bce8d19dd661c42b45e
4
+ data.tar.gz: 30cf8ca2fae16fde091cf74bac9fb4cfa3419e1a
5
5
  SHA512:
6
- metadata.gz: c60acec3262ac60b21834edbf0f83c2d6d4107e411e763eb870f2e1a40a711c950b98983f3913ada9a033f42aa6a7c703dd182b67668e041f169b1b216b893c9
7
- data.tar.gz: 78b9983c8d189f1c9f755a301eeac0075c9252fc96590cd6f2d68f8407220113b943c2a871839f3801bff632ebb1724ceda5fa422838f62b6cf885a8671f36a2
6
+ metadata.gz: fe307f91f1d1a9794eab60bd64b875b41676167211ee0a2b47c8a5b79392ee6615d64b3031cb0ad93c3cc227d9377c3232d4cfe8b5baef111c57f048d40edad3
7
+ data.tar.gz: 729a890bf00251d97aba8c530ec82600d18ed5be75025d6239e1ccd6885491241d86f7bf994b8d39fa728b032ddb6789dfe96819de72ae3e4f990a3038c4207f
data/README.rdoc CHANGED
@@ -91,15 +91,19 @@ Also, dynamo_schema takes args and many options. Here's the full syntax:
91
91
  ...
92
92
  end
93
93
 
94
+
95
+ == Current State
96
+
94
97
  At the moment, OceanDynamo is fully usable as an ActiveModel and can be used by Rails
95
98
  controllers. Furthermore, OceanDynamo implements much of the infrastructure of ActiveRecord;
96
99
  for instance, +read_attribute+, +write_attribute+, and much of the control logic and
97
100
  parameters.
98
101
 
99
- Relations are not yet implemented, but are underway. Assocations will use secondary
102
+ Relations are underway. Assocations will use secondary
100
103
  indices, up to the DynamoDB maximum of 5 secondary keys per table. At the moment,
101
104
  only find with a single id is implemented. Collections can not yet be obtained.
102
- Work has begun on the +belongs_to+ association which requires only a primary index.
105
+ Work has begun on the +has_many+ / +belongs_to+ association which requires only a primary index.
106
+ After that, the +has_and_belongs_to_many+ association will be added.
103
107
 
104
108
  OceanDynamo is currently used in the Ocean framework (http://wiki.oceanframework.net)
105
109
  e.g. to implement critical job queues. It will be used increasingly as features are
@@ -1,31 +1,31 @@
1
1
  module OceanDynamo
2
2
  class Base
3
3
 
4
- def self.belongs_to(other_class)
5
- klass = other_class.to_s.capitalize.constantize
6
- other_class_attr = other_class.to_s.underscore
7
- name = "#{other_class_attr}_id"
8
- attribute name, :reference, default: nil, target_class: klass
9
- attribute other_class_attr, :reference, default: nil, target_class: klass, no_save: true
10
-
11
- self.class_eval "def #{other_class_attr}
12
- read_and_maybe_load_pointer('#{name}')
4
+ def self.belongs_to(target) # :api_user, "api_user", ApiUser
5
+ target_attr = target.to_s.underscore # "api_user"
6
+ target_attr_id = "#{target_attr}_id" # "api_user_id"
7
+ target_class = target_attr.camelize.constantize # ApiUser
8
+ attribute target_attr_id, :reference, default: nil, target_class: target_class
9
+ attribute target_attr, :reference, default: nil, target_class: target_class, no_save: true
10
+
11
+ self.class_eval "def #{target_attr}
12
+ read_and_maybe_load_pointer('#{target_attr_id}')
13
13
  end"
14
14
 
15
- self.class_eval "def #{name}
16
- read_pointer_id('#{name}')
15
+ self.class_eval "def #{target_attr}=(value)
16
+ write_attribute('#{target_attr_id}', value)
17
+ write_attribute('#{target_attr}', value)
17
18
  end"
18
19
 
19
- self.class_eval "def #{other_class_attr}=(value)
20
- write_attribute('#{name}', value)
21
- write_attribute('#{other_class_attr}', value)
20
+ self.class_eval "def #{target_attr_id}
21
+ read_pointer_id('#{target_attr}')
22
22
  end"
23
23
 
24
- self.class_eval "def #{name}=(value)
25
- write_attribute('#{name}', value)
26
- write_attribute('#{other_class_attr}', value)
24
+ self.class_eval "def #{target_attr_id}=(value)
25
+ write_attribute('#{target_attr_id}', value)
26
+ write_attribute('#{target_attr}', value)
27
27
  end"
28
- # TODO: Additional "?" method for name
28
+ # TODO: "?" methods
29
29
  end
30
30
 
31
31
 
@@ -0,0 +1,995 @@
1
+ module OceanDynamo
2
+ module Associations
3
+ #
4
+ # This entire file shamelessly lifted from ActiveRecord, for compatibility
5
+ # reasons. OceanDynamo must implement the same query interface as ActiveRecord.
6
+ #
7
+
8
+
9
+ #
10
+ # Association proxies in OceanDynamo are middlemen between the object that
11
+ # holds the association, known as the <tt>@owner</tt>, and the actual associated
12
+ # object, known as the <tt>@target</tt>. The kind of association any proxy is
13
+ # about is available in <tt>@reflection</tt>. That's an instance of the class
14
+ # OceanDynamo::Reflection::AssociationReflection.
15
+ #
16
+ # For example, given
17
+ #
18
+ # class Blog < OceanDynamo::Base
19
+ # has_many :posts
20
+ # end
21
+ #
22
+ # blog = Blog.first
23
+ #
24
+ # the association proxy in <tt>blog.posts</tt> has the object in +blog+ as
25
+ # <tt>@owner</tt>, the collection of its posts as <tt>@target</tt>, and
26
+ # the <tt>@reflection</tt> object represents a <tt>:has_many</tt> macro.
27
+ #
28
+ # This class delegates unknown methods to <tt>@target</tt> via
29
+ # <tt>method_missing</tt>.
30
+ #
31
+ # The <tt>@target</tt> object is not \loaded until needed.
32
+ #
33
+ class CollectionProxy < Relation
34
+ #delegate(*(ActiveRecord::Calculations.public_instance_methods - [:count]), to: :scope)
35
+
36
+ def initialize(klass, association) #:nodoc:
37
+ @association = association
38
+ super klass, klass.arel_table
39
+ self.default_scoped = true
40
+ merge! association.scope(nullify: false)
41
+ end
42
+
43
+ def target
44
+ @association.target
45
+ end
46
+
47
+ def load_target
48
+ @association.load_target
49
+ end
50
+
51
+
52
+ #
53
+ # Returns +true+ if the association has been loaded, otherwise +false+.
54
+ #
55
+ # person.pets.loaded? # => false
56
+ # person.pets
57
+ # person.pets.loaded? # => true
58
+ #
59
+ def loaded?
60
+ @association.loaded?
61
+ end
62
+
63
+
64
+ #
65
+ # Works in two ways.
66
+ #
67
+ # *First:* Specify a subset of fields to be selected from the result set.
68
+ #
69
+ # class Person < OceanDynamo::Base
70
+ # has_many :pets
71
+ # end
72
+ #
73
+ # person.pets
74
+ # # => [
75
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
76
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
77
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
78
+ # # ]
79
+ #
80
+ # person.pets.select(:name)
81
+ # # => [
82
+ # # #<Pet id: nil, name: "Fancy-Fancy">,
83
+ # # #<Pet id: nil, name: "Spook">,
84
+ # # #<Pet id: nil, name: "Choo-Choo">
85
+ # # ]
86
+ #
87
+ # person.pets.select([:id, :name])
88
+ # # => [
89
+ # # #<Pet id: 1, name: "Fancy-Fancy">,
90
+ # # #<Pet id: 2, name: "Spook">,
91
+ # # #<Pet id: 3, name: "Choo-Choo">
92
+ # # ]
93
+ #
94
+ # Be careful because this also means you're initializing a model
95
+ # object with only the fields that you've selected. If you attempt
96
+ # to access a field that is not in the initialized record you'll
97
+ # receive:
98
+ #
99
+ # person.pets.select(:name).first.person_id
100
+ # # => ActiveModel::MissingAttributeError: missing attribute: person_id
101
+ #
102
+ # *Second:* You can pass a block so it can be used just like Array#select.
103
+ # This builds an array of objects from the database for the scope,
104
+ # converting them into an array and iterating through them using
105
+ # Array#select.
106
+ #
107
+ # person.pets.select { |pet| pet.name =~ /oo/ }
108
+ # # => [
109
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
110
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
111
+ # # ]
112
+ #
113
+ # person.pets.select(:name) { |pet| pet.name =~ /oo/ }
114
+ # # => [
115
+ # # #<Pet id: 2, name: "Spook">,
116
+ # # #<Pet id: 3, name: "Choo-Choo">
117
+ # # ]
118
+ #
119
+ def select(select = nil, &block)
120
+ @association.select(select, &block)
121
+ end
122
+
123
+ # Finds an object in the collection responding to the +id+. Uses the same
124
+ # rules as <tt>OceanDynamo::Base.find</tt>. Returns <tt>OceanDynamo::RecordNotFound</tt>
125
+ # error if the object can not be found.
126
+ #
127
+ # class Person < OceanDynamo::Base
128
+ # has_many :pets
129
+ # end
130
+ #
131
+ # person.pets
132
+ # # => [
133
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
134
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
135
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
136
+ # # ]
137
+ #
138
+ # person.pets.find(1) # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
139
+ # person.pets.find(4) # => OceanDynamo::RecordNotFound: Couldn't find Pet with id=4
140
+ #
141
+ # person.pets.find(2) { |pet| pet.name.downcase! }
142
+ # # => #<Pet id: 2, name: "fancy-fancy", person_id: 1>
143
+ #
144
+ # person.pets.find(2, 3)
145
+ # # => [
146
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
147
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
148
+ # # ]
149
+ def find(*args, &block)
150
+ @association.find(*args, &block)
151
+ end
152
+
153
+ # Returns the first record, or the first +n+ records, from the collection.
154
+ # If the collection is empty, the first form returns +nil+, and the second
155
+ # form returns an empty array.
156
+ #
157
+ # class Person < OceanDynamo::Base
158
+ # has_many :pets
159
+ # end
160
+ #
161
+ # person.pets
162
+ # # => [
163
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
164
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
165
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
166
+ # # ]
167
+ #
168
+ # person.pets.first # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
169
+ #
170
+ # person.pets.first(2)
171
+ # # => [
172
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
173
+ # # #<Pet id: 2, name: "Spook", person_id: 1>
174
+ # # ]
175
+ #
176
+ # another_person_without.pets # => []
177
+ # another_person_without.pets.first # => nil
178
+ # another_person_without.pets.first(3) # => []
179
+ def first(*args)
180
+ @association.first(*args)
181
+ end
182
+
183
+ # Returns the last record, or the last +n+ records, from the collection.
184
+ # If the collection is empty, the first form returns +nil+, and the second
185
+ # form returns an empty array.
186
+ #
187
+ # class Person < OceanDynamo::Base
188
+ # has_many :pets
189
+ # end
190
+ #
191
+ # person.pets
192
+ # # => [
193
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
194
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
195
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
196
+ # # ]
197
+ #
198
+ # person.pets.last # => #<Pet id: 3, name: "Choo-Choo", person_id: 1>
199
+ #
200
+ # person.pets.last(2)
201
+ # # => [
202
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
203
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
204
+ # # ]
205
+ #
206
+ # another_person_without.pets # => []
207
+ # another_person_without.pets.last # => nil
208
+ # another_person_without.pets.last(3) # => []
209
+ def last(*args)
210
+ @association.last(*args)
211
+ end
212
+
213
+ # Returns a new object of the collection type that has been instantiated
214
+ # with +attributes+ and linked to this object, but have not yet been saved.
215
+ # You can pass an array of attributes hashes, this will return an array
216
+ # with the new objects.
217
+ #
218
+ # class Person < OceanDynamo::Base
219
+ # has_many :pets
220
+ # end
221
+ #
222
+ # person.pets.build
223
+ # # => #<Pet id: nil, name: nil, person_id: 1>
224
+ #
225
+ # person.pets.build(name: 'Fancy-Fancy')
226
+ # # => #<Pet id: nil, name: "Fancy-Fancy", person_id: 1>
227
+ #
228
+ # person.pets.build([{name: 'Spook'}, {name: 'Choo-Choo'}, {name: 'Brain'}])
229
+ # # => [
230
+ # # #<Pet id: nil, name: "Spook", person_id: 1>,
231
+ # # #<Pet id: nil, name: "Choo-Choo", person_id: 1>,
232
+ # # #<Pet id: nil, name: "Brain", person_id: 1>
233
+ # # ]
234
+ #
235
+ # person.pets.size # => 5 # size of the collection
236
+ # person.pets.count # => 0 # count from database
237
+ def build(attributes = {}, &block)
238
+ @association.build(attributes, &block)
239
+ end
240
+ alias_method :new, :build
241
+
242
+ # Returns a new object of the collection type that has been instantiated with
243
+ # attributes, linked to this object and that has already been saved (if it
244
+ # passes the validations).
245
+ #
246
+ # class Person < OceanDynamo::Base
247
+ # has_many :pets
248
+ # end
249
+ #
250
+ # person.pets.create(name: 'Fancy-Fancy')
251
+ # # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
252
+ #
253
+ # person.pets.create([{name: 'Spook'}, {name: 'Choo-Choo'}])
254
+ # # => [
255
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
256
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
257
+ # # ]
258
+ #
259
+ # person.pets.size # => 3
260
+ # person.pets.count # => 3
261
+ #
262
+ # person.pets.find(1, 2, 3)
263
+ # # => [
264
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
265
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
266
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
267
+ # # ]
268
+ def create(attributes = {}, &block)
269
+ @association.create(attributes, &block)
270
+ end
271
+
272
+ # Like +create+, except that if the record is invalid, raises an exception.
273
+ #
274
+ # class Person < OceanDynamo::Base
275
+ # has_many :pets
276
+ # end
277
+ #
278
+ # class Pet
279
+ # validates :name, presence: true
280
+ # end
281
+ #
282
+ # person.pets.create!(name: nil)
283
+ # # => OceanDynamo::RecordInvalid: Validation failed: Name can't be blank
284
+ def create!(attributes = {}, &block)
285
+ @association.create!(attributes, &block)
286
+ end
287
+
288
+ # Add one or more records to the collection by setting their foreign keys
289
+ # to the association's primary key. Since << flattens its argument list and
290
+ # inserts each record, +push+ and +concat+ behave identically. Returns +self+
291
+ # so method calls may be chained.
292
+ #
293
+ # class Person < OceanDynamo::Base
294
+ # pets :has_many
295
+ # end
296
+ #
297
+ # person.pets.size # => 0
298
+ # person.pets.concat(Pet.new(name: 'Fancy-Fancy'))
299
+ # person.pets.concat(Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo'))
300
+ # person.pets.size # => 3
301
+ #
302
+ # person.id # => 1
303
+ # person.pets
304
+ # # => [
305
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
306
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
307
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
308
+ # # ]
309
+ #
310
+ # person.pets.concat([Pet.new(name: 'Brain'), Pet.new(name: 'Benny')])
311
+ # person.pets.size # => 5
312
+ def concat(*records)
313
+ @association.concat(*records)
314
+ end
315
+
316
+ # Replaces this collection with +other_array+. This will perform a diff
317
+ # and delete/add only records that have changed.
318
+ #
319
+ # class Person < OceanDynamo::Base
320
+ # has_many :pets
321
+ # end
322
+ #
323
+ # person.pets
324
+ # # => [#<Pet id: 1, name: "Gorby", group: "cats", person_id: 1>]
325
+ #
326
+ # other_pets = [Pet.new(name: 'Puff', group: 'celebrities']
327
+ #
328
+ # person.pets.replace(other_pets)
329
+ #
330
+ # person.pets
331
+ # # => [#<Pet id: 2, name: "Puff", group: "celebrities", person_id: 1>]
332
+ #
333
+ # If the supplied array has an incorrect association type, it raises
334
+ # an <tt>OceanDynamo::AssociationTypeMismatch</tt> error:
335
+ #
336
+ # person.pets.replace(["doo", "ggie", "gaga"])
337
+ # # => OceanDynamo::AssociationTypeMismatch: Pet expected, got String
338
+ def replace(other_array)
339
+ @association.replace(other_array)
340
+ end
341
+
342
+ # Deletes all the records from the collection. For +has_many+ associations,
343
+ # the deletion is done according to the strategy specified by the <tt>:dependent</tt>
344
+ # option. Returns an array with the deleted records.
345
+ #
346
+ # If no <tt>:dependent</tt> option is given, then it will follow the
347
+ # default strategy. The default strategy is <tt>:nullify</tt>. This
348
+ # sets the foreign keys to <tt>NULL</tt>. For, +has_many+ <tt>:through</tt>,
349
+ # the default strategy is +delete_all+.
350
+ #
351
+ # class Person < OceanDynamo::Base
352
+ # has_many :pets # dependent: :nullify option by default
353
+ # end
354
+ #
355
+ # person.pets.size # => 3
356
+ # person.pets
357
+ # # => [
358
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
359
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
360
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
361
+ # # ]
362
+ #
363
+ # person.pets.delete_all
364
+ # # => [
365
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
366
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
367
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
368
+ # # ]
369
+ #
370
+ # person.pets.size # => 0
371
+ # person.pets # => []
372
+ #
373
+ # Pet.find(1, 2, 3)
374
+ # # => [
375
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: nil>,
376
+ # # #<Pet id: 2, name: "Spook", person_id: nil>,
377
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: nil>
378
+ # # ]
379
+ #
380
+ # If it is set to <tt>:destroy</tt> all the objects from the collection
381
+ # are removed by calling their +destroy+ method. See +destroy+ for more
382
+ # information.
383
+ #
384
+ # class Person < OceanDynamo::Base
385
+ # has_many :pets, dependent: :destroy
386
+ # end
387
+ #
388
+ # person.pets.size # => 3
389
+ # person.pets
390
+ # # => [
391
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
392
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
393
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
394
+ # # ]
395
+ #
396
+ # person.pets.delete_all
397
+ # # => [
398
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
399
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
400
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
401
+ # # ]
402
+ #
403
+ # Pet.find(1, 2, 3)
404
+ # # => OceanDynamo::RecordNotFound
405
+ #
406
+ # If it is set to <tt>:delete_all</tt>, all the objects are deleted
407
+ # *without* calling their +destroy+ method.
408
+ #
409
+ # class Person < OceanDynamo::Base
410
+ # has_many :pets, dependent: :delete_all
411
+ # end
412
+ #
413
+ # person.pets.size # => 3
414
+ # person.pets
415
+ # # => [
416
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
417
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
418
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
419
+ # # ]
420
+ #
421
+ # person.pets.delete_all
422
+ # # => [
423
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
424
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
425
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
426
+ # # ]
427
+ #
428
+ # Pet.find(1, 2, 3)
429
+ # # => OceanDynamo::RecordNotFound
430
+ def delete_all
431
+ @association.delete_all
432
+ end
433
+
434
+ # Deletes the records of the collection directly from the database.
435
+ # This will _always_ remove the records ignoring the +:dependent+
436
+ # option.
437
+ #
438
+ # class Person < OceanDynamo::Base
439
+ # has_many :pets
440
+ # end
441
+ #
442
+ # person.pets.size # => 3
443
+ # person.pets
444
+ # # => [
445
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
446
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
447
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
448
+ # # ]
449
+ #
450
+ # person.pets.destroy_all
451
+ #
452
+ # person.pets.size # => 0
453
+ # person.pets # => []
454
+ #
455
+ # Pet.find(1) # => Couldn't find Pet with id=1
456
+ def destroy_all
457
+ @association.destroy_all
458
+ end
459
+
460
+ # Deletes the +records+ supplied and removes them from the collection. For
461
+ # +has_many+ associations, the deletion is done according to the strategy
462
+ # specified by the <tt>:dependent</tt> option. Returns an array with the
463
+ # deleted records.
464
+ #
465
+ # If no <tt>:dependent</tt> option is given, then it will follow the default
466
+ # strategy. The default strategy is <tt>:nullify</tt>. This sets the foreign
467
+ # keys to <tt>NULL</tt>. For, +has_many+ <tt>:through</tt>, the default
468
+ # strategy is +delete_all+.
469
+ #
470
+ # class Person < OceanDynamo::Base
471
+ # has_many :pets # dependent: :nullify option by default
472
+ # end
473
+ #
474
+ # person.pets.size # => 3
475
+ # person.pets
476
+ # # => [
477
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
478
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
479
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
480
+ # # ]
481
+ #
482
+ # person.pets.delete(Pet.find(1))
483
+ # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
484
+ #
485
+ # person.pets.size # => 2
486
+ # person.pets
487
+ # # => [
488
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
489
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
490
+ # # ]
491
+ #
492
+ # Pet.find(1)
493
+ # # => #<Pet id: 1, name: "Fancy-Fancy", person_id: nil>
494
+ #
495
+ # If it is set to <tt>:destroy</tt> all the +records+ are removed by calling
496
+ # their +destroy+ method. See +destroy+ for more information.
497
+ #
498
+ # class Person < OceanDynamo::Base
499
+ # has_many :pets, dependent: :destroy
500
+ # end
501
+ #
502
+ # person.pets.size # => 3
503
+ # person.pets
504
+ # # => [
505
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
506
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
507
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
508
+ # # ]
509
+ #
510
+ # person.pets.delete(Pet.find(1), Pet.find(3))
511
+ # # => [
512
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
513
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
514
+ # # ]
515
+ #
516
+ # person.pets.size # => 1
517
+ # person.pets
518
+ # # => [#<Pet id: 2, name: "Spook", person_id: 1>]
519
+ #
520
+ # Pet.find(1, 3)
521
+ # # => OceanDynamo::RecordNotFound: Couldn't find all Pets with IDs (1, 3)
522
+ #
523
+ # If it is set to <tt>:delete_all</tt>, all the +records+ are deleted
524
+ # *without* calling their +destroy+ method.
525
+ #
526
+ # class Person < OceanDynamo::Base
527
+ # has_many :pets, dependent: :delete_all
528
+ # end
529
+ #
530
+ # person.pets.size # => 3
531
+ # person.pets
532
+ # # => [
533
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
534
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
535
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
536
+ # # ]
537
+ #
538
+ # person.pets.delete(Pet.find(1))
539
+ # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
540
+ #
541
+ # person.pets.size # => 2
542
+ # person.pets
543
+ # # => [
544
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
545
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
546
+ # # ]
547
+ #
548
+ # Pet.find(1)
549
+ # # => OceanDynamo::RecordNotFound: Couldn't find Pet with id=1
550
+ #
551
+ # You can pass +Fixnum+ or +String+ values, it finds the records
552
+ # responding to the +id+ and executes delete on them.
553
+ #
554
+ # class Person < OceanDynamo::Base
555
+ # has_many :pets
556
+ # end
557
+ #
558
+ # person.pets.size # => 3
559
+ # person.pets
560
+ # # => [
561
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
562
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
563
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
564
+ # # ]
565
+ #
566
+ # person.pets.delete("1")
567
+ # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
568
+ #
569
+ # person.pets.delete(2, 3)
570
+ # # => [
571
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
572
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
573
+ # # ]
574
+ def delete(*records)
575
+ @association.delete(*records)
576
+ end
577
+
578
+ # Destroys the +records+ supplied and removes them from the collection.
579
+ # This method will _always_ remove record from the database ignoring
580
+ # the +:dependent+ option. Returns an array with the removed records.
581
+ #
582
+ # class Person < OceanDynamo::Base
583
+ # has_many :pets
584
+ # end
585
+ #
586
+ # person.pets.size # => 3
587
+ # person.pets
588
+ # # => [
589
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
590
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
591
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
592
+ # # ]
593
+ #
594
+ # person.pets.destroy(Pet.find(1))
595
+ # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
596
+ #
597
+ # person.pets.size # => 2
598
+ # person.pets
599
+ # # => [
600
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
601
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
602
+ # # ]
603
+ #
604
+ # person.pets.destroy(Pet.find(2), Pet.find(3))
605
+ # # => [
606
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
607
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
608
+ # # ]
609
+ #
610
+ # person.pets.size # => 0
611
+ # person.pets # => []
612
+ #
613
+ # Pet.find(1, 2, 3) # => OceanDynamo::RecordNotFound: Couldn't find all Pets with IDs (1, 2, 3)
614
+ #
615
+ # You can pass +Fixnum+ or +String+ values, it finds the records
616
+ # responding to the +id+ and then deletes them from the database.
617
+ #
618
+ # person.pets.size # => 3
619
+ # person.pets
620
+ # # => [
621
+ # # #<Pet id: 4, name: "Benny", person_id: 1>,
622
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
623
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
624
+ # # ]
625
+ #
626
+ # person.pets.destroy("4")
627
+ # # => #<Pet id: 4, name: "Benny", person_id: 1>
628
+ #
629
+ # person.pets.size # => 2
630
+ # person.pets
631
+ # # => [
632
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
633
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
634
+ # # ]
635
+ #
636
+ # person.pets.destroy(5, 6)
637
+ # # => [
638
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
639
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
640
+ # # ]
641
+ #
642
+ # person.pets.size # => 0
643
+ # person.pets # => []
644
+ #
645
+ # Pet.find(4, 5, 6) # => OceanDynamo::RecordNotFound: Couldn't find all Pets with IDs (4, 5, 6)
646
+ def destroy(*records)
647
+ @association.destroy(*records)
648
+ end
649
+
650
+ # Specifies whether the records should be unique or not.
651
+ #
652
+ # class Person < OceanDynamo::Base
653
+ # has_many :pets
654
+ # end
655
+ #
656
+ # person.pets.select(:name)
657
+ # # => [
658
+ # # #<Pet name: "Fancy-Fancy">,
659
+ # # #<Pet name: "Fancy-Fancy">
660
+ # # ]
661
+ #
662
+ # person.pets.select(:name).distinct
663
+ # # => [#<Pet name: "Fancy-Fancy">]
664
+ def distinct
665
+ @association.distinct
666
+ end
667
+ alias uniq distinct
668
+
669
+ # Count all records using SQL.
670
+ #
671
+ # class Person < OceanDynamo::Base
672
+ # has_many :pets
673
+ # end
674
+ #
675
+ # person.pets.count # => 3
676
+ # person.pets
677
+ # # => [
678
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
679
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
680
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
681
+ # # ]
682
+ def count(column_name = nil, options = {})
683
+ @association.count(column_name, options)
684
+ end
685
+
686
+ # Returns the size of the collection. If the collection hasn't been loaded,
687
+ # it executes a <tt>SELECT COUNT(*)</tt> query. Else it calls <tt>collection.size</tt>.
688
+ #
689
+ # If the collection has been already loaded +size+ and +length+ are
690
+ # equivalent. If not and you are going to need the records anyway
691
+ # +length+ will take one less query. Otherwise +size+ is more efficient.
692
+ #
693
+ # class Person < OceanDynamo::Base
694
+ # has_many :pets
695
+ # end
696
+ #
697
+ # person.pets.size # => 3
698
+ # # executes something like SELECT COUNT(*) FROM "pets" WHERE "pets"."person_id" = 1
699
+ #
700
+ # person.pets # This will execute a SELECT * FROM query
701
+ # # => [
702
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
703
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
704
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
705
+ # # ]
706
+ #
707
+ # person.pets.size # => 3
708
+ # # Because the collection is already loaded, this will behave like
709
+ # # collection.size and no SQL count query is executed.
710
+ def size
711
+ @association.size
712
+ end
713
+
714
+ # Returns the size of the collection calling +size+ on the target.
715
+ # If the collection has been already loaded, +length+ and +size+ are
716
+ # equivalent. If not and you are going to need the records anyway this
717
+ # method will take one less query. Otherwise +size+ is more efficient.
718
+ #
719
+ # class Person < OceanDynamo::Base
720
+ # has_many :pets
721
+ # end
722
+ #
723
+ # person.pets.length # => 3
724
+ # # executes something like SELECT "pets".* FROM "pets" WHERE "pets"."person_id" = 1
725
+ #
726
+ # # Because the collection is loaded, you can
727
+ # # call the collection with no additional queries:
728
+ # person.pets
729
+ # # => [
730
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
731
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
732
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
733
+ # # ]
734
+ def length
735
+ @association.length
736
+ end
737
+
738
+ # Returns +true+ if the collection is empty. If the collection has been
739
+ # loaded or the <tt>:counter_sql</tt> option is provided, it is equivalent
740
+ # to <tt>collection.size.zero?</tt>. If the collection has not been loaded,
741
+ # it is equivalent to <tt>collection.exists?</tt>. If the collection has
742
+ # not already been loaded and you are going to fetch the records anyway it
743
+ # is better to check <tt>collection.length.zero?</tt>.
744
+ #
745
+ # class Person < OceanDynamo::Base
746
+ # has_many :pets
747
+ # end
748
+ #
749
+ # person.pets.count # => 1
750
+ # person.pets.empty? # => false
751
+ #
752
+ # person.pets.delete_all
753
+ #
754
+ # person.pets.count # => 0
755
+ # person.pets.empty? # => true
756
+ def empty?
757
+ @association.empty?
758
+ end
759
+
760
+ # Returns +true+ if the collection is not empty.
761
+ #
762
+ # class Person < OceanDynamo::Base
763
+ # has_many :pets
764
+ # end
765
+ #
766
+ # person.pets.count # => 0
767
+ # person.pets.any? # => false
768
+ #
769
+ # person.pets << Pet.new(name: 'Snoop')
770
+ # person.pets.count # => 0
771
+ # person.pets.any? # => true
772
+ #
773
+ # You can also pass a block to define criteria. The behavior
774
+ # is the same, it returns true if the collection based on the
775
+ # criteria is not empty.
776
+ #
777
+ # person.pets
778
+ # # => [#<Pet name: "Snoop", group: "dogs">]
779
+ #
780
+ # person.pets.any? do |pet|
781
+ # pet.group == 'cats'
782
+ # end
783
+ # # => false
784
+ #
785
+ # person.pets.any? do |pet|
786
+ # pet.group == 'dogs'
787
+ # end
788
+ # # => true
789
+ def any?(&block)
790
+ @association.any?(&block)
791
+ end
792
+
793
+ # Returns true if the collection has more than one record.
794
+ # Equivalent to <tt>collection.size > 1</tt>.
795
+ #
796
+ # class Person < OceanDynamo::Base
797
+ # has_many :pets
798
+ # end
799
+ #
800
+ # person.pets.count #=> 1
801
+ # person.pets.many? #=> false
802
+ #
803
+ # person.pets << Pet.new(name: 'Snoopy')
804
+ # person.pets.count #=> 2
805
+ # person.pets.many? #=> true
806
+ #
807
+ # You can also pass a block to define criteria. The
808
+ # behavior is the same, it returns true if the collection
809
+ # based on the criteria has more than one record.
810
+ #
811
+ # person.pets
812
+ # # => [
813
+ # # #<Pet name: "Gorby", group: "cats">,
814
+ # # #<Pet name: "Puff", group: "cats">,
815
+ # # #<Pet name: "Snoop", group: "dogs">
816
+ # # ]
817
+ #
818
+ # person.pets.many? do |pet|
819
+ # pet.group == 'dogs'
820
+ # end
821
+ # # => false
822
+ #
823
+ # person.pets.many? do |pet|
824
+ # pet.group == 'cats'
825
+ # end
826
+ # # => true
827
+ def many?(&block)
828
+ @association.many?(&block)
829
+ end
830
+
831
+ # Returns +true+ if the given object is present in the collection.
832
+ #
833
+ # class Person < OceanDynamo::Base
834
+ # has_many :pets
835
+ # end
836
+ #
837
+ # person.pets # => [#<Pet id: 20, name: "Snoop">]
838
+ #
839
+ # person.pets.include?(Pet.find(20)) # => true
840
+ # person.pets.include?(Pet.find(21)) # => false
841
+ def include?(record)
842
+ @association.include?(record)
843
+ end
844
+
845
+ def proxy_association
846
+ @association
847
+ end
848
+
849
+ # We don't want this object to be put on the scoping stack, because
850
+ # that could create an infinite loop where we call an @association
851
+ # method, which gets the current scope, which is this object, which
852
+ # delegates to @association, and so on.
853
+ def scoping
854
+ @association.scope.scoping { yield }
855
+ end
856
+
857
+ # Returns a <tt>Relation</tt> object for the records in this association
858
+ def scope
859
+ @association.scope.tap do |scope|
860
+ scope.proxy_association = @association
861
+ end
862
+ end
863
+
864
+ # :nodoc:
865
+ alias spawn scope
866
+
867
+ # Equivalent to <tt>Array#==</tt>. Returns +true+ if the two arrays
868
+ # contain the same number of elements and if each element is equal
869
+ # to the corresponding element in the other array, otherwise returns
870
+ # +false+.
871
+ #
872
+ # class Person < OceanDynamo::Base
873
+ # has_many :pets
874
+ # end
875
+ #
876
+ # person.pets
877
+ # # => [
878
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
879
+ # # #<Pet id: 2, name: "Spook", person_id: 1>
880
+ # # ]
881
+ #
882
+ # other = person.pets.to_ary
883
+ #
884
+ # person.pets == other
885
+ # # => true
886
+ #
887
+ # other = [Pet.new(id: 1), Pet.new(id: 2)]
888
+ #
889
+ # person.pets == other
890
+ # # => false
891
+ def ==(other)
892
+ load_target == other
893
+ end
894
+
895
+ # Returns a new array of objects from the collection. If the collection
896
+ # hasn't been loaded, it fetches the records from the database.
897
+ #
898
+ # class Person < OceanDynamo::Base
899
+ # has_many :pets
900
+ # end
901
+ #
902
+ # person.pets
903
+ # # => [
904
+ # # #<Pet id: 4, name: "Benny", person_id: 1>,
905
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
906
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
907
+ # # ]
908
+ #
909
+ # other_pets = person.pets.to_ary
910
+ # # => [
911
+ # # #<Pet id: 4, name: "Benny", person_id: 1>,
912
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
913
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
914
+ # # ]
915
+ #
916
+ # other_pets.replace([Pet.new(name: 'BooGoo')])
917
+ #
918
+ # other_pets
919
+ # # => [#<Pet id: nil, name: "BooGoo", person_id: 1>]
920
+ #
921
+ # person.pets
922
+ # # This is not affected by replace
923
+ # # => [
924
+ # # #<Pet id: 4, name: "Benny", person_id: 1>,
925
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
926
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
927
+ # # ]
928
+ def to_ary
929
+ load_target.dup
930
+ end
931
+ alias_method :to_a, :to_ary
932
+
933
+ # Adds one or more +records+ to the collection by setting their foreign keys
934
+ # to the association's primary key. Returns +self+, so several appends may be
935
+ # chained together.
936
+ #
937
+ # class Person < OceanDynamo::Base
938
+ # has_many :pets
939
+ # end
940
+ #
941
+ # person.pets.size # => 0
942
+ # person.pets << Pet.new(name: 'Fancy-Fancy')
943
+ # person.pets << [Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo')]
944
+ # person.pets.size # => 3
945
+ #
946
+ # person.id # => 1
947
+ # person.pets
948
+ # # => [
949
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
950
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
951
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
952
+ # # ]
953
+ def <<(*records)
954
+ proxy_association.concat(records) && self
955
+ end
956
+ alias_method :push, :<<
957
+ alias_method :append, :<<
958
+
959
+ def prepend(*args)
960
+ raise NoMethodError, "prepend on association is not defined. Please use << or append"
961
+ end
962
+
963
+ # Equivalent to +delete_all+. The difference is that returns +self+, instead
964
+ # of an array with the deleted objects, so methods can be chained. See
965
+ # +delete_all+ for more information.
966
+ def clear
967
+ delete_all
968
+ self
969
+ end
970
+
971
+ # Reloads the collection from the database. Returns +self+.
972
+ # Equivalent to <tt>collection(true)</tt>.
973
+ #
974
+ # class Person < OceanDynamo::Base
975
+ # has_many :pets
976
+ # end
977
+ #
978
+ # person.pets # fetches pets from the database
979
+ # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
980
+ #
981
+ # person.pets # uses the pets cache
982
+ # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
983
+ #
984
+ # person.pets.reload # fetches pets from the database
985
+ # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
986
+ #
987
+ # person.pets(true) # fetches pets from the database
988
+ # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
989
+ def reload
990
+ proxy_association.reload
991
+ self
992
+ end
993
+ end
994
+ end
995
+ end
@@ -1,3 +1,3 @@
1
1
  module OceanDynamo
2
- VERSION = "0.3.4"
2
+ VERSION = "0.3.5"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ocean-dynamo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.4
4
+ version: 0.3.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Bengtson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-09-14 00:00:00.000000000 Z
11
+ date: 2013-09-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk
@@ -165,6 +165,7 @@ files:
165
165
  - lib/ocean-dynamo/base.rb
166
166
  - lib/ocean-dynamo/callbacks.rb
167
167
  - lib/ocean-dynamo/class_variables.rb
168
+ - lib/ocean-dynamo/collection_proxy.rb
168
169
  - lib/ocean-dynamo/engine.rb
169
170
  - lib/ocean-dynamo/exceptions.rb
170
171
  - lib/ocean-dynamo/persistence.rb