How do you order by a custom model method that has no attribute in SQL?

前端 未结 6 1112
生来不讨喜
生来不讨喜 2021-02-02 08:11

Previously I ordered my posts as this:

@posts = Post.find(:all, :order => \"created_at DESC\")

But now I want to replace created_at

相关标签:
6条回答
  • This is a bit more complicated than what I like but this I like to keep my sort to stay as a active record model so its bit more complicated than just

    Post.all.sort_by {|post| post.custom_method }
    

    what I do is:

    ids = Post.all.sort_by {|post| post.custom_method }.map(&:ids)
    Post.for_ids_with_order(ids)
    

    this is a custom scope in the Post model

    #app/models/post.rb
    class Post < ApplicationRecord
      ...
        scope :for_ids_with_order, ->(ids) {
        order = sanitize_sql_array(
          ["position(id::text in ?)", ids.join(',')]
        )
        where(:id => ids).order(order)
      }
    
      ...
    end
    

    I hope that this help

    0 讨论(0)
  • 2021-02-02 08:43

    As the first answer noted, order is an Active Record command that essentially does a SQL query on your database, but that field doesn't actually exist in your database.

    As someone else commented, you can more cleanly run the Ruby method sort_by by using the ampersand (more info here):

    Post.all.sort_by(&:custom_method)

    However, things do get complicated depending on what you want to do in your view. I'll share a case I recently did in case that helps you think through your problem. I needed to group my resource by another resource called "categories", and then sort the original resource by "netvotes" which was a custom model method, then order by name. I did it by:

    • Ordering by name in the controller: @resources = Resource.order(:name)
    • Grouping by category in the outer loop of the view: <% @resources.group_by(&:category).each do |category, resources| %>
    • Then sorting the resources by votes in the partial for resources: <%= render resources.sort_by(&:netvotes).reverse %>

    The view is a bit confusing, so here is the full view loop in index.html.erb:

    <% @resources.group_by(&:category).each do |category, resources| %>
      <div class="well">
        <h3 class="brand-text"><%= category.name %></h3>
        <%= render resources.sort_by(&:netvotes).reverse %>
      </div>
    <% end %>
    

    And here is the _resource.html.erb partial:

    <div class="row resource">
      <div class="col-sm-2 text-center">
        <div class="vote-box">
          <%= link_to fa_icon('chevron-up lg'), upvote_resource_path(resource), method: :put %><br>
          <%= resource.netvotes %><br>
          <%= link_to fa_icon('chevron-down lg'), downvote_resource_path(resource), method: :put %>
        </div>
      </div>
      <div class="col-sm-10">
        <%= link_to resource.name, resource.link, target: "_blank" %>
        <p><%= resource.notes %></p>
      </div>
    </div>
    
    0 讨论(0)
  • 2021-02-02 08:47

    It fails because you are asking your db to do the sorting.

    @posts = Post.all.sort {|a,b| a.custom_method <=> b.custom_method}

    Note that this becomes non-trivial when you want to start paging results and no longer wish to fetch .all. Think about your design a bit before you go with this.

    0 讨论(0)
  • 2021-02-02 08:53

    Just to expand on @Robbie's answer

    Post.all.sort_by {|post| post.custom_method }.reverse
    
    0 讨论(0)
  • 2021-02-02 08:56

    in rails 3 we can do this as: Post.order("custom_method DESC")
    When upgrading app from rails2 to rails3

    0 讨论(0)
  • 2021-02-02 08:58

    Well, just Post.find(:all) would return an array of AR objects. So you could use Array.sort_by and pass it a block, and since those records are already fetched, you can access the virtual attribute inside the block that sort_by takes.

    RDoc: Enumerable.sort_by

    0 讨论(0)
提交回复
热议问题