Archive for November, 2007

Exclusive Conditions for ActiveRecord’s has_many

I’ll start with some code.


class User < ActiveRecord::Base
  has_many :things, :exclusive_conditions => %q(`things`.user_id = #{id} OR #{admin?})
end

 

Assuming, we have 2 users, Alice and Bob.


user = User.find_by_username("alice") # => Alice is an admin.
user.id # => 1
thing = user.things.find_by_name("Bob's Thing")
thing.user_id # => 2, It's still bobs thing.

 

Now in my controller’s I can just say something like:


class ThingsController < ApplicationController
  # using restful_authentication or acts_as_authenticated here.
  before_filter :login_required
 
  def update
   @thing = current_user.things.find(params[:id])
   # ...
  end
end

 

Now I don’t have to write any other code to check ownership, AND an admin can
still play with anyone else’s things.

What I’ve done here is added an :exclusive_conditions option to
has_many. It’s not possible to express this idea with the :conditions
option.

Observe:


User.has_many :things, :conditions => ["your_condition = ?", "yes"]

 

Will result in SQL like:


SELECT * FROM things WHERE (user_id = 1) AND (your_condition = ‘yes’)

The “(user_id = 1) AND“ is hard coded. So I can’t say
(user_id = 1) OR (something_else)“ with has_many without writing out
the entire statement using :finder_sql.

But then if I do that, I lose all the fun proxy methods, preventing me from
doing things like user.things.find</tt> or <tt>user.things.create
which are of course, in the scope of that user.

Here’s the plugin, http://subvert.itred.org/has_many_exclusive_conditions/.

attr_protected my left foot.

I don’t like the idea of attr_protected, and attr_accessible in ActiveRecord.

  1. They makes me put more code in the controller, and sometimes worse, I’m forced to express model ideas in the controller.
  1. They make me put things in the model that should be handled by the controller.

From the Rails API docs:

This is meant to protect sensitive attributes from being overwritten by URL/form hackers.

That’s what the freaking controller is for.

Give me param_protected and param_accessible. Instead of:


  class Customer < ActiveRecord::Base
    attr_protected :credit_rating
  end

  class CustomersController < Application
    ...
    def extra_crap_i_dont_need
       ...
       @customer.credit_rating = :foo
    end
  end

 

I want:


  class CustomersController < Application
    param_protected :customer => [:credit_rating, :etc]
  end

If no one else has done it (I haven’t looked yet), perhaps I will.