hacking at associations in role-based systems
Courtenay : November 19th, 2006
Your application, ContrivedExample (beta!), has a variety of roles, one being administrator. Your projects are associated with one user (the owner of the project). Users can only view their own projects; however, the admin can view all projects. You want to use the simplest interface possible, the standard has_many association, so you can paginate, and keep the logic in your models.
First, a test case
class UserTest < Test::Unit::TestCase
def test_user_project_association_exists
user = User.find(5)
assert user.respond_to?(:projects)
end
def test_project_count_per_role
project_count = Project.count
user = NormalUser.find :first
assert user.is_a?User
# normal users should not have access to the entire set of projects
assert_not_equal project_count, user.projects.size
# admin users should be able to view all projects
user = AdminUser.find :first
assert user.is_a?User
assert_equal project_count, user.projects.size
end
end
What does this test suite tell us? First, users all inherit from User; second, user/projects have a common interface, but the result count differs per-role. How do we achieve this simply?
class User < ActiveRecord::Base
end
class NormalUser < User # STI
has_many :projects
end
user.projects.find(:all, :limit => 5, :offset => params[:page])
class Admin < User
???
end
The easy solution is to define some finder sql and extend the association's methods with the splat.
class Admin < User
has_many :projects, :finder_sql => 'select * from projects' do
def find(*args)
Project.find(*args)
end
end
end
user = User.find(3)
user.projects # success!
For homework, create a proc that you just :extend, so it can be shared around like a cheap floozy.
7 Responses to “hacking at associations in role-based systems”
Sorry, comments are closed for this article.
November 19th, 2006 at 03:47 PM
Nifty, though the tests don’t quite match the prose. The assert_not_equal is an interesting trap. It’ll pass (or continue to pass) if a user gets the wrong projects (or no projects, instead of the expected ones).
November 20th, 2006 at 09:57 AM
I guess this one went a little above my head. What is the purpose of the block attached to has_many?
Also, wouldn’t your test cast fail in instances where you only have 1 regular user (who has 1 project).
November 20th, 2006 at 09:57 AM
I guess this one went a little above my head. What is the purpose of the block attached to has_many?
Also, wouldn’t your test cast fail in instances where you only have 1 regular user (who has 1 project).
November 20th, 2006 at 04:26 PM
it allows you to do admin.projects.find(:all, :conditions…) which (in my quick testing) doesn’t work with finder_sql.
Yes, it’ll fail in that case, but we don’t normally have just one item in the fixtures table.
November 21st, 2006 at 07:11 AM
Hey man, I don’t know how else to contact you. Feel free to delete this comment after reading it.
We’re looking for a Rails contractor and wasn’t sure if you do that sort of thing. If you do, use the email I attached to this comment to contact me.
Thanks!
November 24th, 2006 at 02:42 PM
sorry, not available right now :)
May 18th, 2007 at 10:11 AM
Thank you! I’ve been Googling around for an elegant solution to my finder problems with such a situation for a few hours. This post elicited an ‘Oh! Brilliant!’ from me.