Permalink
Browse files

Merge branch 'activerecord/3_2' of https://github.com/chuckg/rails3_a…

…cts_as_paranoid into chuckg-activerecord/3_2

Conflicts:
	Gemfile
	Gemfile.lock
	lib/rails3_acts_as_paranoid.rb
	lib/validations/uniqueness_without_deleted.rb
	rails3_acts_as_paranoid.gemspec
	test/test_helper.rb
  • Loading branch information...
2 parents 0f0aa0d + 9fca394 commit bc1fa44153352dc0a3482805e08c2cfd15288c44 @goncalossilva committed Mar 8, 2012
View
@@ -1,10 +1,10 @@
source "http://rubygems.org"
-gem "activerecord", ">=3.2"
+gem "activerecord", "~>3.2"
# Development dependencies
gem "rake"
-gem "activesupport", ">=3.2"
+gem "activesupport", "~>3.2"
gem "sqlite3-ruby"
group :test do
View
@@ -2,23 +2,23 @@ GEM
remote: http://rubygems.org/
specs:
ZenTest (4.6.2)
- activemodel (3.1.3)
- activesupport (= 3.1.3)
+ activemodel (3.2.2)
+ activesupport (= 3.2.2)
builder (~> 3.0.0)
- i18n (~> 0.6)
- activerecord (3.1.3)
- activemodel (= 3.1.3)
- activesupport (= 3.1.3)
- arel (~> 2.2.1)
+ activerecord (3.2.2)
+ activemodel (= 3.2.2)
+ activesupport (= 3.2.2)
+ arel (~> 3.0.2)
tzinfo (~> 0.3.29)
- activesupport (3.1.3)
+ activesupport (3.2.2)
+ i18n (~> 0.6)
multi_json (~> 1.0)
- arel (2.2.1)
+ arel (3.0.2)
autotest-growl (0.2.16)
builder (3.0.0)
i18n (0.6.0)
minitest (2.11.3)
- multi_json (1.0.4)
+ multi_json (1.1.0)
rake (0.9.2.2)
sqlite3 (1.3.5)
sqlite3-ruby (1.3.3)
@@ -30,8 +30,8 @@ PLATFORMS
DEPENDENCIES
ZenTest
- activerecord (~> 3.1)
- activesupport (~> 3.1)
+ activerecord (~> 3.2)
+ activesupport (~> 3.2)
autotest-growl
minitest
rake
View
@@ -17,7 +17,7 @@ desc 'Test the rails3_acts_as_paranoid plugin.'
Rake::TestTask.new(:test) do |t|
t.libs << 'lib'
t.libs << 'test'
- t.pattern = 'test/**/*_test.rb'
+ t.pattern = 'test/**/test_*.rb'
t.verbose = true
end
@@ -0,0 +1,31 @@
+module ActsAsParanoid
+ module Associations
+ def self.included(base)
+ base.extend ClassMethods
+ base.class_eval do
+ class << self
+ alias_method_chain :belongs_to, :deleted
+ end
+ end
+ end
+
+ module ClassMethods
+ def belongs_to_with_deleted(target, options = {})
+ with_deleted = options.delete(:with_deleted)
+ result = belongs_to_without_deleted(target, options)
+
+ if with_deleted
+ class_eval <<-RUBY, __FILE__, __LINE__
+ def #{target}_with_unscoped(*args)
+ return #{target}_without_unscoped(*args) unless association(:#{target}).klass.paranoid?
+ association(:#{target}).klass.with_deleted.merge(association(:#{target}).association_scope)
+ end
+ alias_method_chain :#{target}, :unscoped
+ RUBY
+ end
+
+ result
+ end
+ end
+ end
+end
@@ -0,0 +1,159 @@
+module ActsAsParanoid
+ module Core
+ def self.included(base)
+ base.extend ClassMethods
+ end
+
+ module ClassMethods
+ def self.extended(base)
+ base.define_callbacks :recover
+ end
+
+ def before_recover(method)
+ set_callback :recover, :before, method
+ end
+
+ def after_recover(method)
+ set_callback :recover, :after, method
+ end
+
+ def with_deleted
+ without_paranoid_default_scope
+ end
+
+ def only_deleted
+ without_paranoid_default_scope.where("#{paranoid_column_reference} IS NOT ?", nil)
+ end
+
+ def delete_all!(conditions = nil)
+ without_paranoid_default_scope.delete_all!(conditions)
+ end
+
+ def delete_all(conditions = nil)
+ update_all ["#{paranoid_configuration[:column]} = ?", delete_now_value], conditions
+ end
+
+ def paranoid_default_scope_sql
+ self.scoped.table[paranoid_column].eq(nil).to_sql
+ end
+
+ def paranoid_column
+ paranoid_configuration[:column].to_sym
+ end
+
+ def paranoid_column_type
+ paranoid_configuration[:column_type].to_sym
+ end
+
+ def dependent_associations
+ self.reflect_on_all_associations.select {|a| [:destroy, :delete_all].include?(a.options[:dependent]) }
+ end
+
+ def delete_now_value
+ case paranoid_configuration[:column_type]
+ when "time" then Time.now
+ when "boolean" then true
+ when "string" then paranoid_configuration[:deleted_value]
+ end
+ end
+
+ protected
+
+ def without_paranoid_default_scope
+ scope = self.scoped.with_default_scope
+ scope.where_values.delete(paranoid_default_scope_sql)
+
+ scope
+ end
+ end
+
+ def paranoid_value
+ self.send(self.class.paranoid_column)
+ end
+
+ def destroy!
+ with_transaction_returning_status do
+ run_callbacks :destroy do
+ act_on_dependent_destroy_associations
+ self.class.delete_all!(self.class.primary_key.to_sym => self.id)
+ self.paranoid_value = self.class.delete_now_value
+ freeze
+ end
+ end
+ end
+
+ def destroy
+ if paranoid_value.nil?
+ with_transaction_returning_status do
+ run_callbacks :destroy do
+ self.class.delete_all(self.class.primary_key.to_sym => self.id)
+ self.paranoid_value = self.class.delete_now_value
+ self
+ end
+ end
+ else
+ destroy!
+ end
+ end
+
+ def recover(options={})
+ options = {
+ :recursive => self.class.paranoid_configuration[:recover_dependent_associations],
+ :recovery_window => self.class.paranoid_configuration[:dependent_recovery_window]
+ }.merge(options)
+
+ self.class.transaction do
+ run_callbacks :recover do
+ recover_dependent_associations(options[:recovery_window], options) if options[:recursive]
+
+ self.paranoid_value = nil
+ self.save
+ end
+ end
+ end
+
+ def recover_dependent_associations(window, options)
+ self.class.dependent_associations.each do |association|
+ if association.collection? && self.send(association.name).paranoid?
+ self.send(association.name).unscoped do
+ self.send(association.name).paranoid_deleted_around_time(paranoid_value, window).each do |object|
+ object.recover(options) if object.respond_to?(:recover)
+ end
+ end
+ elsif association.macro == :has_one && association.klass.paranoid?
+ association.klass.unscoped do
+ object = association.klass.paranoid_deleted_around_time(paranoid_value, window).send('find_by_'+association.foreign_key, self.id)
+ object.recover(options) if object && object.respond_to?(:recover)
+ end
+ elsif association.klass.paranoid?
+ association.klass.unscoped do
+ id = self.send(association.foreign_key)
+ object = association.klass.paranoid_deleted_around_time(paranoid_value, window).find_by_id(id)
+ object.recover(options) if object && object.respond_to?(:recover)
+ end
+ end
+ end
+ end
+
+ def act_on_dependent_destroy_associations
+ self.class.dependent_associations.each do |association|
+ if association.collection? && self.send(association.name).paranoid?
+ association.klass.with_deleted.instance_eval("find_all_by_#{association.foreign_key}(#{self.id.to_json})").each do |object|
+ object.destroy!
+ end
+ end
+ end
+ end
+
+ def deleted?
+ !paranoid_value.nil?
+ end
+ alias_method :destroyed?, :deleted?
+
+ private
+
+ def paranoid_value=(value)
+ self.send("#{self.class.paranoid_column}=", value)
+ end
+ end
+end
@@ -0,0 +1,37 @@
+module ActsAsParanoid
+ module Relation
+ def self.included(base)
+ base.class_eval do
+
+ def paranoid?
+ klass.try(:paranoid?) ? true : false
+ end
+
+ def paranoid_deletion_attributes
+ { klass.paranoid_column => klass.delete_now_value }
+ end
+
+ alias_method :orig_delete_all, :delete_all
+ def delete_all!(conditions = nil)
+ if conditions
+ where(conditions).delete_all!
+ else
+ orig_delete_all
+ end
+ end
+
+ def delete_all(conditions = nil)
+ if paranoid?
+ update_all(paranoid_deletion_attributes, conditions)
+ else
+ delete_all!(conditions)
+ end
+ end
+
+ def destroy!(id_or_array)
+ where(primary_key => id_or_array).orig_delete_all
+ end
+ end
+ end
+ end
+end
@@ -0,0 +1,41 @@
+require 'active_support/core_ext/array/wrap'
+
+module ActsAsParanoid
+ module Validations
+ def self.included(base)
+ base.extend ClassMethods
+ end
+
+ class UniquenessWithoutDeletedValidator < ActiveRecord::Validations::UniquenessValidator
+ def validate_each(record, attribute, value)
+ finder_class = find_finder_class_for(record)
+ table = finder_class.arel_table
+
+ coder = record.class.serialized_attributes[attribute.to_s]
+
+ if value && coder
+ value = coder.dump value
+ end
+
+ relation = build_relation(finder_class, table, attribute, value)
+ relation = relation.and(table[finder_class.primary_key.to_sym].not_eq(record.send(:id))) if record.persisted?
+
+ Array.wrap(options[:scope]).each do |scope_item|
+ scope_value = record.send(scope_item)
+ relation = relation.and(table[scope_item].eq(scope_value))
+ end
+
+ # Re-add ActsAsParanoid default scope conditions manually.
+ if finder_class.unscoped.where(finder_class.paranoid_default_scope_sql).where(relation).exists?
+ record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value))
+ end
+ end
+ end
+
+ module ClassMethods
+ def validates_uniqueness_of_without_deleted(*attr_names)
+ validates_with UniquenessWithoutDeletedValidator, _merge_attributes(attr_names)
+ end
+ end
+ end
+end
Oops, something went wrong.

0 comments on commit bc1fa44

Please sign in to comment.