仅检索具有多个请求的唯一记录

时间:2015-02-04 19:11:54

标签: ruby-on-rails ruby sorting activerecord filtering

我有这个" heavy_rotation"过滤我正在努力。基本上它根据某些参数(listens_count,staff_pick,purchase_count,仅举几例)的混合物从我们的数据库中抓取曲目。

对filter_tracks控制器操作发出xhr请求。在那里,我有一个标志,以检查它是否" heavy_rotation"。我可能会把这个移到模型上(因为这个控制器变胖了)...无论如何,我怎样才能确保(以有效的方式)不让它拉相同的记录?我考虑过偏移量,但是我必须跟踪每个查询的偏移量。或者可以存储track.id以进行每个查询的比较?有任何想法吗?我很难想出一个优雅的方法来做这件事。

也许应该注意,通过Javascript设置限制为14,并且当用户点击"查看更多"为了分页,它向filter_tracks发送另一个请求。

任何帮助表示赞赏!谢谢!

def filter_tracks

    params[:limit] ||= 50
    params[:offset] ||= 0
    params[:order] ||= 'heavy_rotation'

    # heavy rotation filter flag
    heavy_rotation ||= (params[:order] == 'heavy_rotation')

    @result_offset = params[:offset]
    @tracks = Track.ready.with_artist

    params[:order] = "tracks.#{params[:order]}" unless heavy_rotation

    if params[:order]
      order = params[:order]
      order.match(/artist.*/){|m|
        params[:order] = params[:order].sub /tracks\./, ''
      }
      order.match(/title.*/){|m|
        params[:order] = params[:order].sub /tracks.(title)(.*)/i, 'LOWER(\1)\2'
      }
    end

    searched = params[:q] && params[:q][:search].present?

    @tracks = parse_params(params[:q], @tracks)

    @tracks = @tracks.offset(params[:offset])

    @result_count = @tracks.count

    @tracks = @tracks.order(params[:order], 'tracks.updated_at DESC').limit(params[:limit]) unless heavy_rotation

    # structure heavy rotation results
    if heavy_rotation

      puts "*" * 300

      week_ago = Time.now - 7.days
      two_weeks_ago = Time.now - 14.days
      three_months_ago = Time.now - 3.months


      # mix in top licensed tracks within last 3 months
      t = Track.top_licensed
      tracks_top_licensed = t.where(
        "tracks.updated_at >= :top", 
        top: three_months_ago).limit(5)


      # mix top listened to tracks within last two weeks
      tracks_top_listens = @tracks.order('tracks.listens_count DESC').where(
        "tracks.updated_at >= :top",
        top: two_weeks_ago)
        .limit(3)  


      # mix top downloaded tracks within last two weeks
      tracks_top_downloaded = @tracks.order("tracks.downloads_count DESC").where(
        "tracks.updated_at >= :top",
        top: two_weeks_ago)
        .limit(2)


      # mix in 25% of staff picks added within 3 months
      tracks_staff_picks = Track.ready.staff_picks.
        includes(:artist).order("tracks.created_at DESC").where(
        "tracks.updated_at >= :top", 
        top: three_months_ago)
        .limit(4)

      @tracks = tracks_top_licensed + tracks_top_listens + tracks_top_downloaded + tracks_staff_picks


    end

    render partial: "shared/results"
  end

1 个答案:

答案 0 :(得分:1)

我认为寻求优雅"解决方案将产生许多不同的意见,所以我将提供一种方法和我的推理。在我的设计决策中,我觉得在这种情况下,通过过滤返回的记录对象而不是试图将查询限制为仅产生唯一结果,在查询交叉点上强制执行唯一性是最佳和优雅的。至于获取分页的连续结果,另一方面,我会存储每个查询的偏移量,并使用实例变量或会话作为下一个查询的起点,具体取决于数据需要如何保留。

这里是我的代码重构版本的gist,其中包含一个解决方案,并解释了我选择使用某些逻辑或数据结构的原因:https://gist.github.com/femmestem/2b539abe92e9813c02da

#filter_tracks包含一个哈希映射@tracks_offset,其他方法可以访问和更新;每个查询方法都有责任将自己的偏移键添加到@tracks_offset

#filter_tracks还为已经出现在搜索结果中的曲目保存了一系列曲目ID。

如果您需要持久性,请创建@tracks_offset@track_ids个会话/ cookie而不是实例变量。逻辑应该是相同的。如果您使用会话来存储结果中的偏移量和ID,请记住在用户完成与此功能的交互时清除它们。

见下文。请注意,我重构了您的#filter_tracks方法,将责任分为9种不同的方法:#filter_tracks#heavy_rotation#order_by_params#heavy_rotation?#validate_and_return_top_results,和#tracks_top_licensed ... #tracks_top_<whatever>。这将使我的笔记更容易理解,并且您的代码更易于维护。

def filter_tracks
  # Does this need to be so high when JavaScript limits display to 14?
  @limit ||= 50

  @tracks_offset ||= {}
  @tracks_offset[:default] ||= 0
  @result_track_ids ||= []

  @order ||= params[:order] || 'heavy_rotation'

  tracks = Track.ready.with_artist
  tracks = parse_params(params[:q], tracks)
  @result_count = tracks.count

  # Checks for heavy_rotation filter flag
  if heavy_rotation? @order
    @tracks = heavy_rotation
  else
    @tracks = order_by_params
  end

  render partial: "shared/results"
end

所有#heavy_rotation都会调用各种查询方法。这样可以轻松地添加,修改或删除任何一种查询方法作为条件更改,而不会影响任何其他方法。

def heavy_rotation
  week_ago = Time.now - 7.days
  two_weeks_ago = Time.now - 14.days
  three_months_ago = Time.now - 3.months

  tracks_top_licensed(date_range: three_months_ago, max_results: 5) +
  tracks_top_listens(date_range: two_weeks_ago, max_results: 3) +
  tracks_top_downloaded(date_range: two_weeks_ago, max_results: 2) +
  tracks_staff_picks(date_range: three_months_ago, max_results: 4)
end

这里有一个查询方法的样子。它们基本相同,但使用自定义SQL / ORM查询。您注意到我没有将:limit参数设置为我希望查询方法返回的结果数。如果返回的其中一个记录被另一个查询方法复制,如果staff_pickstop_downloaded返回了相同的轨道,则会产生问题。然后我将不得不进行额外的查询以获得另一条记录。这不是一个错误的决定,只是一个我没有决定做的决定。

def tracks_top_licensed(args = {})
  args = @default.merge args
  max = args[:max_results]
  date_range = args[:date_range]

  # Adds own offset key to #filter_tracks hash map => @tracks_offset
  @tracks_offset[:top_licensed] ||= 0

  unfiltered_results = Track.top_licensed
    .where("tracks.updated_at >= :date_range", date_range: date_range)
    .limit(@limit)
    .offset(@tracks_offset[:top_licensed])

  top_tracks = validate_and_return_top_results(unfiltered_results, max)

  # Add offset of your most recent query to the cumulative offset
  # so triggering 'view more'/pagination returns contiguous results
  @tracks_offset[:top_licensed] += top_tracks[:offset]

  top_tracks[:top_results]
end

在每种查询方法中,我都是通过自定义方法#validate_and_return_top_results清理记录对象。我的验证器通过记录对象检查其祖先方法@track_ids#filter_tracks集合的重复项。然后它返回其调用者指定的记录数。

def validate_and_return_top_results(collection, max = 1)
  top_results = []
  i = 0 # offset incrementer

  until top_results.count >= max do
    # Checks if track has already appeared in the results
    unless @result_track_ids.include? collection[i].id

      # this will be returned to the caller
      top_results << collection[i]

      # this is the point of reference to validate your query method results
      @result_track_ids << collection[i].id
    end
    i += 1
  end
  { top_results: top_results, offset: i }
end