The business logic is this: Users are in a Boat through a join table, I guess let’s call that model a Ticket. But when a User instance wants to check who else is on the boat, there’s a condition that asks if that user has permission see everyone on the Boat, or just certain people on the Boat. If a User can see everyone, the normal deal is fine: some_user.boats.first.users returns all users with a ticket for that boat. But for some users, the only people that are on the boat (as far as they’re concerned) are people in, let’s say the dining room. So if User’s ticket is “tagged” (using an acts_as_taggable style system) with “Dining Room”, the only Users returned from some_user.boats.first.users should be Users with tickets tagged “Dining Room”.
Just for the record, I’m not trying to design something to be insane from the getgo – I’m trying to wedge this arbitrary grouping into a (mostly) existent system.
So we’ve got:
class User
has_many :tickets
has_many :boats, :through => :tickets
end
class Ticket
belongs_to :user
belongs_to :boat
end
class Boat
has_many :tickets
has_many :users, :through => :tickets
end
Initially, I thought that I could conditionally modify the virtual class like:
singleton = class << a_user_instance ; self ; end
singleton.class_eval(<<-code
has_many :tickets, :include => :tags, :conditions => ['tags.id in (?)', [#{tag_ids.to_s(:db)}]]
code
)
That gets all the way down to generating the SQL, but when generated, it generates SQL ending in:
LEFT OUTER JOIN "tags" ON ("tags"."id" = "taggings"."tag_id") WHERE ("tickets"._id = 1069416589 AND (tags.id in (5001,4502)))
I’ve tried digging around the ActiveRecord code, but I can’t find anywhere that would prefix that ‘id’ in the SQL above with an underscore. I know that associations are loaded when an ActiveRecord class is loaded, and I’d assume the same with a singleton class. shrug.
I also used an alias_method_chain like:
singleton = class << a_user_instance ; self ; end
singleton.class_eval(<<-code
def tickets_with_tag_filtering
tags = Tag.find(etc, etc)
tickets_without_tag_filtering.scoped(:include => :tags, :conditions => {:'tags.id' => tags})
end
alias_method_chain :tickets, :tag_filtering
code
)
But while that approach produces the desired Tickets, any joins on those tickets use the conditions in the class, not the virtual class. some_user.boats.first.users returns all users.
Any type of comment will be appreciated, especially if I’m barking up the wrong tree with this approach. Thanks!
So a wild guess about your underscore issue is that Rails is generating the assocation code based on the context at the time of evaluation. Being in a singleton class could mess this up, like so:
You could get in there and define a class name property on your singleton class and see if that fixes the issue.
On the whole I don’t recommend this. It creates behavior that is agonizing to track down and impossible to extend effectively. It creates a landmine in the codebase that will wound you or someone you love at a later time.
Instead, consider using a
named_scopedeclaration:While you may have to go back and edit some code, this is much more flexible in the ways it can be used:
It’s clear that a restriction is being placed on the find, and what the purpose of that restriction is. Because the conditions are dynamically calculated at runtime, you can deal with the next weird modification your client hits you with. Say some of their users have xray vision and clairvoyance:
By returning an empty hash, you can effectively nullify the effect of the scope.