Module: IsParanoid::ClassMethods
- Defined in:
- lib/is_paranoid.rb
Instance Method Summary collapse
-
#delete_all(conditions = nil) ⇒ Object
Actually delete the model, bypassing the safety net.
-
#exists_with_destroyed(id) ⇒ Object
TODO: needs better implementation.
-
#has_many(association_id, options = {}, &extension) ⇒ Object
ensure that we respect the is_paranoid conditions when being loaded as a has_many :through NOTE: this only works if is_paranoid is declared before has_many relationships.
- #is_or_equals_not_destroyed ⇒ Object
- #is_paranoid? ⇒ Boolean
-
#method_missing(name, *args, &block) ⇒ Object
find_with_destroyed and other blah_with_destroyed and blah_destroyed_only methods are defined here.
-
#restore(id, options = {}) ⇒ Object
Use update_all with an exclusive scope to restore undo the soft-delete.
-
#with_exclusive_scope(method_scoping = {}, &block) ⇒ Object
with_exclusive_scope is used internally by ActiveRecord when preloading associations.
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(name, *args, &block) ⇒ Object
find_with_destroyed and other blah_with_destroyed and blah_destroyed_only methods are defined here
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
# File 'lib/is_paranoid.rb', line 128 def method_missing name, *args, &block if name.to_s =~ /^(.*)(_destroyed_only|_with_destroyed)$/ and self.respond_to?($1) self.extend(Module.new{ if $2 == '_with_destroyed' # Example: # def count_with_destroyed(*args) # self.with_exclusive_scope{ self.send(:count, *args) } # end define_method name do |*args| self.with_exclusive_scope{ self.send($1, *args) } end else # Example: # def count_destroyed_only(*args) # self.with_exclusive_scope do # with_scope({:find => { :conditions => ["#{destroyed_field} IS NOT ?", nil] }}) do # self.send(:count, *args) # end # end # end define_method name do |*args| self.with_exclusive_scope do with_scope({:find => { :conditions => ["#{self.table_name}.#{destroyed_field} IS NOT ?", field_not_destroyed] }}) do self.send($1, *args, &block) end end end end }) self.send(name, *args, &block) else super(name, *args, &block) end end |
Instance Method Details
#delete_all(conditions = nil) ⇒ Object
Actually delete the model, bypassing the safety net. Because this method is called internally by Model.delete(id) and on the delete method in each instance, we don’t need to specify those methods separately
68 69 70 |
# File 'lib/is_paranoid.rb', line 68 def delete_all conditions = nil self.with_exclusive_scope { super conditions } end |
#exists_with_destroyed(id) ⇒ Object
TODO: needs better implementation
122 123 124 |
# File 'lib/is_paranoid.rb', line 122 def exists_with_destroyed id self.with_exclusive_scope{ exists?(id)} end |
#has_many(association_id, options = {}, &extension) ⇒ Object
ensure that we respect the is_paranoid conditions when being loaded as a has_many :through NOTE: this only works if is_paranoid is declared before has_many relationships. Only use is_paranoid conditions when the associated class is also paranoid
53 54 55 56 57 58 59 60 61 62 |
# File 'lib/is_paranoid.rb', line 53 def has_many(association_id, = {}, &extension) association_klass_name = [:class_name] || [:source] || association_id association_klass = association_klass_name.to_s.classify.try(:constantize) if .key?(:through) && association_klass.is_paranoid? conditions = "#{[:through].to_s.pluralize}.#{destroyed_field} #{is_or_equals_not_destroyed}" [:conditions] = "(" + [[:conditions], conditions].compact.join(") AND (") + ")" end super end |
#is_or_equals_not_destroyed ⇒ Object
42 43 44 45 46 47 48 |
# File 'lib/is_paranoid.rb', line 42 def is_or_equals_not_destroyed if [nil, 'NULL'].include?(field_not_destroyed) 'IS NULL' else "= #{field_not_destroyed}" end end |
#is_paranoid? ⇒ Boolean
38 39 40 |
# File 'lib/is_paranoid.rb', line 38 def is_paranoid? true end |
#restore(id, options = {}) ⇒ Object
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 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)
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)
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/is_paranoid.rb', line 93 def restore(id, = {}) .reverse_merge!({:include_destroyed_dependents => true}) unless [:include] with_exclusive_scope do update_all( "#{destroyed_field} = #{connection.quote(field_not_destroyed)}", primary_key.to_sym => id ) end self.reflect_on_all_associations.each do |association| if association.[:dependent] == :destroy and association.klass.respond_to?(:restore) dependent_relationship = association.macro.to_s =~ /^has/ if should_restore?(association.name, dependent_relationship, ) if dependent_relationship (association.klass, association.primary_key_name, id, ) else ( association.klass, association.klass.primary_key, self.first(id).send(association.primary_key_name), ) end end end end end |
#with_exclusive_scope(method_scoping = {}, &block) ⇒ Object
with_exclusive_scope is used internally by ActiveRecord when preloading associations. Unfortunately this is problematic for is_paranoid since we want preloaded is_paranoid items to still be scoped to their deleted conditions. so we override that here.
169 170 171 172 173 174 175 176 177 |
# File 'lib/is_paranoid.rb', line 169 def with_exclusive_scope(method_scoping = {}, &block) # this is rather hacky, suggestions for improvements appreciated... the idea # is that when the caller includes the method preload_associations, we want # to apply our is_paranoid conditions if caller.any?{|c| c =~ /\d+:in `preload_associations'$/} method_scoping.deep_merge!(:find => {:conditions => {destroyed_field => field_not_destroyed} }) end super method_scoping, &block end |