【问题标题】:Merging three Active Record arrays合并三个 Active Record 数组
【发布时间】:2017-02-27 15:16:43
【问题描述】:

我正在尝试在一个 Rails 5 应用程序中合并三个 Active Record 数组,以便在我的主页上有一个不错的作业、论坛主题和博客集合。

我有以下代码:

application_controller.rb

def home
  @blogs = Blog.limit(6)
  @jobs = Job.where(approved: true).limit(6)
  @forum_threads = ForumThread.includes(:forum_posts).limit(6)
  @everything = @blogs + @jobs + @forum_threads
end

home.html.erb

<% @everything.sort_by(&:created_at).reverse.each do |item| %>
    <% if item.is_a?(Job) %>
      <%= render partial: "application/partials/home_job", locals: {item: item} %>
    <% elsif item.is_a?(ForumThread) %>
      <%= render partial: "application/partials/home_forum", locals: {item: item} %>
    <% elsif item.is_a?(Blog) %>
      <%= render partial: "application/partials/home_blog", locals: {item: item} %>
    <% end %>
<% end %>

我遇到的问题是这段代码没有按created_by 的日期顺序显示记录,而是从看似随机的日期开始的工作、论坛主题和博客的随机集合。

如果我添加了一个新工作,它不会出现在/home 页面上显示的集合中。但是,如果我从数据库中删除所有记录并开始添加新记录,则代码可以正常工作并以正确的顺序显示帖子,并具有我期望的行为。

我无法将此代码实时推送到 Heroku,因为我无法删除生产中已经存在的所有记录。几乎就像有某种需要清除的缓存。有谁知道怎么回事?

【问题讨论】:

    标签: ruby-on-rails heroku activerecord


    【解决方案1】:
     @blogs = Blog.order(created_at: :desc).limit(6)
    

    等等

    【讨论】:

    • 原则上很好。我建议Blog.order(created_at: :desc).limit(6) 否则在订单之前提供限制并且可能不包括最新的。
    • 如果我这样做,那么三个不同的集合就会组合在一起。所以所有的博客文章都相邻,工作也相邻......我希望它们全部混合并按 created_by 排序
    • 不,您仍然可以执行@everything,然后执行sort_by... @KcUS_unico 答案只是确保您首先获得正确的记录。
    • 需要(created_at: :desc)('created_at desc') 否则你只会得到最旧的记录。
    • @SteveTurczyn => 谢谢
    【解决方案2】:

    问题 1:从数据库中获取正确的记录

    选项 A:如果您总是按 created_at 值对每个模型进行排序(常见的愿望),请为每个模型添加一个 default_scope(Rails 4+ 版本以下)。您在控制器中的限制调用将自动利用默认范围。

    app/models/blog.rb

    class Blog < ActiveRecord::Base
      default_scope { order created_at: :desc }
      ...
    end
    

    选项 B:如果您只在某些情况下这样做,但您对多个模型都这样做,我喜欢将其提取到时间戳模块中(如下)。从数据库中提取记录时,您需要在控制器中使用 most_recent 方法,以确保您获得的是最新的。

    app/models/concerns/timestamped.rb

    module Timestamped
      extend ActiveSupport::Concern
    
      included do
        scope :most_recent,  -> { order created_at: :desc }
        scope :least_recent, -> { order created_at: :asc }
        scope :most_fresh,   -> { order updated_at: :desc }
        scope :least_fresh,  -> { order updated_at: :asc }
      end
    end
    
    class Blog < ActiveRecord::Base
      include Timestamped
      ...
    end
    

    问题2:数组排序

    即使是这样一个简单的例子,我还是建议添加一个数组扩展,它与 timestamped.rb 为 ActiveRecord::Relations 定义的 most_recent 方法相匹配。

    lib/array_extensions.rb

    class Array
      def most_recent
        sort { |a, b| b.created_at <=> a.created_at }
      end
    end
    

    然后使用初始化器要求扩展:

    config/initializers/extensions.rb

    require 'array_extensions'
    

    问题 3:保持控制器清洁。

    通常每个控制器操作应该只设置一个实例变量,在这种情况下,看起来您甚至没有在视图中使用@blogs、@jobs 和@forum_threads 变量。 Vivek 的回答解决了这个问题,尽管我会在控制器中执行展平和排序逻辑:

    def home
      @posts = Blog.most_recent.limit(6) + Job.approved.most_recent.limit(6) + ForumThread.most_recent.includes(:forum_posts).limit(6)
      @posts = @posts.most_recent
    end
    

    问题 4:在你的视图中最小化 if/then 逻辑

    而不是这个:

    <% @everything.sort_by(&:created_at).reverse.each do |item| %>
      <% if item.is_a?(Job) %>
        <%= render partial: "application/partials/home_job", locals: {item: item} %>
      <% elsif item.is_a?(ForumThread) %>
        <%= render partial: "application/partials/home_forum", locals: {item: item} %>
      <% elsif item.is_a?(Blog) %>
        <%= render partial: "application/partials/home_blog", locals: {item: item} %>
      <% end %>
    <% end %>
    

    这样做:

    <% @everything.sort_by(&:created_at).reverse.each do |item| %>
      <%= render "application/partials/home_#{item.class.name.underscore}", item: item %>
    <% end %>
    

    并确保您的部分命名正确

    【讨论】:

    • 谢谢。现在我使用选项 A,但我只是重构了我的代码以清理控制器并避免视图中的 if/else 语句。我什至不知道你能做到"application/partials/home_#{item.class.name.underscore}" 所以谢谢你教我一些新东西
    • 很高兴它有帮助!
    【解决方案3】:

    你可以这样做:

    def home
      @collections=[]
      @collections << Blog.limit(6)
      @collections << Job.where(approved: true).limit(6)
      @collections << ForumThread.includes(:forum_posts).limit(6)
    end
    
    <% @collections.flatten.sort_by(&:created_at).reverse.each do |item| %>
    
    ....iteration here ....
    
    <% end %>
    

    【讨论】:

      【解决方案4】:

      如果我正确理解了您的问题,您希望在按日期合并后对数组进行排序。我会这样做:

      @everything = @everything.sort {|x| x.created_at }
      

      希望对您有所帮助。

      【讨论】:

        猜你喜欢
        • 2023-04-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-06-27
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多