Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RoR - Don't destroy object, just flag as hidden

I have a simple model in RoR and I would like to keep eveything people enter on the site. But I also want to be able to hide some content if the user click on "Remove".

So I added a bolean attribute in my model called "displayed".

I would like to know, what would be the best-practices-styled method.

I guess I have to change the controller with something like :

def destroy
 @point = Point.find(params[:id])
 @point.displayed = false
 @point.save

respond_to do |format|
  format.html { redirect_to points_url }
  format.json { head :no_content }
end

But I am not sure it is clean. What would be the best way to do it.

As you guess I am noobish with RoR. Chunks of code would be appreciated.

Thank you

like image 729
Paul Etienney Avatar asked Feb 05 '13 23:02

Paul Etienney


3 Answers

Implement it yourself (rather than using a gem). It's much, much easier than it seems at first, and it's less complex than any of the gems out there that change the meaning of the destroy method, which is a bad idea, in my opinion.

I'm not saying that using the gems themselves are complex - I'm saying that by changing the meaning of the destroy method you're changing the meaning of something that people in the Rails world take for granted - that when you call destroy that record is going to go away and that destroy maybe also be called on dependent objects if they are chained together via dependent: destroy callbacks.

Changing the meaning of destroy is also bad because in the "convention over configuration" world, when you screw with conventions you're essentially breaking the "automagic-ness" of your Rails code. All that stuff you take for granted because you read a piece of Rails code and you know that certain assumptions generally apply - those go out the window. When you change those assumptions in ways that aren't obvious you're almost certain to introduce a bug down the line because of it.

Don't get me wrong, there's nothing better than actually reading the code for checking your assumptions, but it's also nice, as a community, to be able to talk about certain things and generally have their behavior act in a certain way.

Consider the following:

  • There's nothing in Rails that says you have to implement the destroy action in the controller, so don't. It's one of the standard actions, but it's not required.
  • Use the update action to set and clear an archived boolean attribute (or something similarly named)
  • I've used the acts_as_paranoid gem, and if you need to add any scopes to your models (other than the ones the gem provides) you're going to find yourself having to hack your way around it, turning off the default "hide archived records" scope, and when you run into that it almost immediately loses its value. Besides, that gem does almost nothing on its own, and its functionality could easily be written yourself (and I mean barely more work than installing the gem itself), so there's really no benefit to using it from that perspective.
  • As previously stated, overriding the destroy method or action is a bad idea because it breaks the Rails (and ActiveRecord) convention as to what it means to call destroy on an object. Any gem that does this (acts_as_paranoid for example) is also breaking that convention, and you're going to wind up confusing yourself or someone else because destroy simply won't mean what it's supposed to mean. This adds confusion, not clarity to your code. Don't do this - you'll pay for it later.
  • If you want to use a soft-delete gem because you are protecting against some theoretical, future developer who might hork up your data...well, the best solution to that is not to hire or work with those people. People that inexperienced need mentorship, not a gem to prevent them from making mistakes.
  • If you really, absolutely, must prevent destroying a record of a given model (in addition to being able to simply archive it), then use the before_destroy callback and simply return false, which will prevent it from being destroyed at all unless an explicit call to delete is used (which isn't the same as destroy anyway). Also, having the callback in place makes it (a) really obvious why destroy doesn't work without changing its meaning, and (b) it's easy to write a test to make sure it's not destroyable. This means in the future, if you should accidentally remove that callback or do something else that makes that model destroyable, then a test will fail, alerting you to the situation.
like image 185
jefflunt Avatar answered Oct 30 '22 14:10

jefflunt


Something like this:

class Point < ActiveRecord::Base

  def archive        
    update_attribute!(:displayed, false)
  end 

end

And then call @point.archive in the destroy action of your controller where you would normally call @point.destroy. You can also create a default_scope to hide archived points until you explicitly query for them, seethe RoR guide on appling a default scope.

Edit: Updated my answer as per normalocity & logan's comments below.

like image 31
Noz Avatar answered Oct 30 '22 16:10

Noz


Look at the acts_as_archive gem. It will do soft deletes seamlessly.

like image 20
Shane Andrade Avatar answered Oct 30 '22 16:10

Shane Andrade