administrate icon indicating copy to clipboard operation
administrate copied to clipboard

How to sort by relationship field

Open Rodrigora opened this issue 9 years ago • 10 comments

Hi all!

I searched through documentation and code and didn't find a way to sort the records by a relation field.

This is related to #265.

There is someone working on it? If not, I'd be happy to provide PR.

Rodrigora avatar Nov 30 '15 22:11 Rodrigora

Hey, @Rodrigora! Thanks for the suggestion!

Right now this isn't supported. I'm a bit curious about how you'd sort by a relationship - it seems like it would vary depending on whether the relation is a has_many, belongs_to, has_one, or polymorphic relationship.

Can you elaborate on what behavior you'd like to see?

c-lliope avatar Dec 01 '15 00:12 c-lliope

My models are:

class Product
  belongs_to :category
end

class Category
  has_many :product
end

I'd like to sort products by category name, since my products page has the columns id and category (Category column displays the category name).

Rodrigora avatar Dec 01 '15 00:12 Rodrigora

I've used this before with spree: https://github.com/activerecord-hackery/ransack

Could be an easy win.

sebbean avatar Dec 01 '15 10:12 sebbean

I've just implemented a very hacky version. This class sits at the bottom of the user controller. I'm not suggesting it as a fix but might help in your case :)

class SearchUser
  def initialize(resolver, term)
    @resolver = resolver
    @term = term
  end

  SEARCH_TABLES = %w(addresses vouchers)
  # Manually add the joins for these tables

  def run
    if @term.blank?
      resource_class.all
    else
      resource_class.uniq
        .joins(:addresses, user_vouchers: [:voucher])
        .where(query, *search_terms)
    end
  end

  private

  delegate to: :resolver

  def query
    prefixed_attributes.map { |attr| "lower(#{attr}) LIKE ?" }.join(' OR ')
  end

  def search_terms
    ["%#{term.downcase}%"] * prefixed_attributes.count
  end

  def prefixed_attributes
    [resource_class] + SEARCH_TABLES.flat_map do |table|
      attributes_for(table).map { |attr| "#{table}.#{attr}" }
    end
  end

  def attributes_for(dashboard)
    Object.const_get(dashboard.classify + 'Dashboard')::ATTRIBUTE_TYPES.select do |_, type|
      type.searchable?
    end.keys
  end

  attr_reader :resolver, :term
end

tomgatzgates avatar Jan 05 '16 11:01 tomgatzgates

Does anyone have any updates on this type of thing? Being able to sort/search by an attribute on a belongs_to association would be a huge win for me.

hughfm avatar May 06 '16 00:05 hughfm

@nickcharlton We should look into this one for an upcoming release. There's a proposed solution in #750

carlosramireziii avatar Mar 02 '17 20:03 carlosramireziii

I've been experimenting with this during this last couple of days. Here's what I have so far: https://github.com/thoughtbot/administrate/pull/1506

pablobm avatar Dec 23 '19 15:12 pablobm

Just wanted to echo my support for a feature like this. Thank you!

connor avatar Jul 22 '20 16:07 connor

We implemented sorting by relationship field by something like this.

class Comment
	has_one :moderation

	delegate :state, to: :moderation, prefix: true
end

class Moderation
	belongs_to :comment

	enum :approved, :disapproved
end

# comments_controller.rb
def scoped_resource
	if order.order_by?(:moderation_state)
		Comment.joins(:operation).merge(Moderation.order(state: order.direction))
	else
		Comment.default_scoped
	end
end

shouichi avatar Nov 25 '21 04:11 shouichi

I agree with @shouichi that overriding the controller is the best way forward at the moment. I think I would override order instead. See this example, adapted from the example app:

module Admin
  class ProductsController < Admin::ApplicationController
    class OrderByProductMetaTag < Administrate::Order
      def initialize(direction = nil)
        super("product_meta_tag", direction)
      end

      def apply(relation)
        relation.joins(:product_meta_tag).order("product_meta_tags.meta_title" => (direction || "asc").to_sym)
      end

      def ordered_by?(attr)
        attr.to_s == "product_meta_tag"
      end
    end

    def order
      if sorting_attribute == "product_meta_tag"
        OrderByProductMetaTag.new(sorting_direction)
      else
        super
      end
    end
  end
end

The short-term task then is to document this.

pablobm avatar Dec 16 '21 10:12 pablobm