Search code examples
ruby-on-railsrubydestroy

Problems with Destroy and Delete functions Rails


I decided to change the destroy function on my app to just switch a Status field to false (The index and show actions only show the results with status: true).

Well, I've changed this, and it worked at first, but now it's just "rolling back" before it complete the commit...

The controller destroy :

def destroy
@customer = Customer.find(params[:id])
@contact = @customer.contacts
@adress = @customer.adresses

if @customer.delete(current_user)
  @contact.each do |f|
    f.delete(current_user)
  end
  @adress.each do |g|
    @telephone = g.telephones
    @telephone.each do |t|
      t.delete(current_user)
    end
    g.delete(current_user)
  end
  flash[:success] = "Cliente excluído"
else
  flash[:danger] = "Erro! O cliente não foi excluído."
end
redirect_to customers_path
end

The model delete :

def delete(user_id)
update_attributes(status: false, changed_by: user_id, deleted_at: Time.now, updated_at: Time.now)
end

Besides that I restricted the :destroy method only to Admin (Is that the right method? Why is it not delete?)

Thanks!


Solution

  • Your issue with the rolling back is likely related to a validation failure. You can find out what this is by changing update_attributes into update_attributes!This will throw an error when the validation fails and you can see what went wrong.

    Apart from that there are a few ways you can improve your code

    First off, rather than manually deleting a customer's contacts and address you can add dependent: :destroy to the relation.

    This works like so:

    class Customer < ActiveRecord::Base
      has_many :contacts, dependent: :destroy
      has_many :adresses, dependent: :destroy
    end
    
    class Adress < ActiveRecord::Base
      has_many :telephones, dependent: :destroy
    end
    

    When you call .destroy on a record it will call .destroy on any relations that have dependent: :destroy on them. .delete will not do this pass through, that is the major difference between them.

    By adding that to the above relations like so you can change your controller to this:

    def destroy
      @customer = Customer.find(params[:id])
      if @customer.destroy
        flash[:success] = "Cliente excluído"
      else
        flash[:danger] = "Erro! O cliente não foi excluído."
      end
      redirect_to customers_path
    end
    

    This will destroy the customer, it's adresses, and the addresses' telephones.

    The non-permanent delete

    To do a soft-delete like you want you should use the Paranoia gem. It lets you assign a class with acts_as_paranoid so when you call .delete or .destroy it isn't really removed, it is just hidden and can be recovered later.

    It is well integrated with Activerecord so you won't need to do things like Customer.where(status: true) to get your non-deleted records, it just won't include deleted records by default. Although you can request them if you need.

    The gem's Github page does a really good job of explaining how to use it.

    By default it won't save the user that performed the deletion, just the time it was performed at. If you want to do that you should create a wrapper that calls delete after assigning the user that performed the deletion.

    Something like this should work:

    def destroy_by(user)
      update_attributes!(changed_by: user)
      destroy
    end
    

    Then you can change @customer.destroy in your controller to @customer.destroy_by(current_user)

    There is no need to override or restrict delete or destroy.