zobie's blog I create software, I like music, and I'm mildly(?) OCD.

1Jan/09

Read-only Models in ActiveRecord

I recently worked on a project that was built around several hundred GB of data. Several large databases were populated by one team and then consumed by several other applications.

The project I was working on needed to access that data but would never need to modify it. Perhaps I was being overly paranoid, but I wanted to specify that everything from those databases was read-only to make sure nothing was accidently changed.

ActiveRecord is one of the coolest things about rails. With almost no work I get data models, including dead-simple CRUD, in my applications. But the inability to designate a model as being read-only is really frustrating to me.

I know that rails can mark individual instances with :readonly, but I wanted to ensure that every instance of these objects was read-only. Here's what I came up with:

class FooBar < ActiveRecord::Base
  # Prevent creation of new records and modification to existing records
  def readonly?
    return true
  end
 
  # Prevent objects from being destroyed
  def before_destroy
    raise ActiveRecord::ReadOnlyRecord
  end
end

And that's all there is to it! FooBar objects are now read-only.

# You will not be able to create new records
>> fb = FooBar.create(:name => "zork")
ActiveRecord::ReadOnlyRecord: ActiveRecord::ReadOnlyRecord
 
# You will not be able to save modified records
>> fb = FooBar.find(:first)
>> fb.name = "plugh"
>> fb.save
ActiveRecord::ReadOnlyRecord: ActiveRecord::ReadOnlyRecord
 
# You will not be able to destroy existing records
>> fb = FooBar.find(:first)
>> fb.destroy
ActiveRecord::ReadOnlyRecord: ActiveRecord::ReadOnlyRecord

For the sake of simplicity the code here is in a regular rails model. But if you're working with a large number of models like I was, you might consider extracting these methods into a new class "ReadOnlyActiveRecord" which can then used as the parent for the other models. DRY, right?

Depending on your situation, you may need to lock down the record more tightly. With this code you could still create and manipulate new FooBar objects in your code. The exception isn't thrown until you try to persist to the database.

I certainly don't consider myself an expert on rails. If anyone knows a better solution to this problem I'd love to hear it.

Comments (6) Trackbacks (0)
  1. Thanks for this post! It came up on a Google search for “activerecord and readonly”. I have a situation where a proposal is generated, and after that, several records need to be locked down. This is perfect for that!

  2. Thanks,

    I’m using this with a materialized view that gets updated by plpgsql database functions and triggers.

  3. Thanks for this post. I was doing something quite similar and found that you can still call delete and delete_all, as they do not use callback methods. You can override those methods on your read only model and raise the ReadOnly exception in the exact same way:


    def self.delete_all
    raise ActiveRecord::ReadOnlyRecord
    end

    def delete
    raise ActiveRecord::ReadOnlyRecord
    end

  4. why not ensure read only access on the db layer through proper user rights?
    this would be more secure (your solution does not cover native sql)

    pascal

    • I agree that enforcing constraints at the database level is a good approach. For the specific project I was working on when I wrote this article, I did not have access to make changes to the database itself. Because I tend to be paranoid, I wanted some assurance that data wasn’t accidentally modified. 🙂


Trackbacks are disabled.