【问题标题】:Rails: How to set up an IF condition with a JOIN in a has_many :through relationshipRails:如何在 has_many 中使用 JOIN 设置 IF 条件:通过关系
【发布时间】:2012-07-16 08:45:27
【问题描述】:

我有一个应用程序,用户可以在其中自定义日历并用给定的事件池填充它。用户还可以用别名覆盖他自己的日历的标题。所以我有以下 has_many :through 关系:

class Calendar < ActiveRecord::Base
    has_many :event_aliases
    has_many :events, :through => :event_aliases
end

class Event < ActiveRecord::Base
    attr_accessible :title

    has_many :event_aliases
    has_many :calendars, :through => :event_aliases
end

class EventAliases < ActiveRecord::Base
    attr_accessible :course_id, :calendar_id, :custom_name

    belongs_to :event
    belongs_to :calendar
end

不,我想提供带有别名的日历。如果事件有别名 (custom_name),则应该显示它。否则应显示默认事件名称 (title)。

有没有一种方法可以轻松设置查询以返回当前日历的所有事件,无论是使用custom_name(如果存在)还是使用默认的title

我当前的解决方案是将 if 条件硬编码到我想避免的查询中。

title_column = "case when custom_name IS NOT NULL then custom_name else title end as title"

# assume we are given a calendar_id
Calendar.find(calendar_id).event_aliases.joins(:event).select(title_column, :event_id).each do |event_alias|
    # do further stuff here
end

如果需要,我还可以获取所有 event_aliases 并遍历它们以获取默认的 title

# assume we are given a calendar_id
Calendar.find(calendar_id).event_aliases.each do |event_alias|
    title = event_alias.custom_name
    if title.nil?
        title = Event.find(event_alias.event_id).title
    # do further stuff here
end

但是这个结果给我带来了太多的查询。

那么有没有更聪明的方法来完成我想要的?也许使用命名范围或其他花哨的 Rails 技术?

更新

我最终通过 has_many :through 关系进行了“自定义”选择。所以唯一改变的是Calendar 模型:

class Calendar < ActiveRecord::Base
    has_many :event_aliases
    has_many :events, :through => :event_aliases,
             :select => "event_aliases.custom_name as custom_name, events.*"
end

所以现在访问custom_name / title 有点像@Doon 建议的那样:

Calendar.find(1).courses.each do |course|
    title = course.custom_name || course.title
end

这只会创建 2 个查询而不是 3 个:

  Calendar Load (0.6ms)  SELECT `calendars`.* FROM `calendars` WHERE `calendars`.`id` = 1 LIMIT 1
  Event Load (0.7ms)  SELECT event_aliases.custom_name as custom_name, events.* FROM `events` INNER JOIN `event_aliases` ON `events`.`id` = `event_aliases`.`event_id` WHERE `event_aliases`.`calendar_id` = 1

【问题讨论】:

    标签: sql ruby-on-rails join has-many-through


    【解决方案1】:

    在提取别名的同时使用includes 来获取事件怎么样。

     Calendar.find(1).event_aliases.includes(:event).each do  |e| 
         puts  e.custom_name.blank? ? e.event.title : e.custom_name
     end 
    

    Rails 生成的 SQL 将如下所示:

     Calendar Load (0.2ms)  SELECT "calendars".* FROM "calendars" WHERE "calendars"."id" = ?      LIMIT 1
     EventAlias Load (0.2ms)  SELECT "event_aliases".* FROM "event_aliases" WHERE "event_aliases"."calendar_id" = 1
     Event Load (0.2ms)  SELECT "events".* FROM "events" WHERE "events"."id" IN (1, 2)
    

    如果你想稍微清理一下,你可以在 EventAlias 中添加一个虚拟字段

    class EventAlias < ActiveRecord::Base
    
      def name
        custom_name || self.event.title
      end
    
    end
    

    只要您使用包含,查询将是相同的。

    【讨论】:

    • 感谢您的提示。好吧,includes() 解决方案与joins() 解决方案没有太大区别。在您的解决方案中,您只需在 Ruby 中而不是 SQL 中创建 if 条件。我想出了另一种受此影响的解决方案,但需要的查询更少。
    猜你喜欢
    • 2012-08-03
    • 1970-01-01
    • 1970-01-01
    • 2012-06-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-09
    • 1970-01-01
    相关资源
    最近更新 更多