tenacity 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/EXTEND.rdoc +9 -13
- data/Rakefile +6 -0
- data/history.txt +17 -0
- data/lib/tenacity.rb +13 -9
- data/lib/tenacity/associate_proxy.rb +56 -0
- data/lib/tenacity/associates_proxy.rb +5 -3
- data/lib/tenacity/association.rb +81 -9
- data/lib/tenacity/associations/belongs_to.rb +28 -16
- data/lib/tenacity/associations/has_many.rb +113 -53
- data/lib/tenacity/associations/has_one.rb +33 -14
- data/lib/tenacity/class_methods.rb +117 -9
- data/lib/tenacity/errors.rb +9 -0
- data/lib/tenacity/instance_methods.rb +40 -3
- data/lib/tenacity/orm_ext/activerecord.rb +114 -95
- data/lib/tenacity/orm_ext/couchrest.rb +132 -113
- data/lib/tenacity/orm_ext/datamapper.rb +138 -112
- data/lib/tenacity/orm_ext/helpers.rb +36 -0
- data/lib/tenacity/orm_ext/mongo_mapper.rb +102 -84
- data/lib/tenacity/orm_ext/mongoid.rb +106 -87
- data/lib/tenacity/orm_ext/sequel.rb +137 -110
- data/lib/tenacity/version.rb +1 -1
- data/tenacity.gemspec +2 -1
- data/test/association_features/belongs_to_test.rb +46 -1
- data/test/association_features/has_many_test.rb +187 -2
- data/test/association_features/has_one_test.rb +57 -2
- data/test/associations/belongs_to_test.rb +41 -7
- data/test/associations/has_many_test.rb +59 -10
- data/test/associations/has_one_test.rb +57 -2
- data/test/fixtures/active_record_car.rb +5 -4
- data/test/fixtures/active_record_climate_control_unit.rb +1 -0
- data/test/fixtures/active_record_engine.rb +1 -0
- data/test/fixtures/active_record_has_many_target.rb +7 -0
- data/test/fixtures/active_record_has_one_target.rb +7 -0
- data/test/fixtures/active_record_object.rb +19 -0
- data/test/fixtures/couch_rest_has_many_target.rb +7 -0
- data/test/fixtures/couch_rest_has_one_target.rb +7 -0
- data/test/fixtures/couch_rest_object.rb +14 -0
- data/test/fixtures/data_mapper_has_many_target.rb +23 -3
- data/test/fixtures/data_mapper_has_one_target.rb +23 -3
- data/test/fixtures/data_mapper_object.rb +14 -0
- data/test/fixtures/mongo_mapper_air_filter.rb +6 -0
- data/test/fixtures/mongo_mapper_alternator.rb +7 -0
- data/test/fixtures/mongo_mapper_autosave_false_has_many_target.rb +8 -0
- data/test/fixtures/mongo_mapper_autosave_false_has_one_target.rb +8 -0
- data/test/fixtures/mongo_mapper_autosave_true_has_many_target.rb +8 -0
- data/test/fixtures/mongo_mapper_autosave_true_has_one_target.rb +8 -0
- data/test/fixtures/mongo_mapper_circuit_board.rb +6 -0
- data/test/fixtures/mongo_mapper_dashboard.rb +5 -2
- data/test/fixtures/mongo_mapper_has_many_target.rb +7 -0
- data/test/fixtures/mongo_mapper_has_one_target.rb +7 -0
- data/test/fixtures/mongo_mapper_object.rb +14 -0
- data/test/fixtures/mongo_mapper_wheel.rb +2 -0
- data/test/fixtures/mongo_mapper_windows.rb +6 -0
- data/test/fixtures/mongoid_has_many_target.rb +7 -0
- data/test/fixtures/mongoid_has_one_target.rb +7 -0
- data/test/fixtures/mongoid_object.rb +14 -0
- data/test/fixtures/sequel_has_many_target.rb +7 -0
- data/test/fixtures/sequel_has_one_target.rb +7 -0
- data/test/fixtures/sequel_object.rb +14 -0
- data/test/helpers/active_record_test_helper.rb +87 -8
- data/test/helpers/couch_rest_test_helper.rb +7 -0
- data/test/helpers/data_mapper_test_helper.rb +41 -3
- data/test/helpers/sequel_test_helper.rb +65 -9
- data/test/orm_ext/activerecord_test.rb +1 -1
- data/test/orm_ext/datamapper_test.rb +33 -24
- data/test/orm_ext/mongo_mapper_test.rb +6 -6
- data/test/orm_ext/mongoid_test.rb +6 -6
- data/test/orm_ext/sequel_test.rb +1 -1
- data/test/test_helper.rb +18 -9
- metadata +119 -55
@@ -1,24 +1,43 @@
|
|
1
1
|
module Tenacity
|
2
|
-
module
|
2
|
+
module Associations
|
3
|
+
module HasOne #:nodoc:
|
3
4
|
|
4
|
-
|
5
|
+
def _t_cleanup_has_one_association(association)
|
6
|
+
associate = has_one_associate(association)
|
7
|
+
unless associate.nil?
|
8
|
+
if association.dependent == :destroy
|
9
|
+
association.associate_class._t_delete([_t_serialize(associate.id)])
|
10
|
+
elsif association.dependent == :delete
|
11
|
+
association.associate_class._t_delete([_t_serialize(associate.id)], false)
|
12
|
+
elsif association.dependent == :nullify
|
13
|
+
associate.send "#{association.foreign_key(self.class)}=", nil
|
14
|
+
associate.save
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
5
18
|
|
6
|
-
|
7
|
-
clazz = association.associate_class
|
8
|
-
clazz._t_find_first_by_associate(association.foreign_key(self.class), self.id.to_s)
|
9
|
-
end
|
19
|
+
private
|
10
20
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
21
|
+
def has_one_associate(association)
|
22
|
+
clazz = association.associate_class
|
23
|
+
associate = clazz._t_find_first_by_associate(association.foreign_key(self.class), _t_serialize(self.id, association))
|
24
|
+
associate
|
25
|
+
end
|
15
26
|
|
16
|
-
|
17
|
-
|
18
|
-
|
27
|
+
def set_has_one_associate(association, associate)
|
28
|
+
associate.send "#{association.foreign_key(self.class)}=", _t_serialize(self.id, association)
|
29
|
+
associate.send "#{association.polymorphic_type}=", self.class.to_s if association.polymorphic?
|
30
|
+
associate.save unless association.autosave == false
|
31
|
+
associate
|
32
|
+
end
|
33
|
+
|
34
|
+
module ClassMethods #:nodoc:
|
35
|
+
def initialize_has_one_association(association)
|
36
|
+
_t_initialize_has_one_association(association) if respond_to?(:_t_initialize_has_one_association)
|
37
|
+
end
|
19
38
|
end
|
20
|
-
end
|
21
39
|
|
40
|
+
end
|
22
41
|
end
|
23
42
|
end
|
24
43
|
|
@@ -86,6 +86,10 @@ module Tenacity
|
|
86
86
|
# You can manipulate objects and associations before they are saved to the database, but there is some special behavior you should be
|
87
87
|
# aware of, mostly involving the saving of associated objects.
|
88
88
|
#
|
89
|
+
# Unless you set the :autosave option on a <tt>t_has_one</tt>, <tt>t_belongs_to</tt>, or
|
90
|
+
# <tt>t_has_many</tt> association. Setting it to +true+ will _always_ save the members,
|
91
|
+
# whereas setting it to +false+ will _never_ save the members.
|
92
|
+
#
|
89
93
|
# === One-to-one associations
|
90
94
|
#
|
91
95
|
# * Assigning an object to a +t_has_one+ association automatically saves that object and the object being replaced (if there is one), in
|
@@ -99,6 +103,30 @@ module Tenacity
|
|
99
103
|
# (the owner of the collection) is not yet stored in the database.
|
100
104
|
# * All unsaved members of the collection are automatically saved when the parent is saved.
|
101
105
|
#
|
106
|
+
# === Polymorphic Associations
|
107
|
+
#
|
108
|
+
# Polymorphic associations on models are not restricted on what types of models they can be associated with. Rather, they
|
109
|
+
# specify an interface that a +t_has_many+ association must adhere to.
|
110
|
+
#
|
111
|
+
# class Asset < ActiveRecord::Base
|
112
|
+
# include Tenacity
|
113
|
+
# t_belongs_to :attachable, :polymorphic => true
|
114
|
+
# end
|
115
|
+
#
|
116
|
+
# class Post
|
117
|
+
# include MongoMapper::Document
|
118
|
+
# include Tenacity
|
119
|
+
# t_has_many :assets, :as => :attachable # The :as option specifies the polymorphic interface to use.
|
120
|
+
# end
|
121
|
+
#
|
122
|
+
# @asset.attachable = @post
|
123
|
+
#
|
124
|
+
# This works by using a type field/column in addition to a foreign key to specify the associated record. In the Asset example, you'd need
|
125
|
+
# an +attachable_id+ field and an +attachable_type+ string field on your model.
|
126
|
+
#
|
127
|
+
# IDs for polymorphic associations are always stored as strings in the database, since we cannot determine the true type of the
|
128
|
+
# id when the association is defined.
|
129
|
+
#
|
102
130
|
# == Caching
|
103
131
|
#
|
104
132
|
# All of the methods are built on a simple caching principle that will keep the result
|
@@ -139,6 +167,7 @@ module Tenacity
|
|
139
167
|
# can be used to override these defaults.
|
140
168
|
#
|
141
169
|
module ClassMethods
|
170
|
+
attr_reader :_tenacity_associations
|
142
171
|
|
143
172
|
# Specifies a one-to-one association with another class. This method should only be used
|
144
173
|
# if the other class contains the foreign key. If the current class contains the foreign key,
|
@@ -170,14 +199,29 @@ module Tenacity
|
|
170
199
|
# Specify the foreign key used for the association. By default this is guessed to be the name
|
171
200
|
# of this class in lower-case and "_id" suffixed. So a Person class that makes a +t_has_one+ association
|
172
201
|
# will use "person_id" as the default <tt>:foreign_key</tt>.
|
202
|
+
# [:dependent]
|
203
|
+
# If set to <tt>:destroy</tt>, the associated object is deleted when this object is, and all delete
|
204
|
+
# callbacks are called. If set to <tt>:delete</tt>, the associated object is deleted *without*
|
205
|
+
# calling any of its delete callbacks. If set to <tt>:nullify</tt>, the associated object's
|
206
|
+
# foreign key is set to +NULL+.
|
207
|
+
# [:readonly]
|
208
|
+
# If true, the associated object is readonly through the association.
|
209
|
+
# [:autosave]
|
210
|
+
# If true, always save the associated object or destroy it if marked for destruction, when saving the parent object. Off by default.
|
211
|
+
# [:as]
|
212
|
+
# Specifies a polymorphic interface (See <tt>t_belongs_to</tt>).
|
173
213
|
#
|
174
214
|
# Option examples:
|
215
|
+
# t_has_one :credit_card, :dependent => :destroy # destroys the associated credit card
|
216
|
+
# t_has_one :credit_card, :dependent => :nullify # updates the associated records foreign key value to NULL rather than destroying it
|
175
217
|
# t_has_one :project_manager, :class_name => "Person"
|
176
218
|
# t_has_one :project_manager, :foreign_key => "project_id" # within class named SecretProject
|
219
|
+
# t_has_one :boss, :readonly => :true
|
220
|
+
# t_has_one :attachment, :as => :attachable
|
177
221
|
#
|
178
222
|
def t_has_one(name, options={})
|
179
|
-
extend(HasOne::ClassMethods)
|
180
|
-
association =
|
223
|
+
extend(Associations::HasOne::ClassMethods)
|
224
|
+
association = _t_create_association(:t_has_one, name, options)
|
181
225
|
initialize_has_one_association(association)
|
182
226
|
|
183
227
|
define_method(association.name) do |*params|
|
@@ -225,14 +269,29 @@ module Tenacity
|
|
225
269
|
# association will use "person_id" as the default <tt>:foreign_key</tt>. Similarly,
|
226
270
|
# <tt>t_belongs_to :favorite_person, :class_name => "Person"</tt> will use a foreign key
|
227
271
|
# of "favorite_person_id".
|
272
|
+
# [:dependent]
|
273
|
+
# If set to <tt>:destroy</tt>, the associated object is deleted when this object is, calling all delete
|
274
|
+
# callbacks. If set to <tt>:delete</tt>, the associated object is deleted *without* calling any of
|
275
|
+
# its delete callbacks. This option should not be specified when <tt>t_belongs_to</tt> is used in
|
276
|
+
# conjuction with a <tt>t_has_many</tt> relationship on another class because of the potential
|
277
|
+
# to leave orphaned records behind.
|
278
|
+
# [:readonly]
|
279
|
+
# If true, the associated object is readonly through the association.
|
280
|
+
# [:autosave]
|
281
|
+
# If true, always save the associated object or destroy it if marked for destruction, when saving the parent object. Off by default.
|
282
|
+
# [:polymorphic]
|
283
|
+
# Specify this association is a polymorphic association by passing +true+. (*Note*: IDs for polymorphic associations are always
|
284
|
+
# stored as strings in the database.)
|
228
285
|
#
|
229
286
|
# Option examples:
|
230
287
|
# t_belongs_to :project_manager, :class_name => "Person"
|
231
288
|
# t_belongs_to :valid_coupon, :class_name => "Coupon", :foreign_key => "coupon_id"
|
289
|
+
# t_belongs_to :project, :readonly => true
|
290
|
+
# t_belongs_to :attachable, :polymorphic => true
|
232
291
|
#
|
233
292
|
def t_belongs_to(name, options={})
|
234
|
-
extend(BelongsTo::ClassMethods)
|
235
|
-
association =
|
293
|
+
extend(Associations::BelongsTo::ClassMethods)
|
294
|
+
association = _t_create_association(:t_belongs_to, name, options)
|
236
295
|
initialize_belongs_to_association(association)
|
237
296
|
|
238
297
|
define_method(association.name) do |*params|
|
@@ -265,7 +324,9 @@ module Tenacity
|
|
265
324
|
# [collection.concat(other_array)]
|
266
325
|
# Adds the objects in the other array to the collection by setting their foreign keys to the collection's primary key.
|
267
326
|
# [collection.delete(object, ...)]
|
268
|
-
# Removes one or more objects from the collection
|
327
|
+
# Removes one or more objects from the collection by setting their foreign keys to +NULL+.
|
328
|
+
# Objects will be in addition deleted and callbacks called if they're associated with <tt>:dependent => :destroy</tt>,
|
329
|
+
# and deleted and callbacks skipped if they're associated with <tt>:dependent => :delete_all</tt>.
|
269
330
|
# [collection.destroy_all]
|
270
331
|
# Removes all objects from the collection, and deletes them from their respective
|
271
332
|
# database. If the deleted objects have any delete callbacks defined, they will be called.
|
@@ -279,7 +340,10 @@ module Tenacity
|
|
279
340
|
# [collection_singular_ids=ids]
|
280
341
|
# Replace the collection with the objects identified by the primary keys in +ids+.
|
281
342
|
# [collection.clear]
|
282
|
-
# Removes every object from the collection.
|
343
|
+
# Removes every object from the collection. This deletes the associated objects and issues callbacks
|
344
|
+
# if they are associated with <tt>:dependent => :destroy</tt>, deletes them directly from the
|
345
|
+
# database without calling any callbacks if <tt>:dependent => :delete_all</tt>, otherwise sets their
|
346
|
+
# foreign keys to +NULL+.
|
283
347
|
# [collection.empty?]
|
284
348
|
# Returns +true+ if there are no associated objects.
|
285
349
|
# [collection.size]
|
@@ -319,6 +383,12 @@ module Tenacity
|
|
319
383
|
# for objects that store associated ids in an array instaed of a join table (CouchRest,
|
320
384
|
# MongoMapper, etc). <b>WARNING:</b> The name of the association with an "_ids" suffix should
|
321
385
|
# not be used as the property name, since tenacity adds a method with this name to the object.
|
386
|
+
# [:dependent]
|
387
|
+
# If set to <tt>:destroy</tt> all the associated objects are deleted alongside this object
|
388
|
+
# in addition to calling their delete callbacks. If set to <tt>:delete_all</tt> all
|
389
|
+
# associated objects are deleted *without* calling their delete callbacks. If set to
|
390
|
+
# <tt>:nullify</tt> all associated objects' foreign keys are set to +NULL+ *without* calling
|
391
|
+
# their save backs.
|
322
392
|
# [:join_table]
|
323
393
|
# Specify the name of the join table if the default based on lexical order isn't what you want.
|
324
394
|
# This option is only valid if one of the models in the association is backed by a relational
|
@@ -336,6 +406,18 @@ module Tenacity
|
|
336
406
|
# "_id" suffixed. So if a Person class makes a +t_has_many+ association to Project, the
|
337
407
|
# association will use "person_id" as the default <tt>:association_key</tt>. This option is
|
338
408
|
# only valid if one of the associated objects is backed by a relational database.
|
409
|
+
# [:readonly]
|
410
|
+
# If true, all the associated objects are readonly through the association.
|
411
|
+
# [:limit]
|
412
|
+
# An integer determining the limit on the number of rows that should be returned. Results
|
413
|
+
# are ordered by a string representation of the id.
|
414
|
+
# [:offset]
|
415
|
+
# An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
|
416
|
+
# Results are ordered by a string representation of the id.
|
417
|
+
# [:autosave]
|
418
|
+
# If true, always save any loaded members and destroy members marked for destruction, when saving the parent object. Off by default.
|
419
|
+
# [:as]
|
420
|
+
# Specifies a polymorphic interface (See <tt>t_belongs_to</tt>).
|
339
421
|
#
|
340
422
|
# Option examples:
|
341
423
|
# t_has_many :products, :class_name => "SpecialProduct"
|
@@ -344,10 +426,13 @@ module Tenacity
|
|
344
426
|
# t_has_many :managers, :join_table => "project_managers_and_projects"
|
345
427
|
# t_has_many :managers, :join_table => "project_managers_and_projects",
|
346
428
|
# :association_foreign_key => "mgr_id", :association_key => "proj_id"
|
429
|
+
# t_has_many :tasks, :dependent => :destroy
|
430
|
+
# t_has_many :reports, :readonly => true
|
431
|
+
# t_has_many :tags, :as => :taggable
|
347
432
|
#
|
348
433
|
def t_has_many(name, options={})
|
349
|
-
extend(HasMany::ClassMethods)
|
350
|
-
association =
|
434
|
+
extend(Associations::HasMany::ClassMethods)
|
435
|
+
association = _t_create_association(:t_has_many, name, options)
|
351
436
|
initialize_has_many_association(association)
|
352
437
|
|
353
438
|
define_method(association.name) do |*params|
|
@@ -357,7 +442,9 @@ module Tenacity
|
|
357
442
|
end
|
358
443
|
|
359
444
|
define_method("#{association.name}=") do |associates|
|
360
|
-
set_associate(association, associates)
|
445
|
+
set_associate(association, associates) do
|
446
|
+
set_has_many_associates(association, associates)
|
447
|
+
end
|
361
448
|
end
|
362
449
|
|
363
450
|
define_method("#{ActiveSupport::Inflector.singularize(association.name)}_ids") do
|
@@ -375,6 +462,27 @@ module Tenacity
|
|
375
462
|
end
|
376
463
|
end
|
377
464
|
|
465
|
+
def _t_serialize(object_id, association=nil) #:nodoc:
|
466
|
+
if association && association.polymorphic?
|
467
|
+
object_id.to_s
|
468
|
+
elsif object_id.nil?
|
469
|
+
nil
|
470
|
+
elsif [Fixnum].include?(object_id.class)
|
471
|
+
object_id
|
472
|
+
else
|
473
|
+
object_id.to_s
|
474
|
+
end
|
475
|
+
end
|
476
|
+
|
477
|
+
private
|
478
|
+
|
479
|
+
def _t_create_association(type, name, options) #:nococ:
|
480
|
+
association = Association.new(type, name, self, options)
|
481
|
+
@_tenacity_associations ||= []
|
482
|
+
@_tenacity_associations << association
|
483
|
+
association
|
484
|
+
end
|
485
|
+
|
378
486
|
end
|
379
487
|
end
|
380
488
|
|
@@ -5,8 +5,26 @@ module Tenacity
|
|
5
5
|
"@_t_" + association.name.to_s
|
6
6
|
end
|
7
7
|
|
8
|
+
def _t_save_autosave_associations
|
9
|
+
self.class._tenacity_associations.each do |association|
|
10
|
+
if association.autosave == true
|
11
|
+
if association.type == :t_has_one || association.type == :t_belongs_to
|
12
|
+
associate = instance_variable_get(_t_ivar_name(association))
|
13
|
+
autosave_save_or_destroy(associate) unless associate.nil?
|
14
|
+
elsif association.type == :t_has_many
|
15
|
+
associates = instance_variable_get(_t_ivar_name(association))
|
16
|
+
associates.each { |associate| autosave_save_or_destroy(associate) } unless associates.nil?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
8
22
|
private
|
9
23
|
|
24
|
+
def autosave_save_or_destroy(associate)
|
25
|
+
associate.marked_for_destruction? ? associate.destroy : associate.save
|
26
|
+
end
|
27
|
+
|
10
28
|
def get_associate(association, params)
|
11
29
|
_t_reload unless id.nil?
|
12
30
|
force_reload = params.first unless params.empty?
|
@@ -19,12 +37,31 @@ module Tenacity
|
|
19
37
|
end
|
20
38
|
|
21
39
|
def set_associate(association, associate)
|
22
|
-
yield if block_given?
|
23
|
-
instance_variable_set _t_ivar_name(association), associate
|
40
|
+
associate = yield if block_given?
|
41
|
+
instance_variable_set _t_ivar_name(association), create_proxy(associate, association)
|
24
42
|
end
|
25
43
|
|
26
44
|
def create_proxy(value, association)
|
27
|
-
value.respond_to?(:
|
45
|
+
return value if value.respond_to?(:proxy_respond_to?)
|
46
|
+
|
47
|
+
if multiple_associates?(association, value)
|
48
|
+
value.map! { |v| create_associate_proxy_for(v, association) }
|
49
|
+
AssociatesProxy.new(self, value, association)
|
50
|
+
else
|
51
|
+
create_associate_proxy_for(value, association)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def create_associate_proxy_for(value, association)
|
56
|
+
value.nil? ? nil : AssociateProxy.new(value, association)
|
57
|
+
end
|
58
|
+
|
59
|
+
def multiple_associates?(association, value)
|
60
|
+
association.type == :t_has_many && value.is_a?(Enumerable)
|
61
|
+
end
|
62
|
+
|
63
|
+
def _t_serialize(object, association=nil)
|
64
|
+
self.class._t_serialize(object, association)
|
28
65
|
end
|
29
66
|
|
30
67
|
end
|
@@ -1,116 +1,135 @@
|
|
1
1
|
module Tenacity
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
def self.setup(model)
|
46
|
-
require 'active_record'
|
47
|
-
if model.ancestors.include?(::ActiveRecord::Base)
|
48
|
-
model.send :include, ActiveRecord::InstanceMethods
|
49
|
-
model.extend ActiveRecord::ClassMethods
|
50
|
-
end
|
51
|
-
rescue LoadError
|
52
|
-
# ActiveRecord not available
|
53
|
-
end
|
2
|
+
module OrmExt
|
3
|
+
# Tenacity relationships on ActiveRecord objects require that certain columns
|
4
|
+
# exist on the associated table, and that join tables exist for one-to-many
|
5
|
+
# relationships. Take the following class for example:
|
6
|
+
#
|
7
|
+
# class Car < ActiveRecord::Base
|
8
|
+
# include Tenacity
|
9
|
+
#
|
10
|
+
# t_has_many :wheels
|
11
|
+
# t_has_one :dashboard
|
12
|
+
# t_belongs_to :driver
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
#
|
16
|
+
# == t_belongs_to
|
17
|
+
#
|
18
|
+
# The +t_belongs_to+ association requires that a property exist in the table
|
19
|
+
# to hold the id of the assoicated object.
|
20
|
+
#
|
21
|
+
# create_table :cars do |t|
|
22
|
+
# t.string :driver_id
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
#
|
26
|
+
# == t_has_one
|
27
|
+
#
|
28
|
+
# The +t_has_one+ association requires no special column in the table, since
|
29
|
+
# the associated object holds the foreign key.
|
30
|
+
#
|
31
|
+
#
|
32
|
+
# == t_has_many
|
33
|
+
#
|
34
|
+
# The +t_has_many+ association requires that a join table exist to store the
|
35
|
+
# associations. The name of the join table follows ActiveRecord conventions.
|
36
|
+
# The name of the join table in this example would be cars_wheels, since cars
|
37
|
+
# comes before wheels when shorted alphabetically.
|
38
|
+
#
|
39
|
+
# create_table :cars_wheels do |t|
|
40
|
+
# t.integer :car_id
|
41
|
+
# t.string :wheel_id
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
module ActiveRecord
|
54
45
|
|
55
|
-
|
56
|
-
|
57
|
-
|
46
|
+
def self.setup(model)
|
47
|
+
require 'active_record'
|
48
|
+
if model.ancestors.include?(::ActiveRecord::Base)
|
49
|
+
model.send :include, ActiveRecord::InstanceMethods
|
50
|
+
model.extend ActiveRecord::ClassMethods
|
51
|
+
end
|
52
|
+
rescue LoadError
|
53
|
+
# ActiveRecord not available
|
58
54
|
end
|
59
55
|
|
60
|
-
|
61
|
-
|
62
|
-
find(:all, :conditions => ["id in (?)", ids])
|
63
|
-
end
|
56
|
+
module ClassMethods #:nodoc:
|
57
|
+
include Tenacity::OrmExt::Helpers
|
64
58
|
|
65
|
-
|
66
|
-
|
67
|
-
|
59
|
+
def _t_id_type
|
60
|
+
Integer
|
61
|
+
end
|
68
62
|
|
69
|
-
|
70
|
-
|
71
|
-
|
63
|
+
def _t_find(id)
|
64
|
+
find_by_id(_t_serialize(id))
|
65
|
+
end
|
72
66
|
|
73
|
-
|
74
|
-
|
75
|
-
|
67
|
+
def _t_find_bulk(ids)
|
68
|
+
return [] if ids.nil? || ids.empty?
|
69
|
+
find(:all, :conditions => ["id in (?)", _t_serialize_ids(ids)])
|
70
|
+
end
|
76
71
|
|
77
|
-
|
78
|
-
|
79
|
-
|
72
|
+
def _t_find_first_by_associate(property, id)
|
73
|
+
find(:first, :conditions => ["#{property} = ?", _t_serialize(id)])
|
74
|
+
end
|
80
75
|
|
81
|
-
|
82
|
-
|
83
|
-
destroy_all(["id in (?)", ids])
|
84
|
-
else
|
85
|
-
delete_all(["id in (?)", ids])
|
76
|
+
def _t_find_all_by_associate(property, id)
|
77
|
+
find(:all, :conditions => ["#{property} = ?", _t_serialize(id)])
|
86
78
|
end
|
87
|
-
end
|
88
|
-
end
|
89
79
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
end
|
80
|
+
def _t_initialize_has_one_association(association)
|
81
|
+
after_destroy { |record| record._t_cleanup_has_one_association(association) }
|
82
|
+
end
|
94
83
|
|
95
|
-
|
96
|
-
|
97
|
-
|
84
|
+
def _t_initialize_tenacity
|
85
|
+
after_save { |record| record._t_save_autosave_associations }
|
86
|
+
end
|
87
|
+
|
88
|
+
def _t_initialize_has_many_association(association)
|
89
|
+
after_save { |record| record.class._t_save_associates(record, association) }
|
90
|
+
after_destroy { |record| record._t_cleanup_has_many_association(association) }
|
91
|
+
end
|
92
|
+
|
93
|
+
def _t_initialize_belongs_to_association(association)
|
94
|
+
after_destroy { |record| record._t_cleanup_belongs_to_association(association) }
|
95
|
+
end
|
98
96
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
97
|
+
def _t_delete(ids, run_callbacks=true)
|
98
|
+
if run_callbacks
|
99
|
+
destroy_all(["id in (?)", _t_serialize_ids(ids)])
|
100
|
+
else
|
101
|
+
delete_all(["id in (?)", _t_serialize_ids(ids)])
|
104
102
|
end
|
105
103
|
end
|
106
104
|
end
|
107
105
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
106
|
+
module InstanceMethods #:nodoc:
|
107
|
+
include Tenacity::OrmExt::Helpers
|
108
|
+
|
109
|
+
def _t_reload
|
110
|
+
reload
|
111
|
+
end
|
112
|
+
|
113
|
+
def _t_clear_associates(association)
|
114
|
+
self.connection.execute("delete from #{association.join_table} where #{association.association_key} = #{_t_serialize_id_for_sql(self.id)}")
|
115
|
+
end
|
116
|
+
|
117
|
+
def _t_associate_many(association, associate_ids)
|
118
|
+
self.transaction do
|
119
|
+
_t_clear_associates(association)
|
120
|
+
associate_ids.each do |associate_id|
|
121
|
+
self.connection.execute("insert into #{association.join_table} (#{association.association_key}, #{association.association_foreign_key}) values (#{_t_serialize_id_for_sql(self.id)}, #{_t_serialize_id_for_sql(associate_id)})")
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def _t_get_associate_ids(association)
|
127
|
+
return [] if self.id.nil?
|
128
|
+
rows = self.connection.execute("select #{association.association_foreign_key} from #{association.join_table} where #{association.association_key} = #{_t_serialize_id_for_sql(self.id)}")
|
129
|
+
ids = []; rows.each { |r| ids << r[0] }; ids
|
130
|
+
end
|
112
131
|
end
|
113
|
-
end
|
114
132
|
|
133
|
+
end
|
115
134
|
end
|
116
135
|
end
|