Search code examples
ruby-on-railsrubybeforeupdate

Rails preventing update to certain values in a model


In my app I have note.rb with the schema shown below. What I am trying to do is restrict the ability to edit notes. Basically if the note has been created in the last 24 hours, you are allowed to update whatever you want. After 24 hours, all you can do is change the remind_at time. I have a method in the note.rb file that checks if the note is editable. If it is I display different partials. But that only restricts the front end, if someone has left the edit window open, or types in the URL, they can still edit the note after 24 hours.

What I am attempting to do is write a before_update method that checks if you are allowed to update the body of the note or not. No matter what you can update the remind_at time. Part of the problem is that the way the controller knows whether or not this is simply a change to the remind_at time is via params[:reschedule] and params are not accessible in the model.

# == Schema Information
#
# Table name: notes
#
#  id                :integer          not null, primary key
#  body              :text
#  remind_at         :datetime
#  created_at        :datetime
#  updated_at        :datetime

# Only allow editing of notes that are less that 1 day old
  def editable?
    self.created_at > (Time.now-1.day)
  end

Solution

  • I don't understand, the model shouldn't have to look at params[:reschedule] to know if it's "simply a change to the remind_at time" -- the model is the thing being changed, it should be able to look at what's actually being changed and make sure it's only remind_at.

    Looking at params[:reschedule] wouldn't be right even if you could easily do it -- as conceivably params[:reschedule] could be set at the same time other attributes were being modified, and you wouldn't want to allow that either. The only point to doing this in the model is not to trust the controller is doing the right thing (and has no security holes), but to ensure that only what's actually allowed is being done.

    Which if I understand right, what's allowed is changing no attributes but remind_at, if created_at is past a certain time.

    So what you really need to do is just look at what attributes have changed in a validation hook?

    I believe the changed method should work to do that. It return a list of changed attributes since the last save. If the post is too old, you want to make sure they include include nothing but remind_at.

    Does that work?

     if self.created_at > (Time.now-1.day) &&
        self.changed.find {|k| k.to_s != "remind_at"}
             # BAD, they're trying to save some other attribute changed
     end
    

    http://api.rubyonrails.org/classes/ActiveModel/Dirty.html#method-i-changed