tenacity 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|