Faking Fantastic
Confessions of a Web Developing Phony

ReadOnly needs to be set to true or false, not :true or :false

Just taking some time to let you in on another gotcha. Hopefully, this saves people some aggravation some day.

While working on Kludge, we took a RESTful approach and built our controller like so:

1
2
3
4
5
class ProjectsController < ApplicationController
  def update
    @project = Project.find(params[:id]).update_attributes(params[:project])
  end
end

While this works, it doesn’t offer you any protection that evil users may try to update projects that don’t belong to them. A better approach is to scope the projects to that belong to the account. So we updated the action to the following:

1
2
3
4
5
class ProjectsController < ApplicationController
  def update
    @project = current_account.projects.find(params[:id]).update_attributes(params[:project])
  end
end

…and we got an error!

1
2
ActiveRecord::ReadOnlyRecord (ActiveRecord::ReadOnlyRecord):
  app/controllers/projects_controller.rb:45:in `update'

This error arises because Rails makes all associations read-only. Since projects is now an association to account, we need to force readonly to false in the account model:

1
2
3
4
class Account < ActiveRecord::Base
  has_many :clients
  has_many :projects, :through => :clients, :readonly => :false
end

However, this will still throw the same error message! After googling, IRC-ing, and crying … I finally got the answer through brute-force trial and error. Turns out, :readonly does not accept the symbol :false, and only the literal false. Changing the following will make your test green:

1
2
3
4
class Account < ActiveRecord::Base
  has_many :clients
  has_many :projects, :through => :clients, :readonly => false # <-- no longer a symbol
end