ocean-dynamo 0.3.4 → 0.3.5

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.
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