Skip to content

Commit

Permalink
stop using with_exclusive_scope, ensuring other scopes stay in effect
Browse files Browse the repository at this point in the history
  • Loading branch information
chewi authored and mislav committed May 16, 2010
1 parent daeb386 commit 5ea623f
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 16 deletions.
1 change: 0 additions & 1 deletion README.markdown
Expand Up @@ -62,4 +62,3 @@ Pitfalls


* `validates_uniqueness_of` does not ignore items marked with a "deleted_at" flag * `validates_uniqueness_of` does not ignore items marked with a "deleted_at" flag
* various eager-loading and associations-related issues (see ["Killing is_paranoid"](http://blog.semanticart.com/killing_is_paranoid/)) * various eager-loading and associations-related issues (see ["Killing is_paranoid"](http://blog.semanticart.com/killing_is_paranoid/))
* `find/count_with_destroyed` uses `with_exclusive_scope` and thus doesn't play nicely with named scopes
56 changes: 41 additions & 15 deletions lib/is_paranoid.rb
Expand Up @@ -15,35 +15,32 @@ module SafetyNet
# end # end
def is_paranoid def is_paranoid
class_eval do class_eval do
# This is the real magic. All calls made to this model will append # This is the real magic. All calls made to this model will
# the conditions deleted_at => nil. Exceptions require using # append the conditions deleted_at => nil. Exceptions require
# exclusive_scope (see self.delete_all, self.count_with_destroyed, # using with_destroyed_scope (see self.delete_all,
# and self.find_with_destroyed ) # self.count_with_destroyed, and self.find_with_destroyed )
default_scope :conditions => {:deleted_at => nil} default_scope :conditions => {:deleted_at => nil}


# Actually delete the model, bypassing the safety net. Because # Actually delete the model, bypassing the safety net. Because
# this method is called internally by Model.delete(id) and on the # this method is called internally by Model.delete(id) and on the
# delete method in each instance, we don't need to specify those # delete method in each instance, we don't need to specify those
# methods separately # methods separately
## FIXME: this is dangerous. find a better solution def self.delete_all conditions = nil
# def self.delete_all conditions = nil self.with_destroyed_scope { super conditions }
# self.with_exclusive_scope do end
# super conditions
# end
# end


# Return a count that includes the soft-deleted models. # Return a count that includes the soft-deleted models.
def self.count_with_destroyed *args def self.count_with_destroyed *args
self.with_exclusive_scope { count(*args) } self.with_destroyed_scope { count(*args) }
end end


# Return instances of all models matching the query regardless # Return instances of all models matching the query regardless
# of whether or not they have been soft-deleted. # of whether or not they have been soft-deleted.
def self.find_with_destroyed *args def self.find_with_destroyed *args
self.with_exclusive_scope { find(*args) } self.with_destroyed_scope { find(*args) }
end end


# Perofm a find only on destroyed instances # Perform a find only on destroyed instances.
def self.find_only_destroyed *args def self.find_only_destroyed *args
self.with_only_destroyed_scope { find(*args) } self.with_only_destroyed_scope { find(*args) }
end end
Expand Down Expand Up @@ -77,8 +74,37 @@ def destroy_without_callbacks
end end


def self.with_only_destroyed_scope(&block) def self.with_only_destroyed_scope(&block)
with_exclusive_scope do with_destroyed_scope do
with_scope({:find => { :conditions => ["deleted_at IS NOT NULL"] }}, &block) table = connection.quote_table_name(table_name)
attr = connection.quote_column_name(:deleted_at)
with_scope(:find => { :conditions => "#{table}.#{attr} IS NOT NULL" }, &block)
end
end

def self.with_destroyed_scope
find = current_scoped_methods[:find]

if find[:conditions]
original = find[:conditions].dup

begin
case find[:conditions]
when Hash:
if find[:conditions][:deleted_at].nil?
find[:conditions].delete(:deleted_at)
end
when String:
conditions = sanitize_conditions(:deleted_at => nil)
find[:conditions].gsub!(conditions, '1=1')
end

result = yield
ensure
find[:conditions] = original
return result if result
end
else
yield
end end
end end
end end
Expand Down
13 changes: 13 additions & 0 deletions spec/is_paranoid_spec.rb
Expand Up @@ -7,6 +7,9 @@ class Person < ActiveRecord::Base
class Android < ActiveRecord::Base class Android < ActiveRecord::Base
validates_uniqueness_of :name validates_uniqueness_of :name
is_paranoid is_paranoid
named_scope :ordered, :order => 'name DESC'
named_scope :r2d2, :conditions => { :name => 'R2D2' }
named_scope :c3p0, :conditions => { :name => 'C3P0' }
end end


describe Android do describe Android do
Expand Down Expand Up @@ -88,4 +91,14 @@ class Android < ActiveRecord::Base
@r2d2.destroy @r2d2.destroy
Android.find_only_destroyed(:all).should == [@r2d2] Android.find_only_destroyed(:all).should == [@r2d2]
end end

it "should honor named scopes" do
@r2d2.destroy
@c3p0.destroy
Android.r2d2.find_only_destroyed(:all).should == [@r2d2]
Android.c3p0.ordered.find_only_destroyed(:all).should == [@c3p0]
Android.ordered.find_only_destroyed(:all).should == [@r2d2,@c3p0]
Android.r2d2.c3p0.find_only_destroyed(:all).should == []
Android.find_only_destroyed(:all).should == [@r2d2,@c3p0]
end
end end

0 comments on commit 5ea623f

Please sign in to comment.