Permalink
Browse files

allow specifying :include => [...] on restore [Issue #3]

  • Loading branch information...
1 parent 25929ef commit 444af85e7793eb3515220a078b2d3e9ab08f4763 Jeffrey Chupp committed May 20, 2009
Showing with 69 additions and 13 deletions.
  1. +45 −11 lib/is_paranoid.rb
  2. +10 −1 spec/is_paranoid_spec.rb
  3. +7 −0 spec/models.rb
  4. +7 −1 spec/schema.rb
View
@@ -38,27 +38,46 @@ def delete_all conditions = nil
# Use update_all with an exclusive scope to restore undo the soft-delete.
# This bypasses update-related callbacks.
#
- # By default, restores cascade through associations that are
+ # By default, restores cascade through associations that are belongs_to
# :dependent => :destroy and under is_paranoid. You can prevent restoration
# of associated models by passing :include_destroyed_dependents => false,
# for example:
- # Android.restore(:include_destroyed_dependents => false)
+ #
+ # Android.restore(:include_destroyed_dependents => false)
+ #
+ # Alternatively you can specify which relationships to restore via :include,
+ # for example:
+ #
+ # Android.restore(:include => [:parts, memories])
+ #
+ # Please note that specifying :include means you're not using
+ # :include_destroyed_dependents by default, though you can explicitly use
+ # both if you want all has_* relationships and specific belongs_to
+ # relationships, for example
+ #
+ # Android.restore(:include => [:home, :planet], :include_destroyed_dependents => true)
def restore(id, options = {})
- options.reverse_merge!({:include_destroyed_dependents => true})
+ options.reverse_merge!({:include_destroyed_dependents => true}) unless options[:include]
with_exclusive_scope do
update_all(
"#{destroyed_field} = #{connection.quote(field_not_destroyed)}",
"id = #{id}"
)
end
- if options[:include_destroyed_dependents]
- self.reflect_on_all_associations.each do |association|
- if association.options[:dependent] == :destroy and association.klass.respond_to?(:restore)
- association.klass.find_destroyed_only(:all,
- :conditions => ["#{association.primary_key_name} = ?", id]
- ).each do |model|
- model.restore
+ self.reflect_on_all_associations.each do |association|
+ if association.options[:dependent] == :destroy and association.klass.respond_to?(:restore)
+ dependent_relationship = association.macro.to_s =~ /^has/
+ if should_restore?(association.name, dependent_relationship, options)
+ if dependent_relationship
+ restore_related(association.klass, association.primary_key_name, id, options)
+ else
+ restore_related(
+ association.klass,
+ association.klass.primary_key,
+ self.first(id).send(association.primary_key_name),
+ options
+ )
end
end
end
@@ -103,6 +122,21 @@ def method_missing name, *args
super(name, *args)
end
end
+
+ protected
+
+ def should_restore?(association_name, dependent_relationship, options) #:nodoc:
+ ([*options[:include]] || []).include?(association_name) or
+ (options[:include_destroyed_dependents] and dependent_relationship)
+ end
+
+ def restore_related klass, key_name, id, options #:nodoc:
+ klass.find_destroyed_only(:all,
+ :conditions => ["#{key_name} = ?", id]
+ ).each do |model|
+ model.restore(options)
+ end
+ end
end
module InstanceMethods
@@ -166,4 +200,4 @@ def restore(options = {})
end
-ActiveRecord::Base.send(:extend, IsParanoid)
+ActiveRecord::Base.send(:extend, IsParanoid)
View
@@ -147,6 +147,15 @@
@r2d2.restore(:include_destroyed_dependents => false)
}.should_not change(Component, :count)
end
+
+ it "should restore parent and child models specified via :include" do
+ sub_component = SubComponent.create(:name => 'part', :component_id => @r2d2.components.first.id)
+ @r2d2.destroy
+ SubComponent.first(:conditions => {:id => sub_component.id}).should be_nil
+ @r2d2.components.first.restore(:include => [:android, :sub_components])
+ SubComponent.first(:conditions => {:id => sub_component.id}).should_not be_nil
+ Android.find(@r2d2.id).should_not be_nil
+ end
end
describe 'validations' do
@@ -245,4 +254,4 @@
end
end
-end
+end
View
@@ -21,6 +21,8 @@ def raise_hell
class Component < ActiveRecord::Base #:nodoc:
is_paranoid
+ belongs_to :android, :dependent => :destroy
+ has_many :sub_components, :dependent => :destroy
NEW_NAME = 'Something Else!'
after_destroy :change_name
@@ -29,6 +31,11 @@ def change_name
end
end
+class SubComponent < ActiveRecord::Base #:nodoc:
+ is_paranoid
+ belongs_to :component, :dependent => :destroy
+end
+
class Memory < ActiveRecord::Base #:nodoc:
is_paranoid
belongs_to :android, :class_name => "Android", :foreign_key => "parent_id"
View
@@ -21,6 +21,12 @@
t.datetime "updated_at"
end
+ create_table "sub_components", :force => true do |t|
+ t.string "name"
+ t.integer "component_id"
+ t.datetime "deleted_at"
+ end
+
create_table "memories", :force => true do |t|
t.string "name"
t.integer "parent_id"
@@ -42,4 +48,4 @@
t.string "name"
t.boolean "alive", :default => true
end
-end
+end

0 comments on commit 444af85

Please sign in to comment.