views:

332

answers:

4

Hi,

I'm working on a multi-user, multi-account App where 1 account can have n users. It is very important that every user can only access info from its account. My approach is to add an account_id to every model in the DB and than add a filter in every controller to only select objects with the current account_id. I will use the authorization plugin.

Is this approach a good idea?

What is the best way to always set the account_id for every object that is created without writing

object.account = @current_account

in every CREATE action? Maybe a filter?

Also I'm not sure about the best way to implement the filter for the select options. I need something like a general condition: No matter what else appears in the SQL statement, there is always a "WHERE account_id = XY".

Thanks for your help!

+4  A: 

This is similar to a User.has_many :emails scenario. You don't want the user to see other peoples emails by changing the ID in the URL, so you do this:

@emails = current_user.emails

In your case, you can probably do something like this:

class ApplicationController < ActionController::Base
  def current_account
    @current_account ||= current_user && current_user.account
  end
end

# In an imagined ProjectsController
@projects = current_account.projects
@project = current_account.projects.find(params[:id])
August Lilleaas
Well, I could do that and use my current_account method, but than I have to change every action. Isn't there an easier way? I even thought about patching the find / create method, because I'm always setting the same value (for one logged in user).
ole_berlin
Doing it in every action is not a problem. It's a one-time only effort, and it takes you, what, 5 minutes? It also makes your code easy to understand -- it's right there, you don't have to look at the abstractions.
August Lilleaas
It is a little more than 5 min, but after a lot of hassle and monkeypathing with my solution I will just do it the rails way. Thanks.
ole_berlin
A: 

I know, I know, if you access Session-variables or Instance variables in your Model you didn't understand the MVC pattern and "should go back to PHP". But still, this could be very useful if you have - like us - a lot of controllers and actions where you don't always want to write @current_account.object.do_something (not very DRY).

The solution I found is very easy:

Step 1: Add your current_account to Thread.current, so for example

class ApplicationController < ActionController::Base
  before_filter :get_current_account      

  protected
   def get_current_account
     # somehow get the current account, depends on your approach
     Thread.current[:account] = @account
   end
end

Step 2: Add a current_account method to all your models

   #/lib/ar_current_account.rb
   ActiveRecord::Base.class_eval do
     def self.current_account
      Thread.current[:account]
     end
   end

Step 3: Voilá, in your Models you can do something like this:

class MyModel < ActiveRecord::Base

  belongs_to :account

  # Set the default values
  def initialize(params = nil) 
    super
      self.account_id ||= current_account.id
  end

end

You could also work with something like the before_validation callback in active_record and then make with a validation sure the account is always set.

The same approach could be used if you always want to add the current_user to every created object.

What do you think?

ole_berlin
As I commented above, stating it in every action is not a problem. It's a one-time job, and it doesn't take long.
August Lilleaas
+1  A: 

To answer your second question, check out the new default_scope feature in Rails 2.3.

John Topley
great, exactly what I needed!
ole_berlin
A: 

I understand that you don't want to bother about scoping you account all time. Lets be honest, it's a pain in the a**.

To add a bit magic and have this scoping done seamlessly give a look at the following gem

http://gemcutter.org/gems/account%5Fscopper

Hope this helps,

-- Sebastien Grosjean - ZenCocoon

Sébastien Grosjean - ZenCocoon
Some combination of SO / Firefox means that link doesn't work for me - it 404s. Alternative link to the same location: http://is.gd/4SyvE
NeilS
Thanks for the url fix NeilS.
Sébastien Grosjean - ZenCocoon