Porting complicated has_many relationships to Rails >4.1 (without finder_sql)

纵然是瞬间 提交于 2019-12-30 06:53:51

问题


I am porting a Rails app to Rails 4.2. This Rails app contains some rather complex manual SQL code in associations - partly due to DB optimizations (e.g. subselects instead of JOINs), partly due to no feasible alternative at the time of writing (Rails 3.0), partly surely due to lack of knowledge (I hope, at least - that would be easy to solve).

Example: An InternalMessage class. Messages can be sent between users (Recipients of an InternalMessage, and 'deletions' of messages, are stored in InternalMessagesRecipients, since there can be several) and they can be read, replied to, forwarded and deleted. The association looks like this:

class User < AR::Base
  has_many :internal_messages,
      :finder_sql => "SELECT DISTINCT(internal_messages.id), internal_messages.* FROM internal_messages " +
          ' LEFT JOIN internal_messages_recipients ON internal_messages.id=internal_messages_recipients.internal_message_id' +
          ' WHERE internal_messages.sender_id = #{id} OR internal_messages_recipients.recipient_id = #{id}',
      :counter_sql => 'SELECT count(DISTINCT(internal_messages.id)) FROM internal_messages ' +
          ' LEFT JOIN internal_messages_recipients ON internal_messages.id=internal_messages_recipients.internal_message_id' +
          ' WHERE internal_messages.sender_id = #{id} OR internal_messages_recipients.recipient_id = #{id}'
  # ...
end

The key part is the "OR" clause at the end - with this association I want to get both received and sent messages, which are joined with the user table seperately:

  has_many :sent_messages, -> { where(:sender_deleted_at => nil) }, :class_name => 'InternalMessage', :foreign_key => 'sender_id' #, :include => :sender
  has_many :internal_messages_recipients, :foreign_key => 'recipient_id'
  has_many :rcvd_messages, :through => :internal_messages_recipients,  :source => :internal_message, :class_name => 'InternalMessage'

since an InternalMessage might have several recipients (and can also be sent to the sender himself).

Q: How do I port this finder_sql to a Rails 4.2 compatible has_many definition?


回答1:


Update

I learnt a while ago that this makes no sense. A has_many relationship must have injective connections at least in one direction, so an "OR" in a SQL clause makes no sense. How should a CREATE operation decide which condition to satisfy to create a new record? This relationship is read only by definition and so it is not a has_many relationship.

In this case, a simple class method (or scope) would be the right answer instead of has_many. To concatenate results from several queries use something like

def internal_messages
  InternalMessage.where( id: sent_message_ids + received_message_ids)
end

to keep the resulting object chainable (i.e. @user.internal_messages.by_date etc.)




回答2:


Pass the proc that contains the SQL string as scope.

has_many :internal_messages, -> { proc { "SELECT DISTINCT(internal_messages.id), internal_messages.* FROM internal_messages " +
      ' LEFT JOIN internal_messages_recipients ON internal_messages.id=internal_messages_recipients.internal_message_id' +
      ' WHERE internal_messages.sender_id = #{id} OR internal_messages_recipients.recipient_id = #{id}' } }


来源:https://stackoverflow.com/questions/28009768/porting-complicated-has-many-relationships-to-rails-4-1-without-finder-sql

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!