如何创建在关联模型上运行的查找器条件

时间:2009-01-17 05:54:03

标签: ruby-on-rails

假设rails app有以下型号的歌曲,播放,radio_station: 歌曲has_many播放 玩has_many radio_stations radio_station具有属性“call_sign”

我想为Song创建一个发现,它将为我返回所有歌曲,其中最后一个(歌曲的)播放由“WKRP”广播电台播放

我能做到

found = [] Song.find(:all,:include => [{:plays => [:radio_station]}])。每个歌曲|歌曲|  发现<<歌曲如歌曲.plays.last.radio_station.call_sign ==“WKRP” 端

但这意味着所有的歌曲必须首先从数据库中下载并循环播放......随着歌曲和剧本的构建,这些歌曲会变慢。

如何将其置于一个查找条件中?

看起来这应该是可行的 - 但我无法弄清楚我是怎样的,我不是一个真正的SQL大师......

然后..会喜欢把它挤进一个命名的范围,所以我可以称之为:

Song.find_all_last_played_at( “WKRP”)

5 个答案:

答案 0 :(得分:1)

这可能是ActiveRecord查找器无法完成的。您必须使用子查询(需要MySQL 5)滚动一些自定义SQL:


SELECT s.* FROM songs s LEFT JOIN plays p
 WHERE p.radio_station_id = [id of WKRP] 
   AND  p.created_at = (SELECT MAX(created_at) FROM plays p2 
                         WHERE p2.song_id = s.id)

使用find_by_sql

加载这些歌曲

Song.find_by_sql('SELECT ...')

如果您愿意,可以使用named_scope重构它。



class Song < ActiveRecord::Base
  has_many :plays

  named_scope :last_played_on, lambda { |radio_station| 
    { :conditions => ['plays.created_at = (SELECT MAX(p2.created_at) FROM plays p2 
        WHERE p2.song_id = songs.id) AND plays.radio_station_id = ?', radio_station.id],
      :joins => [:plays] }
  }
end

@wkrp = RadioStation.find_by_name('WKRP') 
Song.last_played_on(@wkrp).each { |song| ... }

请注意,使用子查询可能会很慢。您可能希望将最新的play_id歌曲缓存为songs表中的字段,以使查询更轻松,更快捷。首先在歌曲表中添加last_play_id字段。


class Play < ActiveRecord::Base
  belongs_to :song

  after_create do |play|
    play.song.last_play_id = play.id
    play.song.save!
  end
end 

答案 1 :(得分:0)

您应该查看命名范围。

class Song
  named_scope :last_played_by, lambda {|call_sign| {:conditions => ["plays.radio_stations.call_sign = ?", call_sign]}}
end

您可以这样使用

Song.last_played_by("WKRZ")

然而,我意识到我不知道你用什么机制来找到最后一个游戏。但是你可以把它分成两个命名的范围:

class Song
  named_scope :played_by, lambda {|call_sign| {:conditions => ["plays.radio_station.call_sign = ?", call_sign]}}
  named_scope :last_play {:conditions => ["MAX(plays.created_at)"]
end

使用它:

Song.last_play.played_by("WKRZ")

我认为这是正确的,但我没有测试过,我对命名范围并不熟悉。

答案 2 :(得分:0)

为表现,你应该做什么wvangergen建议。但对于合成糖,我认为这可能有用:

class Song
  has_many :plays, :order => 'created_at' do
    def last_played
      self.maximum(:created-at)
    end
  end
end

class Play
  named_scope :by, lambda {|call_sign| {:conditions => ["radio_stations.call_sign = ?", call_sign]}}
end

使用:

Song.plays.last_played.by("WKRZ")

我没有测试代码,所以对语法错误道歉=)现在我正在阅读它,我认为它不会起作用,但我希望你能够弄明白或某事

答案 3 :(得分:0)

我认为你可以从关联开始做到这一点(注意默认的'order'是'created_at'):

class Song
  has_many :plays
  has_many :radio_staions, :through => :plays, :uniq => true
  has_one  :last_radio_station, :through => :plays, :source => :last_radio_station

  named_scope :last_played_at, lambda {|call_sign| {:include => :last_radio_station, :conditions => ["radio_stations.call_sign = ?", call_sign]}}
end

这需要您的Play模型:

class Play
  has_one :last_radio_station, :class_name => 'RadioStation', :order => 'created_at'
  has_many :radio_stations

然后你可以简单地打电话:

Song.last_played_at("WKRZ")

我自己没有尝试过,但希望它会起作用。

答案 4 :(得分:0)

创建一个named_scope,用于拉出该特定无线电台的所有播放,命令'created_at DESC',然后链.last到返回查询的末尾。沿着:

Song.played_by('WRXD').last