I have a Rails 4.1.7 application, where we have users
and themes
:
class User < ActiveRecord::Base
has_many :themes
end
class Theme < ActiveRecord::Base
belongs_to :user
end
Now, the user can create some own themes (then these themes will have their user_id
set to the user's id
), or can use any of the predefined themes (having user_id
set to null
)
What I would like to do is the following: to somehow change the has_many
association so that when I call @user.themes
, this bring me the predifined themes along with those of the user.
What I have tried:
1) to define an instance method instead of the has_many
association:
class User < ActiveRecord::Base
def themes
Theme.where user_id: [id, nil]
end
end
but since I would like to eager-load the themes with the user(s) (includes(:themes)
), that won't really do.
2) to use some scope (has_many :themes, -> { where user_id: nil }
), but it gives mySql queries like ... WHERE user_id = 123 AND user_id IS NULL
, which returns empty. I guess with Rails5 I could do it with something like has_many :themes, -> { or.where user_id: nil }
, but changing the Rails version is not an option for now.
Since posting my question, I tried many things to achieve my goal. One was interesting and, I guess, worth to be mentioned:
I tried to unscope the has_many
assiciation using unscope or rewhere, and it looked like this:
has_many :themes, -> user = self { # EDIT: `= self` can even be omitted
u_id = user.respond_to?(:id) ? user.id : user.ids
unscope(where: :user_id).where(user_id: [u_id, nil])
# or the same but with other syntax:
rewhere(user_id: [u_id, nil])
}
When I tried @user.themes
, it worked like wonder, and gave the following mySql line:
SELECT `themes`.* FROM `themes`
WHERE ((`themes`.`user_id` = 123 OR `themes`.`user_id` IS NULL))
But when I tried to eager load it (why I started my research after all), it simply refused to unscope the query, and gave the same old user_id = 123 AND user_id = NULL
line.
After all, @Ilya's comment convinced me along with this answer, that it is one thing to use the has_many
for querying, but it has other sides, like assigning for example, and overriding it for one's sake can spoil many other things.
So I decided to remain with my nice method, only I gave it a more specific name to avoid future confusion:
def available_themes
Theme.where user_id: [id, nil]
end
As for @AndreyDeineko's response - since he continually refuses to answer my question, always answering something that has never been asked -, I still fail to understand why his method (having the same reults as my available_themes
, but using 3 additional queries) would be a better solution.