Giter Site home page Giter Site logo

paranoia's Introduction

Gem Version build

Notice:

paranoia has some surprising behaviour (like overriding ActiveRecord's delete and destroy) and is not recommended for new projects. See discard's README for more details.

Paranoia will continue to accept bug fixes and support new versions of Rails but isn't accepting new features.

Paranoia

Paranoia is a re-implementation of acts_as_paranoid for Rails 3/4/5, using much, much, much less code.

When your app is using Paranoia, calling destroy on an ActiveRecord object doesn't actually destroy the database record, but just hides it. Paranoia does this by setting a deleted_at field to the current time when you destroy a record, and hides it by scoping all queries on your model to only include records which do not have a deleted_at field.

If you wish to actually destroy an object you may call really_destroy!. WARNING: This will also really destroy all dependent: :destroy records, so please aim this method away from face when using.

If a record has has_many associations defined AND those associations have dependent: :destroy set on them, then they will also be soft-deleted if acts_as_paranoid is set, otherwise the normal destroy will be called. See Destroying through association callbacks for clarifying examples.

Getting Started Video

Setup and basic usage of the paranoia gem GoRails #41

Installation & Usage

For Rails 3, please use version 1 of Paranoia:

gem "paranoia", "~> 1.0"

For Rails 4 and 5, please use version 2 of Paranoia (2.2 or greater required for rails 5):

gem "paranoia", "~> 2.2"

Of course you can install this from GitHub as well from one of these examples:

gem "paranoia", github: "rubysherpas/paranoia", branch: "rails3"
gem "paranoia", github: "rubysherpas/paranoia", branch: "rails4"
gem "paranoia", github: "rubysherpas/paranoia", branch: "rails5"

Then run:

bundle install

Updating is as simple as bundle update paranoia.

Run your migrations for the desired models

Run:

bin/rails generate migration AddDeletedAtToClients deleted_at:datetime:index

and now you have a migration

class AddDeletedAtToClients < ActiveRecord::Migration
  def change
    add_column :clients, :deleted_at, :datetime
    add_index :clients, :deleted_at
  end
end

Usage

In your model:

class Client < ActiveRecord::Base
  acts_as_paranoid

  # ...
end

Hey presto, it's there! Calling destroy will now set the deleted_at column:

>> client.deleted_at
# => nil
>> client.destroy
# => client
>> client.deleted_at
# => [current timestamp]

If you really want it gone gone, call really_destroy!:

>> client.deleted_at
# => nil
>> client.really_destroy!
# => client

If you need skip updating timestamps for deleting records, call really_destroy!(update_destroy_attributes: false). When we call really_destroy!(update_destroy_attributes: false) on the parent client, then each child email will also have really_destroy!(update_destroy_attributes: false) called.

>> client.really_destroy!(update_destroy_attributes: false)
# => client

If you want to use a column other than deleted_at, you can pass it as an option:

class Client < ActiveRecord::Base
  acts_as_paranoid column: :destroyed_at

  ...
end

If you want to skip adding the default scope:

class Client < ActiveRecord::Base
  acts_as_paranoid without_default_scope: true

  ...
end

If you want to access soft-deleted associations, override the getter method:

def product
  Product.unscoped { super }
end

If you want to include associated soft-deleted objects, you can (un)scope the association:

class Person < ActiveRecord::Base
  belongs_to :group, -> { with_deleted }
end

Person.includes(:group).all

If you want to find all records, even those which are deleted:

Client.with_deleted

If you want to exclude deleted records, when not able to use the default_scope (e.g. when using without_default_scope):

Client.without_deleted

If you want to find only the deleted records:

Client.only_deleted

If you want to check if a record is soft-deleted:

client.paranoia_destroyed?
# or
client.deleted?

If you want to restore a record:

Client.restore(id)
# or
client.restore

If you want to restore a whole bunch of records:

Client.restore([id1, id2, ..., idN])

If you want to restore a record and their dependently destroyed associated records:

Client.restore(id, :recursive => true)
# or
client.restore(:recursive => true)

If you want to restore a record and only those dependently destroyed associated records that were deleted within 2 minutes of the object upon which they depend:

Client.restore(id, :recursive => true, :recovery_window => 2.minutes)
# or
client.restore(:recursive => true, :recovery_window => 2.minutes)

Note that by default paranoia will not prevent that a soft destroyed object can't be associated with another object of a different model. A Rails validator is provided should you require this functionality:

validates :some_assocation, association_not_soft_destroyed: true

This validator makes sure that some_assocation is not soft destroyed. If the object is soft destroyed the main object is rendered invalid and an validation error is added.

For more information, please look at the tests.

About indexes:

Beware that you should adapt all your indexes for them to work as fast as previously. For example,

add_index :clients, :group_id
add_index :clients, [:group_id, :other_id]

should be replaced with

add_index :clients, :group_id, where: "deleted_at IS NULL"
add_index :clients, [:group_id, :other_id], where: "deleted_at IS NULL"

Of course, this is not necessary for the indexes you always use in association with with_deleted or only_deleted.

Unique Indexes

Because NULL != NULL in standard SQL, we can not simply create a unique index on the deleted_at column and expect it to enforce that there only be one record with a certain combination of values.

If your database supports them, good alternatives include partial indexes (above) and indexes on computed columns. E.g.

add_index :clients, [:group_id, 'COALESCE(deleted_at, false)'], unique: true

If not, an alternative is to create a separate column which is maintained alongside deleted_at for the sake of enforcing uniqueness. To that end, paranoia makes use of two method to make its destroy and restore actions: paranoia_restore_attributes and paranoia_destroy_attributes.

add_column :clients, :active, :boolean
add_index :clients, [:group_id, :active], unique: true

class Client < ActiveRecord::Base
  # optionally have paranoia make use of your unique column, so that
  # your lookups will benefit from the unique index
  acts_as_paranoid column: :active, sentinel_value: true

  def paranoia_restore_attributes
    {
      deleted_at: nil,
      active: true
    }
  end

  def paranoia_destroy_attributes
    {
      deleted_at: current_time_from_proper_timezone,
      active: nil
    }
  end
end
Destroying through association callbacks

When dealing with dependent: :destroy associations and acts_as_paranoid, it's important to remember that whatever method is called on the parent model will be called on the child model. For example, given both models of an association have acts_as_paranoid defined:

class Client < ActiveRecord::Base
  acts_as_paranoid

  has_many :emails, dependent: :destroy
end

class Email < ActiveRecord::Base
  acts_as_paranoid

  belongs_to :client
end

When we call destroy on the parent client, it will call destroy on all of its associated children emails:

>> client.emails.count
# => 5
>> client.destroy
# => client
>> client.deleted_at
# => [current timestamp]
>> Email.where(client_id: client.id).count
# => 0
>> Email.with_deleted.where(client_id: client.id).count
# => 5

Similarly, when we call really_destroy! on the parent client, then each child email will also have really_destroy! called:

>> client.emails.count
# => 5
>> client.id
# => 12345
>> client.really_destroy!
# => client
>> Client.find 12345
# => ActiveRecord::RecordNotFound
>> Email.with_deleted.where(client_id: client.id).count
# => 0

However, if the child model Email does not have acts_as_paranoid set, then calling destroy on the parent client will also call destroy on each child email, thereby actually destroying them:

class Client < ActiveRecord::Base
  acts_as_paranoid

  has_many :emails, dependent: :destroy
end

class Email < ActiveRecord::Base
  belongs_to :client
end

>> client.emails.count
# => 5
>> client.destroy
# => client
>> Email.where(client_id: client.id).count
# => 0
>> Email.with_deleted.where(client_id: client.id).count
# => NoMethodError: undefined method `with_deleted' for #<Class:0x0123456>

Acts As Paranoid Migration

You can replace the older acts_as_paranoid methods as follows:

Old Syntax New Syntax
find_with_deleted(:all) Client.with_deleted
find_with_deleted(:first) Client.with_deleted.first
find_with_deleted(id) Client.with_deleted.find(id)

The recover method in acts_as_paranoid runs update callbacks. Paranoia's restore method does not do this.

Callbacks

Paranoia provides several callbacks. It triggers destroy callback when the record is marked as deleted and real_destroy when the record is completely removed from database. It also calls restore callback when the record is restored via paranoia

For example if you want to index your records in some search engine you can go like this:

class Product < ActiveRecord::Base
  acts_as_paranoid

  after_destroy      :update_document_in_search_engine
  after_restore      :update_document_in_search_engine
  after_real_destroy :remove_document_from_search_engine
end

You can use these events just like regular Rails callbacks with before, after and around hooks.

License

This gem is released under the MIT license.

paranoia's People

Contributors

benmorganio avatar codeodor avatar delba avatar donv avatar duderman avatar edwardmp avatar huoxito avatar iggant avatar jarednorman avatar jesktop avatar jhawthorn avatar joergschiller avatar ken3ypa avatar konto-andrzeja avatar leonardoalifraco avatar lime avatar mathieujobin avatar maurogeorge avatar michaltn avatar mishina2228 avatar nicolasleger avatar patkoperwas avatar radar avatar sergey-alekseev avatar sevenseacat avatar sgonyea avatar stgeneral avatar tonybyrne avatar tricknotes avatar westonganger avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

paranoia's Issues

Calling to_param on destroyed ActiveRecord object returns nil

When creating an admin interface to restore destroyed objects, I ran across a problem when retrieving the path to the resource. Rails calls the to_param method during this process and it was returning nil for destroyed objects.

As you can see, the ActiveModel to_param method calls persisted?, which in the ActiveRecord persisted? method checks if it's a new_record? or destroyed?. Since Paranoia implements a destroyed? method, it will return true for paranoid destroyed objects.

A solution for this would be for paranoia to implement the ActiveRecord persisted? method which only checks for new_record if paranoid. For example:

def persisted?
  paranoid? ? !new_record? : super
end

Destroy vs. delete callbacks

I'm building a model which has some logic that I want to happen only around a hard-destroy. Specifically, I want to destroy some dependent relationships which should remain while the record is soft-deleted. The problem I'm running into is that the before_destroy runs when both #destroy and #destroy! are called. I see there is a #delete method which is basically #destroy without callbacks; however, I feel like #destroy is more of an alias for #delete in this case.

Therefore, I wish the destroy callbacks would run only when the record is being destroyed (for reals), and a bonus would be if there were a new set of callbacks (delete?) that ran when the record is being deleted.

I'd be happy to add this if it would be accepted.

Does not work with Rails 2

bundle exec rake gems:install

results in

gem install paranoia --version ">= 0"
ERROR:  Error installing paranoia:
        activemodel requires activesupport (= 3.2.8)

the readme says that config.gem "paranoia" should be sufficient, but I guess rails 2 is too old? haha

`only_deleted` and Rails 4

I'm having an issue with only_deleted scope, after upgrading to Rails 4 and using paranoia's rails4 branch.

> User.only_deleted.first
  User Load (0.7ms)  SELECT "users".* FROM "users" WHERE (NOT ('---
:deleted_at:
')) ORDER BY "users"."id" ASC LIMIT 1
PG::Error: ERROR:  invalid input syntax for type boolean: "---
:deleted_at:
"
LINE 1: SELECT  "users".* FROM "users"  WHERE (NOT ('---
                                                    ^
: SELECT  "users".* FROM "users"  WHERE (NOT ('---
:deleted_at:
'))  ORDER BY "users"."id" ASC LIMIT 1
ActiveRecord::StatementInvalid: PG::Error: ERROR:  invalid input syntax for type boolean: "---
:deleted_at:
"
LINE 1: SELECT  "users".* FROM "users"  WHERE (NOT ('---
                                                    ^
: SELECT  "users".* FROM "users"  WHERE (NOT ('---
:deleted_at:
'))  ORDER BY "users"."id" ASC LIMIT 1
from /Users/leonelgalan/.rbenv/versions/2.0.0-p195/lib/ruby/gems/2.0.0/gems/activerecord-4.0.0/lib/active_record/connection_adapters/postgresql_adapter.rb:768:in `exec'

User is my model using paranoia and I called first only to force the Relation to execute the query. This code worked fine on Rails 3.2.13 and Paranoia 1.2.0

Update

My "quick" "fix" (neither quick or a fix) was to overwrite the only_deleted scope: (I'm using squeel for my where clause.)

scope :only_deleted, -> { unscoped.where{deleted_at != nil} }

has_many :through associations may not respect default_scope :conditions

If you have a :has_many, :through association, where the join model is paranoid, the through association does not respect the default_scope, thus "deleted" records are returned when the association is reference.

see my fork for examples in the test: https://github.com/dalton/paranoia/blob/master/test/paranoia_test.rb

related: https://rails.lighthouseapp.com/projects/8994/tickets/3610-has_many-through-associations-may-not-respect-default_scope-conditions?spam=1#ticket-3610-18

"Hard delete" record

Let's say, you have an API on which you also sync deleted records (to delete them at the remote location). But after a defined time range, you want to purge those records because they are synced now (means: also deleted at all the remote locations) and you want to reduce the overhead of those deletion checks by definitely removing them from your records.

Is there a possibility to really remove a record from the database? I peeked at the code but could not answer my question.

Overriding getter to access soft-deleted associations not working

From the README:

If you want to access soft-deleted associations, override the getter method:

def product
  Product.unscoped { super }
end

But it only works if you're willing to tolerate N+1 queries. That is, if you add .includes to your scope, then the getter override method is not called. Here is a commit that reproduces the problem in a sample app. In the controller, the scope is build with .includes. If you have a couple or more users each with associated company and posts, then you will see queries like these:

Started GET "/users" for 127.0.0.1 at 2014-02-11 16:30:21 -0500
Processing by UsersController#index as HTML
  User Load (0.3ms)  SELECT "users".* FROM "users"
  Company Load (0.1ms)  SELECT "companies".* FROM "companies" WHERE "companies"."deleted_at" IS NULL AND "companies"."id" IN (1, 2)
  Post Load (0.1ms)  SELECT "posts".* FROM "posts" WHERE "posts"."deleted_at" IS NULL AND "posts"."user_id" IN (1, 2)

Calling really_destroy! on an ActiveModel raises an exception?

I get the following exception when calling really_destroy! on an ActiveModel
ActiveModel is set to acts_as_paranoid

NoMethodError: undefined method `reflections' for #MyActiveRecord:0x00000102793aa8

from /Users/name/.rvm/gems/ruby-2.0.0-p451@company/gems/activemodel-4.1.4/lib/active_model/attribute_methods.rb:435:in `method_missing'

Any idea what's causing this?

Doesn't work with inherited_resources

When using the popular josevalim gem inherited_resourses, paranoia breaks down in a spec environment (haven't tested in production yet). It might be because you are both aliasing destroy.

The expected behavior is that the record is not actually destroyed, but the actual behavior is that it is.

Impossibility to declare an association with deleted items

The gem https://github.com/goncalossilva/acts_as_paranoid owns declares the usage of :with_deleted => true in the association declarations, like this:

belongs_to :parent_including_deleted, :class_name => "Parent", :with_deleted => true

This declaration unscope association like with_deleted scope.

I tried several methods to get the same results, but I did not found any solution.
http://stackoverflow.com/questions/23867467/remove-default-scope-in-association/23871202?noredirect=1#comment36745044_23871202

`only_deleted` method does not work on associations

Hi there,

Currently, the only_deleted method of a model is implemented as

    def only_deleted
      unscoped {
        where("deleted_at is not null")
      }
    end

Because of this, only_deleted can only be called on the class which is marked as acts_as_paranoid. If it is called anywhere else, for instance on an association, then it will behave as if it was called on the class:

irb> Organization.where('LENGTH(name) > 100').only_deleted
Organization Load (1.3ms)  SELECT `organizations`.* FROM `organizations` WHERE (deleted_at is not null)
=> [#<Organization id: ...>, ...]

The behavior I expect is

irb> Organization.where('LENGTH(name) > 100').only_deleted
Organization Load (1.3ms)  SELECT `organizations`.* FROM `organizations` WHERE LENGTH(name) > 100 AND (deleted_at is not null)
=> [#<Organization id: ...>, ...]

So it should somehow only negate the deleted_at IS NULL condition that is set as the default scope.

Furthermore, relations also don't work:

irb> org = Organization.first
=> #<Organization id: 1,...>
irb> org.users.only_deleted
User Load (1.3ms)  SELECT `users`.* FROM `users` WHERE (deleted_at is not null)
=> [...]

You would expect:

irb> org = Organization.first
=> #<Organization id: 1,...>
irb> org.users.only_deleted
User Load (2.7ms)  SELECT `users`.* FROM `users` WHERE organization_id = 1 AND (deleted_at is not null)
=> [...]

Of course, this could be tricky. Since wat would be te behavior of:

Organization.where(:deleted_at => nil).only_deleted

vs.

Organization.only_deleted

I'm willing to write a patch for this if there is support for the behavior.

Thanks,
HJ

Restore doesn't restore dependent children

Using .restore! doesn't restore the dependent children.

1.9.3-p0 :008 > owner = Owner.unscoped.first
  DEBUG - Owner Load (0.7ms)  SELECT `owners`.* FROM `owners` LIMIT 1
 => #<Owner id: 2, first_name: "CJ", last_name: "Lazell", phone: nil, creator_id: nil, updater_id: nil, deleted_at: "2012-02-17 14:30:26", created_at: "2012-02-17 14:28:45", updated_at: "2012-02-17 14:30:26"> 
1.9.3-p0 :009 > owner.restore!
  DEBUG -  (10.4ms)  BEGIN
  DEBUG -  (0.8ms)  UPDATE `owners` SET `deleted_at` = NULL, `updated_at` = '2012-02-17 14:36:33' WHERE `owners`.`id` = 2
  DEBUG -  (3.0ms)  COMMIT
 => true 

Also if you call .restore! on a just destroyed record you get an error:

1.9.3-p0 :001 > owner = Owner.first
  DEBUG - Owner Load (0.2ms)  SELECT `owners`.* FROM `owners` WHERE `owners`.`deleted_at` IS NULL LIMIT 1
 => #<Owner id: 2, first_name: "CJ", last_name: "Lazell", phone: nil, creator_id: nil, updater_id: nil, deleted_at: nil, created_at: "2012-02-17 14:28:45", updated_at: "2012-02-17 14:28:45"> 
1.9.3-p0 :002 > owner.destroy
  DEBUG - Address Load (3.7ms)  SELECT `addresses`.* FROM `addresses` WHERE `addresses`.`deleted_at` IS NULL AND `addresses`.`location_id` = 2 AND `addresses`.`location_type` = 'Owner' LIMIT 1
  DEBUG -  (0.1ms)  BEGIN
  DEBUG -  (2.0ms)  UPDATE `addresses` SET `deleted_at` = '2012-02-17 14:30:26', `updated_at` = '2012-02-17 14:30:26' WHERE `addresses`.`id` = 1
  DEBUG -  (1.7ms)  COMMIT
  DEBUG -  (0.1ms)  BEGIN
  DEBUG -  (3.4ms)  UPDATE `owners` SET `deleted_at` = '2012-02-17 14:30:26', `updated_at` = '2012-02-17 14:30:26' WHERE `owners`.`id` = 2
  DEBUG -  (0.5ms)  COMMIT
 => #<Owner id: 2, first_name: "CJ", last_name: "Lazell", phone: nil, creator_id: nil, updater_id: nil, deleted_at: "2012-02-17 14:30:26", created_at: "2012-02-17 14:28:45", updated_at: "2012-02-17 14:30:26"> 
1.9.3-p0 :003 > owner.restore!
RuntimeError: can't modify frozen Hash

Do not freeze the deleted object as it breaks interaction with devise

Hi there.

I opened this issue with devise: heartcombo/devise#2266 (comment)

But the issue is actually with paranoia. In the delete method, freeze is called on self. This presents a problem as it conflicts with devise (and probably others) in the handling of other object destruction callbacks.

Do you see any workarounds beyond removing the call to freeze? I was thinking a flag, but that seems messy.

Would appreciate your feedback. Thanks.

About the has_one association problem

catch

My Gemfile file use
gem 'paranoia', :github => 'radar/paranoia', :branch => 'rails4'

User model
has_one :organization, dependent: :destroy
has_many :activities, dependent: :destroy

Organization model
belongs_to :user

Activity model
belongs_to :user

The user has_one organization , and user has_many activities.
Once I restore the user record, execute "User.restore(10, :recursive => true)", all activities are ok, but the organization still missing.
Look at the picture, the query :
SELECT organizations.* FROM organizations WHERE organizations.deleted_at IS NULL AND organizations.user_id = 10 ORDER BY organizations.id ASC LIMIT 1

Why it found the record : 'deleted_at' IS NULL
The correct should be "'deleted_at' IS NOT NULL " ,right?
So, is it a bug?

Paranoia inhibits chaining of multiple #having statements on an AR::Relation

class Person < ActiveRecord::Base
  acts_as_paranoid
end
>  Person.group(:name).having("count(id) > 1").having("count(id) < 1").to_sql
=> "SELECT `people`.* FROM `people`  WHERE `people`.`deleted_at` IS NULL GROUP BY name HAVING count(id) > 1"

upon removing acts_as_paranoid from the model:

>  Person.group(:name).having("count(id) > 1").having("count(id) < 1").to_sql
=> "SELECT `people`.* FROM `people`  GROUP BY name HAVING count(id) > 1 AND count(id) < 1"

I realize the #having statements used here are nonsensical, but hopefully this demonstrates the point. Please let me know if there is anything else I can do to help with this issue. I haven't been able to identify anything in the paranoia implementation that would cause this... but I don't really know how deep scopes, unscoped, etc goes.

Edit: I also should have specified this is with paranoia 2.0.2 and Rails 4.0.0. I did also test against Rails 4.0.3 because initially I thought it might have been an ActiveRecord issue, but the same behavior occurred there.

Gemfile.lock

The Gemfile.lock should probably be in the repository. My newer version of rake is grumpy about the DSL.

NoMethodError: undefined method `only_deleted' for parent model. Does the 'restore' with 'recursive: true' option really take effect in rails4?

pry(main)> Project.find(199).destroy

(0.1ms) BEGIN SQL (0.4ms) UPDATE `publications` SET `publications`.`updated_at` = '2014-07-22 07:15:33', `publications`.`deleted_at` = '2014-07-22 07:15:33' WHERE `publications`.`id` = 549 (21.4ms) COMMIT (0.1ms) BEGIN SQL (0.3ms) UPDATE `projects` SET `projects`.`updated_at` = '2014-07-22 07:15:33', `projects`.`deleted_at` = '2014-07-22 07:15:33' WHERE `projects`.`id` = 199 (23.1ms) COMMIT pry(main)> Project.restore(199, :recursive => true) Project Load (0.6ms) SELECT `projects`.\* FROM `projects` WHERE (`projects`.`deleted_at` IS NOT NULL) AND `projects`.`id` = 199 LIMIT 1 (0.2ms) BEGIN SQL (0.3ms) UPDATE `projects` SET `projects`.`deleted_at` = NULL WHERE `projects`.`id` = 199 Publication Load (2.0ms) SELECT `publications`.\* FROM `publications` WHERE `publications`.`deleted_at` IS NULL AND `publications`.`as_publication_id` = 199 AND `publications`.`as_publication_type` = 'Project' ORDER BY `publications`.`id` ASC LIMIT 1 (32.0ms) ROLLBACK NoMethodError: undefined method `only_deleted' for #Publication:0xdf8ee60

Does not work with audited-activerecord

I created a basic rails app with paranoia and audited-activerecord. I made a model both audited and acts as paranoid. When I try to delete a record, I receive an error that you cannot call create unless the parent is saved (presumably because the audit record is trying to reference the deleted entry).

Has anyone worked around this problem?

validates_uniqueness_of does not use "where deleted_at is null"

It's trivial in Rails 4 for the client code to fix by adding :conditions but that's not an option in Rails 3.

Is there any interest in a fix for this, either by adding a new method (maybe append _without_deleted to the name, or fixing it to work to be consistent with the rest of the queries it modifies?

Update callbacks being triggered on destroy

We're seeing an after_update callback getting triggered on a destroy call. (We're sure it's not something else.)

We've looked through the code, and it seems logical and good.

gems:
paranoia 1.1.0
rails 3.1.6 ( and all the activerecord stuff at the same version)
sqlite3 1.3.5
sqlite3-ruby 1.3.3

overriding getter `def bars` doesn't override `Foo.includes(:bars)`

I'm overriding the getter method to access soft-deleted associations

def bars
Bar.unscoped {super}
end

but it doesn't help when I do Foo.includes(:bars). This doesn't give me the soft-deleted stuff, and so I'll get nil exceptions triggered from foo.bars. But foo.reload.bars works. That seems odd.

Is there a way to do this?

Query containing a join of two paranoia models, only applies paranoia to the parent model

When both a parent model and a related model have acts_as_paranoia, and I query the parent model with a join statement (e.g. ParentModel.joins(:related_models).where(related_models: { [come criteria] })), the constructed SQL query does contain "related_models".deleted_at IS NULL, and thus all destroyed related_models are taken into account in the query.

See pull request for test case.

Before/After really_destroy!

Hi, I have a case where I need to have a before or after destroy callback, but I only want it to run when the paranoid record is REALLY destroyed. The before_destroy callback runs for both soft deletes and real destroys, which is not what I want.

Any idea how this can be done? Thanks in advance!

Rails 4.1 really_destroy! soft deletes the record

Perhaps I'm missing something, however some of my test started failing.

I use paranoia master rails4 branch, I saw really_deleted! and tried to give it a go, unfortunately it seems that on Rails 4.1.0 (4-1-stable) doing so just soft deletes the record, that is, it sets the deleted_at value.

not sure if it helps but here's the debug trace:

=> 145:       destroy!
   146:     end
   147: 
   148:     include Paranoia
   149:     class_attribute :paranoia_column

(byebug) step

[58, 67] in /Users/kain/.rvm/gems/ruby-2.1.1/bundler/gems/paranoia-d657443970a8/lib/paranoia.rb
   58:   # unless you touch the paranoia column before.
   59:   # We need to override it here otherwise children records might be removed
   60:   # when they shouldn't
   61:   if ActiveRecord::VERSION::STRING >= "4.1"
   62:     def destroy!
=> 63:       destroyed? ? super : destroy || raise(ActiveRecord::RecordNotDestroyed)
   64:     end
   65:   end
   66: 
   67:   def delete

(byebug) destroyed?
false
(byebug) step

[78, 87] in /Users/kain/.rvm/gems/ruby-2.1.1/bundler/gems/paranoia-d657443970a8/lib/paranoia.rb
   78:     end
   79:   end
   80:   alias :restore :restore!
   81: 
   82:   def destroyed?
=> 83:     !!send(paranoia_column)
   84:   end
   85:   alias :deleted? :destroyed?
   86: 
   87:   private

(byebug) n
Next went up a frame because previous frame finished

[58, 67] in /Users/kain/.rvm/gems/ruby-2.1.1/bundler/gems/paranoia-d657443970a8/lib/paranoia.rb
   58:   # unless you touch the paranoia column before.
   59:   # We need to override it here otherwise children records might be removed
   60:   # when they shouldn't
   61:   if ActiveRecord::VERSION::STRING >= "4.1"
   62:     def destroy!
=> 63:       destroyed? ? super : destroy || raise(ActiveRecord::RecordNotDestroyed)
   64:     end
   65:   end
   66: 
   67:   def delete

(byebug) s

[48, 57] in /Users/kain/.rvm/gems/ruby-2.1.1/bundler/gems/paranoia-d657443970a8/lib/paranoia.rb
   48:       end
   49:     end
   50:   end
   51: 
   52:   def destroy
=> 53:     callbacks_result = run_callbacks(:destroy) { touch_paranoia_column(true) }
   54:     callbacks_result ? self : false
   55:   end
   56: 
   57:   # As of Rails 4.1.0 +destroy!+ will no longer remove the record from the db

I tried running the paranoia tests locally, and so far just one, possibly not related failure that is also present on travis.

The test for really_destroy! passes in the test suite, I also verified the sql queries it is doing and they seems correct, although I believe I'm testing against 4.0.4.

acts_as_paranoid triggering validations

When I try to delete a record, one of my validations triggers and will not allow acts_as_paranoid to update the deleted_at field, then save. The one validation that triggers looks like this:

validates_datetime :start_date , :on_or_after => Proc.new { Time.zone.now }, :on_or_after_message => 'must be in future', :unless => :use_time_zone

Records where deleted_at is nil are still behaving as if they've been deleted

Hi,

I'm migrating data from a legacy app and some of the models are using the paranoia gem. I've come across some rather weird behaviour which I'm hoping that someone can explain.

The issue is that some records which are (correctly) behaving as if they've been deleted in the legacy app have a deleted_at value of nil. Here's an example:

ruby-1.9.2-p180 :014 > Client.find(93)
ActiveRecord::RecordNotFound: Couldn't find Client with ID=93 [WHERE `clients`.`deleted_at` IS NULL]
ruby-1.9.2-p180 :015 > Client.unscoped.find(93).deleted_at
 => nil 
ruby-1.9.2-p180 :016 > Client.unscoped.find(93).deleted_at.nil?
 => true 

This doesn't seem to be affecting the legacy version of the app โ€“ everything still appears to be behaving as it should โ€“ but it's making it difficult to migrate the data. Because deleted_at is sometimes nil, migrated records which have been deleted from the legacy database are showing up in the new version like some kind of zombie uprising.

Edit: Forgot to mention that the legacy app is running version 1.1.0

can't have a relationship that isn't "paranoic"

I have my model User which is paranoic, and it has many products, but my Product model isn't paranoic.

And when I do @user.products, it generates this query:

"SELECT products.* FROM products.deleted_at IS NULL AND products.user_id = $1"

As you can see it assumes products table has a deleted_at column.

Is there an option to tell the relationship that the related model is not paranoic?

.restore! does not have any effect

With version 1.2.0, calling Thing.only_deleted.find(123456).restore! results in no effect, the SQL that gets run is:

UPDATE "things" SET "deleted_at" = NULL WHERE "things"."deleted_at" IS NULL AND "things"."id" = 123456

It probably has to do with the fixes to the scoping because it worked in previous version.

Boolean flag_column does not work well

class AddDeletedAtToClients < ActiveRecord::Migration
  def change
    add_column :clients, :deleted_at, :datetime
    add_column :clients, :is_deleted, :boolean, null: false, default: 0
    add_index :clients, [:username, :is_deleted], unique: true
  end
end

Use example code in README. If I:
create a new record using
Client.create(username: 'John')
Delete it
Client.first.destroy
Create another record will success
Client.create(username: 'John')

But I will never be able to delete this record unless I change its username
as there is already one record that has username == 'John' and is_deleted == true

I finally managed to do database unique index by creating partial index like this:
add_index :clients, :username, unique: true, where: '"deleted_at" IS NULL'

Validate uniqueness without deleted items

There should be a way to ignore items deleted via paranoia when validating the uniqueness of an model.

Currently, if you soft delete an item with a unique constraint on the model you will not be able to recreate a record with the same value.

Unless I'm missing something...

Add query to determine of a class is paranoid

To be compatible with acts_as_paranoid you need something like this:

class ActiveRecord::Base
def self.paranoid? ; false ; end
end

module Paranoia
def self.included(klazz)
klazz.extend ClassMethods
end

def self.paranoid? ; true ; end

module ClassMethods
def paranoid? ; true ; end
end
end

Please add and release a new gem.

Possible ambiguous column issue for only_deleted

Hello.

Suppose there were 2 database tables named alpha and beta and both had a deleted_at column...

One would get an ambiguous column "deleted_at" if the following were attempted:

Alpha.joins(:beta).only_deleted

Perhaps this:

def only_deleted
  unscoped {
    where("deleted_at is not null")
  }
end

Should be this:

def only_deleted
  unscoped {
    where("#{self.table_name}.deleted_at is not null")
  }
end

Or perhaps I am attempting to use this method in a manner that is not intended.

Version 2.0.2 requires deleted_at in unspecified models

Updated from 2.0.1 to 2.0.2, suddenly we are getting errors asking for deleted_at in models that do not include paranoia e.g.

PG::UndefinedColumn: ERROR: column core_permissions.deleted_at does not exist

Is the gem including itself automatically? Or maybe requiring that associations also has delete_at? I haven't look too much into why this is happening, but for now we are just reverting back to 2.0.1

I will try to investigate this if it isn't anything obvious to you.

Validates uniqueness of attribute(unique index)

I have a problem here: an attribute that the model should validate its uniqueness, but only with non deleted records.

As this issue: #31
This would perfectly solve the problem, but this attribute has an unique index at database. So this solution didn't worked. And the customer does not allow me to change or remove this index.

Any ideas to solve this ?

Alias :unscoped to :with_deleted for more meaningful queries?

If we throw alias :with_deleted :unscoped in there somewhere, I think

Foo.with_deleted.where(...)

makes a ton more sense than

Foo.unscoped.where(...)

because scoping and paranoia are inherently different issues. If you agree, I can submit a pull request.

Paranoia not compatible with Rails 3.0.x

In particular, the default_scoped attribute appears to have been introduced in Rails 3.1, and is not present in Rails 3.0.20, the latest release.

As far as I know right now, this may only affect the only_deleted and with_deleted methods that are added onto paranoid models.

before_destroy callbacks called extra times on destroy

So in my spec suite I have a test similar to this:

@user_3 = create(:user)
expect(@user_3).to receive(:cancel_stripe_subscription).twice.and_return(true)
expect{@user_3.destroy}.to change(UserNotification, :count).by(-1)
expect{@user_3.destroy}.to_not change(UserNotification, :count)

to test the delete modes on associations.
It turns out that this fails, because:

before_destroy :cancel_stripe_subscription

is being called 3 times instead of 2.
This only happens when, as per Readme, attempting to destroy permanently an instance by calling destroy on the same object twice. Using a single destroy or destroy! does not exhibit this behavior.

This is also not happening when the model does not include the paranoia macro (standard AR).

I believe this can lead to uncertain behavior in some apps.

I'm using Rails 4 (4.0.0, 4.0.1, 4.0.2 tested), Paranoia 2.0.1 (both master and rails 4 branches and released gems).

It is possible that the problem is not restricted to only before_destroy callbacks but I have not tested yet.

Including some built-in RSpec shared examples

Would you be interested in including a shared example to make testing use of acts_as_paranoid more simple?

I use the following shared example in my apps to make sure the default scope has been modified as expected.

shared_examples_for 'a paranoid model' do
  describe 'acts_as_paranoid' do
    context 'with the default scope' do
      it 'adds a deleted_at call to all selects' do
        described_class.scoped.where_values_hash.
          should include(deleted_at: nil)
      end
    end

    context 'when unscoped' do
      it 'skips adding the deleted_at condition' do
        described_class.unscoped.where_values_hash.
          should_not have_key(:deleted_at)
      end
    end
  end
end

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.