tenacity 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data/EXTEND.rdoc +9 -13
  2. data/Rakefile +6 -0
  3. data/history.txt +17 -0
  4. data/lib/tenacity.rb +13 -9
  5. data/lib/tenacity/associate_proxy.rb +56 -0
  6. data/lib/tenacity/associates_proxy.rb +5 -3
  7. data/lib/tenacity/association.rb +81 -9
  8. data/lib/tenacity/associations/belongs_to.rb +28 -16
  9. data/lib/tenacity/associations/has_many.rb +113 -53
  10. data/lib/tenacity/associations/has_one.rb +33 -14
  11. data/lib/tenacity/class_methods.rb +117 -9
  12. data/lib/tenacity/errors.rb +9 -0
  13. data/lib/tenacity/instance_methods.rb +40 -3
  14. data/lib/tenacity/orm_ext/activerecord.rb +114 -95
  15. data/lib/tenacity/orm_ext/couchrest.rb +132 -113
  16. data/lib/tenacity/orm_ext/datamapper.rb +138 -112
  17. data/lib/tenacity/orm_ext/helpers.rb +36 -0
  18. data/lib/tenacity/orm_ext/mongo_mapper.rb +102 -84
  19. data/lib/tenacity/orm_ext/mongoid.rb +106 -87
  20. data/lib/tenacity/orm_ext/sequel.rb +137 -110
  21. data/lib/tenacity/version.rb +1 -1
  22. data/tenacity.gemspec +2 -1
  23. data/test/association_features/belongs_to_test.rb +46 -1
  24. data/test/association_features/has_many_test.rb +187 -2
  25. data/test/association_features/has_one_test.rb +57 -2
  26. data/test/associations/belongs_to_test.rb +41 -7
  27. data/test/associations/has_many_test.rb +59 -10
  28. data/test/associations/has_one_test.rb +57 -2
  29. data/test/fixtures/active_record_car.rb +5 -4
  30. data/test/fixtures/active_record_climate_control_unit.rb +1 -0
  31. data/test/fixtures/active_record_engine.rb +1 -0
  32. data/test/fixtures/active_record_has_many_target.rb +7 -0
  33. data/test/fixtures/active_record_has_one_target.rb +7 -0
  34. data/test/fixtures/active_record_object.rb +19 -0
  35. data/test/fixtures/couch_rest_has_many_target.rb +7 -0
  36. data/test/fixtures/couch_rest_has_one_target.rb +7 -0
  37. data/test/fixtures/couch_rest_object.rb +14 -0
  38. data/test/fixtures/data_mapper_has_many_target.rb +23 -3
  39. data/test/fixtures/data_mapper_has_one_target.rb +23 -3
  40. data/test/fixtures/data_mapper_object.rb +14 -0
  41. data/test/fixtures/mongo_mapper_air_filter.rb +6 -0
  42. data/test/fixtures/mongo_mapper_alternator.rb +7 -0
  43. data/test/fixtures/mongo_mapper_autosave_false_has_many_target.rb +8 -0
  44. data/test/fixtures/mongo_mapper_autosave_false_has_one_target.rb +8 -0
  45. data/test/fixtures/mongo_mapper_autosave_true_has_many_target.rb +8 -0
  46. data/test/fixtures/mongo_mapper_autosave_true_has_one_target.rb +8 -0
  47. data/test/fixtures/mongo_mapper_circuit_board.rb +6 -0
  48. data/test/fixtures/mongo_mapper_dashboard.rb +5 -2
  49. data/test/fixtures/mongo_mapper_has_many_target.rb +7 -0
  50. data/test/fixtures/mongo_mapper_has_one_target.rb +7 -0
  51. data/test/fixtures/mongo_mapper_object.rb +14 -0
  52. data/test/fixtures/mongo_mapper_wheel.rb +2 -0
  53. data/test/fixtures/mongo_mapper_windows.rb +6 -0
  54. data/test/fixtures/mongoid_has_many_target.rb +7 -0
  55. data/test/fixtures/mongoid_has_one_target.rb +7 -0
  56. data/test/fixtures/mongoid_object.rb +14 -0
  57. data/test/fixtures/sequel_has_many_target.rb +7 -0
  58. data/test/fixtures/sequel_has_one_target.rb +7 -0
  59. data/test/fixtures/sequel_object.rb +14 -0
  60. data/test/helpers/active_record_test_helper.rb +87 -8
  61. data/test/helpers/couch_rest_test_helper.rb +7 -0
  62. data/test/helpers/data_mapper_test_helper.rb +41 -3
  63. data/test/helpers/sequel_test_helper.rb +65 -9
  64. data/test/orm_ext/activerecord_test.rb +1 -1
  65. data/test/orm_ext/datamapper_test.rb +33 -24
  66. data/test/orm_ext/mongo_mapper_test.rb +6 -6
  67. data/test/orm_ext/mongoid_test.rb +6 -6
  68. data/test/orm_ext/sequel_test.rb +1 -1
  69. data/test/test_helper.rb +18 -9
  70. metadata +119 -55
data/EXTEND.rdoc CHANGED
@@ -5,14 +5,6 @@ only, so as long as they have been implemented and are available on the model
5
5
  object, Tenacity will be able to manage the object's relationships.
6
6
 
7
7
 
8
- == A note about IDs
9
-
10
- An ID can be an integer, a string, or an object, depending on the database
11
- and database client you are using. To be as compatible as possible, Tenacity
12
- treats all IDs as strings. So, all database client extensions should accept
13
- strings for IDs as input parameters, and return strings for IDs.
14
-
15
-
16
8
  == The Association Class
17
9
 
18
10
  A few of the methods take an association as a parameter. This association
@@ -56,21 +48,25 @@ delete callback meethods are not run. Return nothing.
56
48
 
57
49
  Perform any database client specific initialization necessary to support a has
58
50
  many association. This could include defining properties, or callback methods,
59
- on the object. This method is optional, and does not need to be defined.
51
+ on the object. This method must also call
52
+ _t_cleanup_has_many_association(association) to cleanup the association
53
+ when the after the object has been deleted.
60
54
 
61
55
  _t_initialize_belongs_to_association(association)
62
56
 
63
57
  Perform any database client specific initialization necessary to support a
64
58
  belongs to association. This could include defining properties, or callback
65
- methods, on the object. This method is optional, and does not need to be
66
- defined.
59
+ methods, on the object. This method must also call
60
+ _t_cleanup_belongs_to_association(association) to cleanup the association
61
+ when the after the object has been deleted.
67
62
 
68
63
  _t_initialize_has_one_association(association)
69
64
 
70
65
  Perform any database client specific initialization necessary to support a has one
71
66
  association. This could include defining properties, or callback methods,
72
- on the object. This method is optional, and does not need to be defined.
73
-
67
+ on the object. This method must also call
68
+ _t_cleanup_has_one_association(association) to cleanup the association
69
+ when the after the object has been deleted.
74
70
 
75
71
  == Instance Methods
76
72
 
data/Rakefile CHANGED
@@ -25,6 +25,12 @@ Rake::TestTask.new(:test) do |test|
25
25
  test.verbose = true
26
26
  end
27
27
 
28
+ desc 'Run an abbreviated version of the test suite'
29
+ task :quick_test do
30
+ ENV['QUICK'] = 'true'
31
+ Rake::Task["test"].invoke
32
+ end
33
+
28
34
  Rcov::RcovTask.new do |test|
29
35
  test.libs << 'test' << 'test/fixtures'
30
36
  test.pattern = 'test/**/*_test.rb'
data/history.txt CHANGED
@@ -1,3 +1,20 @@
1
+ == 0.4.0
2
+
3
+ * Major enhancements
4
+
5
+ * Tenacity will no longer convert foreign keys to strings before storing them
6
+ in the database. Instead, the ID will be stored without any modification
7
+ (when possible).
8
+
9
+ * Minor enhancements
10
+
11
+ * Added support for the :dependent option to all associations
12
+ * Added support for the :readonly option to all associations
13
+ * Added support for the :limit option to the t_has_many association
14
+ * Added support for the :offset option to the t_has_many association
15
+ * Added support for the :autosave option to all associations
16
+ * Added support for polymorphic associations to all associations
17
+
1
18
  == 0.3.0
2
19
 
3
20
  * Major enhancements
data/lib/tenacity.rb CHANGED
@@ -1,12 +1,15 @@
1
1
  require File.join('active_support', 'inflector')
2
2
 
3
+ require File.join(File.dirname(__FILE__), 'tenacity', 'associate_proxy')
3
4
  require File.join(File.dirname(__FILE__), 'tenacity', 'associates_proxy')
4
5
  require File.join(File.dirname(__FILE__), 'tenacity', 'association')
5
6
  require File.join(File.dirname(__FILE__), 'tenacity', 'class_methods')
7
+ require File.join(File.dirname(__FILE__), 'tenacity', 'errors')
6
8
  require File.join(File.dirname(__FILE__), 'tenacity', 'instance_methods')
7
9
  require File.join(File.dirname(__FILE__), 'tenacity', 'associations', 'belongs_to')
8
10
  require File.join(File.dirname(__FILE__), 'tenacity', 'associations', 'has_many')
9
11
  require File.join(File.dirname(__FILE__), 'tenacity', 'associations', 'has_one')
12
+ require File.join(File.dirname(__FILE__), 'tenacity', 'orm_ext', 'helpers')
10
13
  require File.join(File.dirname(__FILE__), 'tenacity', 'orm_ext', 'activerecord')
11
14
  require File.join(File.dirname(__FILE__), 'tenacity', 'orm_ext', 'couchrest')
12
15
  require File.join(File.dirname(__FILE__), 'tenacity', 'orm_ext', 'datamapper')
@@ -17,20 +20,21 @@ require File.join(File.dirname(__FILE__), 'tenacity', 'orm_ext', 'sequel')
17
20
  module Tenacity #:nodoc:
18
21
  include InstanceMethods
19
22
 
20
- include BelongsTo
21
- include HasMany
22
- include HasOne
23
+ include Associations::BelongsTo
24
+ include Associations::HasMany
25
+ include Associations::HasOne
23
26
 
24
27
  def self.included(model)
25
- ActiveRecord.setup(model)
26
- CouchRest.setup(model)
27
- DataMapper.setup(model)
28
- MongoMapper.setup(model)
29
- Mongoid.setup(model)
30
- Sequel.setup(model)
28
+ OrmExt::ActiveRecord.setup(model)
29
+ OrmExt::CouchRest.setup(model)
30
+ OrmExt::DataMapper.setup(model)
31
+ OrmExt::MongoMapper.setup(model)
32
+ OrmExt::Mongoid.setup(model)
33
+ OrmExt::Sequel.setup(model)
31
34
 
32
35
  raise "Tenacity does not support the database client used by #{model}" unless model.respond_to?(:_t_find)
33
36
  model.extend(ClassMethods)
37
+ model._t_initialize_tenacity
34
38
  end
35
39
  end
36
40
 
@@ -0,0 +1,56 @@
1
+ module Tenacity
2
+ class AssociateProxy #:nodoc:
3
+ alias_method :proxy_respond_to?, :respond_to?
4
+ instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/ }
5
+
6
+ def initialize(target, association)
7
+ raise "Cannot create a Tenacity::AssociateProxy with a nil target" if target.nil?
8
+ @target = target
9
+ @association = association
10
+ @marked_for_destruction = false
11
+ end
12
+
13
+ def respond_to?(*args)
14
+ proxy_respond_to?(*args) || @target.respond_to?(*args)
15
+ end
16
+
17
+ # Explicitly proxy === because the instance method removal above doesn't catch it.
18
+ def ===(other)
19
+ other === @target
20
+ end
21
+
22
+ def inspect
23
+ @target.inspect
24
+ end
25
+
26
+ def save
27
+ if @association.readonly?
28
+ raise ReadOnlyError
29
+ else
30
+ @target.save
31
+ end
32
+ end
33
+
34
+ def association_target
35
+ @target
36
+ end
37
+
38
+ def mark_for_destruction
39
+ @marked_for_destruction = true
40
+ end
41
+
42
+ def marked_for_destruction?
43
+ @marked_for_destruction
44
+ end
45
+
46
+ private
47
+
48
+ def method_missing(method, *args)
49
+ if block_given?
50
+ @target.send(method, *args) { |*block_args| yield(*block_args) }
51
+ else
52
+ @target.send(method, *args)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -20,17 +20,19 @@ module Tenacity
20
20
 
21
21
  def <<(object)
22
22
  object.save unless @parent.id.nil?
23
- @target << object
23
+ @target << AssociateProxy.new(object, @association)
24
24
  end
25
25
 
26
26
  def push(*objects)
27
27
  objects.each { |object| object.save } unless @parent.id.nil?
28
- @target.push(*objects)
28
+ proxies = objects.map { |object| AssociateProxy.new(object, @association) }
29
+ @target.push(*proxies)
29
30
  end
30
31
 
31
32
  def concat(objects)
32
33
  objects.each { |object| object.save } unless @parent.id.nil?
33
- @target.concat(objects)
34
+ proxies = objects.map { |object| AssociateProxy.new(object, @association) }
35
+ @target.concat(proxies)
34
36
  end
35
37
 
36
38
  def destroy_all
@@ -8,15 +8,33 @@ module Tenacity
8
8
  # Type type of the association (<tt>:t_has_one</tt>, <tt>:t_has_many</tt>, or <tt>:t_belongs_to</tt>)
9
9
  attr_reader :type
10
10
 
11
- # The name of the association
12
- attr_reader :name
13
-
14
11
  # The class defining the association
15
12
  attr_reader :source
16
13
 
17
14
  # The name of the associated class
18
15
  attr_reader :class_name
19
16
 
17
+ # What happens to the associated object when the object is deleted
18
+ attr_reader :dependent
19
+
20
+ # Are the associated objects read only?
21
+ attr_reader :readonly
22
+
23
+ # The limit on the number of results to be returned.
24
+ attr_reader :limit
25
+
26
+ # The offset from where the results should be fetched.
27
+ attr_reader :offset
28
+
29
+ # Should the associated object be saved when the parent object is saved?
30
+ attr_reader :autosave
31
+
32
+ # The interface this association is reffered to as
33
+ attr_reader :as
34
+
35
+ # Is this association a polymorphic association?
36
+ attr_reader :polymorphic
37
+
20
38
  def initialize(type, name, source, options={})
21
39
  @type = type
22
40
  @name = name
@@ -33,6 +51,13 @@ module Tenacity
33
51
  @join_table = options[:join_table]
34
52
  @association_key = options[:association_key]
35
53
  @association_foreign_key = options[:association_foreign_key]
54
+ @dependent = options[:dependent]
55
+ @readonly = options[:readonly]
56
+ @limit = options[:limit]
57
+ @offset = options[:offset]
58
+ @autosave = options[:autosave]
59
+ @polymorphic = options[:polymorphic]
60
+ @as = options[:as]
36
61
 
37
62
  if @foreign_keys_property
38
63
  if @foreign_keys_property.to_s == ActiveSupport::Inflector.singularize(name) + "_ids"
@@ -41,9 +66,18 @@ module Tenacity
41
66
  end
42
67
  end
43
68
 
69
+ # The name of the association
70
+ def name
71
+ @as.nil? ? @name : @as
72
+ end
73
+
44
74
  # Get the associated class
45
- def associate_class
46
- @clazz ||= Kernel.const_get(@class_name)
75
+ def associate_class(object=nil)
76
+ if @type == :t_belongs_to && polymorphic?
77
+ Kernel.const_get(object.send(polymorphic_type))
78
+ else
79
+ @clazz ||= Kernel.const_get(@class_name)
80
+ end
47
81
  end
48
82
 
49
83
  # Get the foreign key used by this association. <tt>t_has_one</tt> and
@@ -52,10 +86,9 @@ module Tenacity
52
86
  def foreign_key(clazz=nil)
53
87
  @foreign_key || begin
54
88
  if @type == :t_belongs_to
55
- @class_name.underscore + "_id"
89
+ belongs_to_foreign_key
56
90
  elsif @type == :t_has_one || @type == :t_has_many
57
- raise "The class of the associate must be provided in order to determine the name of the foreign key" if clazz.nil?
58
- "#{ActiveSupport::Inflector.underscore(clazz)}_id"
91
+ has_x_foreign_key(clazz)
59
92
  end
60
93
  end
61
94
  end
@@ -68,7 +101,14 @@ module Tenacity
68
101
  # Get the name of the join table used by this association
69
102
  def join_table
70
103
  table_name = fetch_table_name
71
- @join_table || (name.to_s < table_name ? "#{name}_#{table_name}" : "#{table_name}_#{name}")
104
+
105
+ if @type == :t_has_many && polymorphic?
106
+ association_name_for_join_table = name.to_s.pluralize
107
+ else
108
+ association_name_for_join_table = name
109
+ end
110
+
111
+ @join_table || (name.to_s < table_name ? "#{association_name_for_join_table}_#{table_name}" : "#{table_name}_#{association_name_for_join_table}")
72
112
  end
73
113
 
74
114
  # Get the name of the column in the join table that represents this object
@@ -82,6 +122,21 @@ module Tenacity
82
122
  @association_foreign_key || name.to_s.singularize + '_id'
83
123
  end
84
124
 
125
+ # Are the associated objects read only?
126
+ def readonly?
127
+ @readonly == true
128
+ end
129
+
130
+ # Is this association a polymorphic association?
131
+ def polymorphic?
132
+ @polymorphic == true || !@as.nil?
133
+ end
134
+
135
+ # The name of the property that stores the polymorphic type (for polymorphic associations)
136
+ def polymorphic_type
137
+ (name.to_s + "_type").to_sym
138
+ end
139
+
85
140
  private
86
141
 
87
142
  def fetch_table_name
@@ -91,5 +146,22 @@ module Tenacity
91
146
  "#{ActiveSupport::Inflector.underscore(@source)}s"
92
147
  end
93
148
  end
149
+
150
+ def belongs_to_foreign_key
151
+ if polymorphic?
152
+ (name.to_s + "_id").to_sym
153
+ else
154
+ @class_name.underscore + "_id"
155
+ end
156
+ end
157
+
158
+ def has_x_foreign_key(clazz)
159
+ raise "The class of the associate must be provided in order to determine the name of the foreign key" if clazz.nil?
160
+ if polymorphic?
161
+ (@as.to_s + "_id").to_sym
162
+ else
163
+ "#{ActiveSupport::Inflector.underscore(clazz)}_id"
164
+ end
165
+ end
94
166
  end
95
167
  end
@@ -1,28 +1,40 @@
1
1
  module Tenacity
2
- module BelongsTo #:nodoc:
2
+ module Associations
3
+ module BelongsTo #:nodoc:
3
4
 
4
- private
5
+ def _t_cleanup_belongs_to_association(association)
6
+ associate_id = self.send(association.foreign_key)
7
+ if associate_id != nil && associate_id.to_s.strip != ''
8
+ if association.dependent == :destroy
9
+ association.associate_class._t_delete(associate_id)
10
+ elsif association.dependent == :delete
11
+ association.associate_class._t_delete(associate_id, false)
12
+ end
13
+ end
14
+ end
5
15
 
6
- def belongs_to_associate(association)
7
- associate_id = self.send(association.foreign_key)
8
- clazz = association.associate_class
9
- clazz._t_find(associate_id)
10
- end
16
+ private
11
17
 
12
- def set_belongs_to_associate(association, associate)
13
- self.send "#{association.foreign_key}=", associate.id.to_s
14
- end
18
+ def belongs_to_associate(association)
19
+ associate_id = self.send(association.foreign_key)
20
+ clazz = association.associate_class(self)
21
+ associate = clazz._t_find(associate_id)
22
+ associate
23
+ end
15
24
 
16
- module ClassMethods #:nodoc:
17
- def initialize_belongs_to_association(association)
18
- _t_initialize_belongs_to_association(association) if self.respond_to?(:_t_initialize_belongs_to_association)
25
+ def set_belongs_to_associate(association, associate)
26
+ self.send "#{association.foreign_key}=", _t_serialize(associate.id)
27
+ self.send "#{association.polymorphic_type}=", associate.class.to_s if association.polymorphic?
28
+ associate
19
29
  end
20
30
 
21
- def _t_stringify_belongs_to_value(record, association)
22
- record.send "#{association.foreign_key}=", record.send(association.foreign_key).to_s
31
+ module ClassMethods #:nodoc:
32
+ def initialize_belongs_to_association(association)
33
+ _t_initialize_belongs_to_association(association) if self.respond_to?(:_t_initialize_belongs_to_association)
34
+ end
23
35
  end
24
- end
25
36
 
37
+ end
26
38
  end
27
39
  end
28
40
 
@@ -1,80 +1,140 @@
1
1
  module Tenacity
2
- module HasMany #:nodoc:
2
+ module Associations
3
+ module HasMany #:nodoc:
3
4
 
4
- def _t_remove_associates(association)
5
- instance_variable_set _t_ivar_name(association), []
6
- _t_clear_associates(association)
7
- end
5
+ def _t_remove_associates(association)
6
+ instance_variable_set _t_ivar_name(association), []
7
+ _t_clear_associates(association)
8
+ end
8
9
 
9
- private
10
+ def _t_cleanup_has_many_association(association)
11
+ associates = has_many_associates(association)
12
+ unless associates.nil? || associates.empty?
13
+ if association.dependent == :destroy
14
+ associates.each { |associate| association.associate_class._t_delete([_t_serialize(associate.id)]) }
15
+ elsif association.dependent == :delete_all
16
+ associates.each { |associate| association.associate_class._t_delete([_t_serialize(associate.id)], false) }
17
+ elsif association.dependent == :nullify
18
+ associates.each do |associate|
19
+ associate.send "#{association.foreign_key(self.class)}=", nil
20
+ associate.save
21
+ end
22
+ end
23
+ end
24
+ end
10
25
 
11
- def has_many_associates(association)
12
- ids = _t_get_associate_ids(association)
13
- clazz = association.associate_class
14
- clazz._t_find_bulk(ids)
15
- end
26
+ private
16
27
 
17
- def has_many_associate_ids(association)
18
- _t_get_associate_ids(association)
19
- end
28
+ def has_many_associates(association)
29
+ ids = _t_get_associate_ids(association)
30
+ pruned_ids = prune_associate_ids(association, ids)
31
+ clazz = association.associate_class
32
+ clazz._t_find_bulk(pruned_ids)
33
+ end
20
34
 
21
- def set_has_many_associate_ids(association, associate_ids)
22
- clazz = association.associate_class
23
- instance_variable_set _t_ivar_name(association), clazz._t_find_bulk(associate_ids)
24
- end
35
+ def set_has_many_associates(association, associates)
36
+ associates.map { |associate| AssociateProxy.new(associate, association) }
37
+ end
25
38
 
26
- def save_without_callback
27
- @perform_save_associates_callback = false
28
- save
29
- ensure
30
- @perform_save_associates_callback = true
31
- end
39
+ def has_many_associate_ids(association)
40
+ ids = _t_get_associate_ids(association)
41
+ prune_associate_ids(association, ids)
42
+ end
43
+
44
+ def set_has_many_associate_ids(association, associate_ids)
45
+ clazz = association.associate_class
46
+ instance_variable_set _t_ivar_name(association), clazz._t_find_bulk(associate_ids)
47
+ end
48
+
49
+ def save_without_callback
50
+ @perform_save_associates_callback = false
51
+ save
52
+ ensure
53
+ @perform_save_associates_callback = true
54
+ end
32
55
 
33
- module ClassMethods #:nodoc:
34
- def initialize_has_many_association(association)
35
- _t_initialize_has_many_association(association) if self.respond_to?(:_t_initialize_has_many_association)
56
+ def prune_associate_ids(association, associate_ids)
57
+ if association.limit || association.offset
58
+ sorted_ids = associate_ids.sort { |a,b| a <=> b }
36
59
 
37
- attr_accessor :perform_save_associates_callback
60
+ limit = association.limit || associate_ids.size
61
+ offset = association.offset || 0
62
+ sorted_ids[offset...(offset + limit)]
63
+ else
64
+ associate_ids
65
+ end
38
66
  end
39
67
 
40
- def _t_save_associates(record, association)
41
- return if record.perform_save_associates_callback == false
68
+ module ClassMethods #:nodoc:
69
+ def initialize_has_many_association(association)
70
+ _t_initialize_has_many_association(association) if self.respond_to?(:_t_initialize_has_many_association)
71
+
72
+ attr_accessor :perform_save_associates_callback
73
+ end
74
+
75
+ def _t_save_associates(record, association)
76
+ return if record.perform_save_associates_callback == false
77
+
78
+ old_associates = get_current_associates(record, association)
79
+
80
+ # Some ORM libraries (CouchRest, ActiveRecord, etc) return a proxy in
81
+ # place of the associated objects. The actual associated objects
82
+ # will be fetched the first time they are needed. So, force them to
83
+ # be fetched here, before we clear them out in the database.
84
+ old_associates.inspect
85
+
86
+ _t_clear_old_associations(record, association)
42
87
 
43
- _t_clear_old_associations(record, association)
88
+ associates = (record.instance_variable_get record._t_ivar_name(association)) || []
89
+ associates.each do |associate|
90
+ associate._t_reload
91
+ associate.send("#{association.foreign_key(record.class)}=", _t_serialize(record.id, association))
92
+ associate.send "#{association.polymorphic_type}=", self.to_s if association.polymorphic?
93
+ save_associate(associate)
94
+ end
44
95
 
45
- associates = (record.instance_variable_get record._t_ivar_name(association)) || []
46
- associates.each do |associate|
47
- associate._t_reload
48
- associate.send("#{association.foreign_key(record.class)}=", record.id.to_s)
49
- save_associate(associate)
96
+ unless associates.blank?
97
+ associate_ids = associates.map { |associate| _t_serialize(associate.id) }
98
+ record._t_associate_many(association, associate_ids)
99
+ save_associate(record)
100
+ end
101
+
102
+ destroy_orphaned_associates(association, old_associates, associates)
50
103
  end
51
104
 
52
- unless associates.blank?
53
- associate_ids = associates.map { |associate| associate.id.to_s }
54
- record._t_associate_many(association, associate_ids)
105
+ def _t_clear_old_associations(record, association)
106
+ property_name = association.foreign_key(record.class)
107
+ old_associates = get_current_associates(record, association)
108
+ old_associates.each do |old_associate|
109
+ old_associate.send("#{property_name}=", nil)
110
+ save_associate(old_associate)
111
+ end
112
+
113
+ record._t_clear_associates(association)
55
114
  save_associate(record)
56
115
  end
57
- end
58
116
 
59
- def _t_clear_old_associations(record, association)
60
- clazz = association.associate_class
61
- property_name = association.foreign_key(record.class)
117
+ def save_associate(associate)
118
+ associate.respond_to?(:_t_save_without_callback) ? associate._t_save_without_callback : associate.save
119
+ end
62
120
 
63
- old_associates = clazz._t_find_all_by_associate(property_name, record.id.to_s)
64
- old_associates.each do |old_associate|
65
- old_associate.send("#{property_name}=", nil)
66
- save_associate(old_associate)
121
+ def get_current_associates(record, association)
122
+ clazz = association.associate_class
123
+ property_name = association.foreign_key(record.class)
124
+ clazz._t_find_all_by_associate(property_name, _t_serialize(record.id, association))
67
125
  end
68
126
 
69
- record._t_clear_associates(association)
70
- save_associate(record)
127
+ def destroy_orphaned_associates(association, old_associates, associates)
128
+ if association.dependent == :destroy || association.dependent == :delete_all
129
+ issue_callbacks = (association.dependent == :destroy)
130
+ (old_associates.map{|a| a.id} - associates.map{|a| a.id}).each do |associate_id|
131
+ association.associate_class._t_delete([_t_serialize(associate_id)], issue_callbacks)
132
+ end
133
+ end
134
+ end
71
135
  end
72
136
 
73
- def save_associate(associate)
74
- associate.respond_to?(:_t_save_without_callback) ? associate._t_save_without_callback : associate.save
75
- end
76
137
  end
77
-
78
138
  end
79
139
  end
80
140