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


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
  # Prevent objects from being destroyed
  def before_destroy
    raise ActiveRecord::ReadOnlyRecord

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.