2

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.

Misu
  • 441
  • 5
  • 15
  • I think that it is a bad idea to rewrite association, because `Theme` without any user specified in fact belongs to each user (many users), it is not a `has_one - has_many` relation. I would recommend to using the other method to achieve your goal. – Ilya Nov 16 '16 at 10:37
  • Yeah, that's what I'm using now. Only, it would be so much nicer to eager load it sometimes... – Misu Nov 16 '16 at 12:13

1 Answers1

-1

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.

Community
  • 1
  • 1
Misu
  • 441
  • 5
  • 15