Skip to content

Commit

Permalink
Add only_deleted scope and associated methods.
Browse files Browse the repository at this point in the history
This patch makes it easy to get only the deleted records from a model class. This facilitates the creation of deleted records listings for "undelete"-like functionalities in your apps.

Added methods: find_only_deleted, count_only_deleted and exists_only_deleted?

Passing tests included (tested with activerecord 2.1).
  • Loading branch information
oboxodo committed Aug 30, 2008
1 parent 1e9144e commit b419750
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 1 deletion.
35 changes: 34 additions & 1 deletion lib/caboose/acts/paranoid.rb
Expand Up @@ -19,6 +19,9 @@ module Acts #:nodoc:
# Widget.find(:all, :with_deleted => true)
# # SELECT * FROM widgets
#
# Widget.find_only_deleted(:all)
# # SELECT * FROM widgets WHERE widgets.deleted_at IS NOT NULL
#
# Widget.find_with_deleted(1).deleted?
# # Returns true if the record was previously destroyed, false if not
#
Expand All @@ -31,6 +34,9 @@ module Acts #:nodoc:
# Widget.count_with_deleted
# # SELECT COUNT(*) FROM widgets
#
# Widget.count_only_deleted
# # SELECT COUNT(*) FROM widgets WHERE widgets.deleted_at IS NOT NULL
#
# Widget.delete_all
# # UPDATE widgets SET deleted_at = '2005-09-17 17:46:36'
#
Expand Down Expand Up @@ -87,14 +93,35 @@ def find_with_deleted(*args)
end
end

def find_only_deleted(*args)
options = args.extract_options!
validate_find_options(options)
set_readonly_option!(options)
options[:only_deleted] = true # yuck!

case args.first
when :first then find_initial(options)
when :all then find_every(options)
else find_from_ids(args, options)
end
end

def exists?(*args)
with_deleted_scope { exists_with_deleted?(*args) }
end

def exists_only_deleted?(*args)
with_only_deleted_scope { exists_with_deleted?(*args) }
end

def count_with_deleted(*args)
calculate_with_deleted(:count, *construct_count_options_from_args(*args))
end

def count_only_deleted(*args)
with_only_deleted_scope { count_with_deleted(*args) }
end

def count(*args)
with_deleted_scope { count_with_deleted(*args) }
end
Expand All @@ -116,12 +143,18 @@ def with_deleted_scope(&block)
with_scope({:find => { :conditions => ["#{table_name}.#{deleted_attribute} IS NULL OR #{table_name}.#{deleted_attribute} > ?", current_time] } }, :merge, &block)
end

def with_only_deleted_scope(&block)
with_scope({:find => { :conditions => ["#{table_name}.#{deleted_attribute} IS NOT NULL AND #{table_name}.#{deleted_attribute} <= ?", current_time] } }, :merge, &block)
end

private
# all find calls lead here
def find_every(options)
options.delete(:with_deleted) ?
find_every_with_deleted(options) :
with_deleted_scope { find_every_with_deleted(options) }
options.delete(:only_deleted) ?
with_only_deleted_scope { find_every_with_deleted(options) } :
with_deleted_scope { find_every_with_deleted(options) }
end
end

Expand Down
13 changes: 13 additions & 0 deletions test/paranoid_test.rb
Expand Up @@ -47,9 +47,15 @@ def test_should_exists_with_deleted
assert !Widget.exists?(2)
end

def test_should_exists_only_deleted
assert Widget.exists_only_deleted?(2)
assert !Widget.exists_only_deleted?(1)
end

def test_should_count_with_deleted
assert_equal 1, Widget.count
assert_equal 2, Widget.count_with_deleted
assert_equal 1, Widget.count_only_deleted
assert_equal 2, Widget.calculate_with_deleted(:count, :all)
end

Expand All @@ -69,6 +75,7 @@ def test_should_destroy
widgets(:widget_1).destroy!
assert_equal 0, Widget.count
assert_equal 0, Category.count
assert_equal 1, Widget.count_only_deleted
assert_equal 1, Widget.calculate_with_deleted(:count, :all)
# Category doesn't get destroyed because the dependent before_destroy callback uses #destroy
assert_equal 4, Category.calculate_with_deleted(:count, :all)
Expand Down Expand Up @@ -113,6 +120,12 @@ def test_should_not_count_deleted
assert_equal 1, Widget.count
assert_equal 1, Widget.count(:all, :conditions => ['title=?', 'widget 1'])
assert_equal 2, Widget.calculate_with_deleted(:count, :all)
assert_equal 1, Widget.count_only_deleted
end

def test_should_find_only_deleted
assert_equal [2], Widget.find_only_deleted(:all).collect { |w| w.id }
assert_equal [1, 2], Widget.find_with_deleted(:all, :order => 'id').collect { |w| w.id }
end

def test_should_not_find_deleted
Expand Down

0 comments on commit b419750

Please sign in to comment.