I must throw my hands up and declare I'm completely stumped on this one!
I have the following models:
Chat:
has_many :messages
Message:
belongs_to :chat, :counter_cache => true
belongs_to :authorable, :polymorphic => true
User:
has_many :messages, :as => :authorable
has_many :chats, :through => :messages, :uniq => true
Guest:
has_many :messages, :as => :authorable
I'm trying to write a named_scope on Chats to give me "unanswered" chats (where unanswered means there aren't any messages for that chat posted by a User)开发者_运维问答 - so far I've only managed to go round in a lot of circles!
Any help would be very much appreciated! fwiw I'm not especially attached to it being a named scope if it makes it easier (or even possible!)
Thanks, Ash
The key to named scopes is to place them in the model that they'll return. To get unanswered chats, your named scope should go in your chat model. Unfortunately you're not making it easy by looking for cases where the association is empty.
Doing that with a named scope involves a LEFT/RIGHT OUTER join and a GROUP_BY operator. It's not going to pretty and it's no better than writing your own SQL
You might find it easier to use a counter cache. However your polymorphic association may mean that a straight counter cache won't work either.
The question was a little unclear, are unanswered chats those with no messages what so ever or just those without messages with users (chats with messages authored only by guests are still considered unanswered?
If it's the former than a simple counter cache will do, other wise you'll have a little more work to do.
Common code for both cases:
Add a column to the chats table called message_count with this migration:
class AddCounterCache < ActiveRecord::Migration
def self.up
add_column :chats, :message_count, :integer, :default => 0
end
def self.down
remove_column :chats, :message_count
end
end
Then create the named scope in the Chat model.
class Chat < ActiveRecord::Base
...
named_scope :unanswered, :conditions => {:message_count => 0}
end
Unique code for the case where an unanswered chat has 0 messages
class Message < ActiveRecord::Base
belongs_to :chat, :counter_cache => true
end
Unique code for the case where an unanswered chat can have messages authored by guests, but not users:
We only want the counter cache to be updated in certain circumstances, so we need to override the method that ActiveRecord uses to increment the counter cache so that it only triggers when we want it to. Rails provides a handy way of renaming methods and wrapping them in others through ActiveSupport's alias_method_chain. So this code creates new methods that trigger the existing methods used to update the counter cache only in the cases where they are necessary. Then alias_method_chain is used to rename the methods so that our new methods are called in place of the ones supplied by ActiveRecord.
class Message < ActiveRecord::Base
belongs_to :chat, :counter_cache => true
def belongs_to_counter_cache_after_create_for_chat_with_users_only
if authorable_type == "User"
belongs_to_counter_cache_after_create_for_chat_without_users_only
end
end
def belongs_to_counter_cache_before_destroy_for_chat_with_users_only
if authorable_type == "User"
belongs_to_counter_cache_before_destroy_for_chat_without_users_only
end
end
alias_method_chain :belongs_to_counter_cache_before_destroy_for_chat, :users_only
alias_method_chain :belongs_to_counter_cache_after_create_for_chat, :users_only
end
Once all that's done. Chat.unanswered
will list all chats that meets your criteria. You also get the bonus of not needing a second query to fetch the number of messages in in chat.
you could define the named_scope on the has_many model itself.
Chat.messages.unanswered
Or, you could write the named_scope in SQL so you could do Chat.unanswered, but that feels like the wrong api to me.
more examples: http://blog.peelmeagrape.net/2008/6/21/named_scope-is-super-awsome-with-has_many
精彩评论