ransack icon indicating copy to clipboard operation
ransack copied to clipboard

ransack_alias doesn't work in associations

Open matgaw opened this issue 1 year ago • 2 comments

class Parent
  ransack_alias :full_name, :first_name_or_last_name
end

class Child
  belongs_to :parent
end

Working scenario:

Parent.ransack(full_name_cont: 'a').result # works correctly

Error scenario: Child.ransack(parent_full_name_cont: 'a').result Result:

undefined method `type' for nil:NilClass (NoMethodError)

      attr.klass.columns.find { |column| column.name == name }.type
                                                              ^^^^^

matgaw avatar Aug 29 '24 20:08 matgaw

This particular line of code is problematic for ActiveAdmin users, as well. If you use ActiveAdmin #includes on a has_one through, you will trigger a call to ransack. ActiveAdmin is feeding ransack the association through the parent, with the result that ransack is told to expect to find grandparent_id that does not exist. This line throws undefined method `type' for nil:NilClass.

Suppose class Whatever < ApplicationRecord has_one :grandparent, through: :parent. ActiveAdmin sends ransack instructions to look for whatevers.grandparent_id.

I can't help but feel there is inadequate error checking here in ransack. Further up the trace in

lib/ransack/nodes/condition.rb:25:in `extract'

there is this comment:

        # TODO: Figure out what to do with multiple types of attributes,
        # if anything. Tempted to go with "garbage in, garbage out" here.

Not being a ransack expert I can't say what the solution would be. But I can say this is a new error introduced since rack 3 and it seems improperly handled.

dougjq avatar Feb 15 '25 02:02 dougjq

I'd like to point out that "mixed" compound conditions that contain an alias + an association do not work, too.

Consider the following setup:

class Author < ApplicationRecord
  has_many :books, dependent: :destroy

  def self.ransackable_attributes(auth_object = nil)
    %w[full_name]
  end
end

class Book < ApplicationRecord
  ransack_alias :ttl, :full_title

  belongs_to :author

  def self.ransackable_associations(auth_object = nil)
    %w[author]
  end

  def self.ransackable_attributes(auth_object = nil)
    %w[full_title ttl]
  end
end

Now, with this setup we have:

<%= search_form_for @q do |f| %>
  <%# ✅ Case #1: alias for the model's attribute - works %>
  <%= f.search_field :ttl_cont, placeholder: "Filter by full_title..." %>

  <%# ✅ Case #2: association's attribute - works %>
  <%= f.search_field :author_full_name_cont, placeholder: "Filter by author's name..." %>

  <%# ✅ Case #3: model's attribute + association's attribute - works %>
  <%= f.search_field :full_title_or_author_full_name_cont, placeholder: "Filter by author's name or title..." %>

  <%# ❌ Case #4: alias + association - DOESN'T WORK! %>
  <%# [NoMethodError] undefined method `type' for nil:NilClass %>
  <%= f.search_field :ttl_or_author_full_name_cont, placeholder: "Filter by author's name or title..." %>

  <%= f.submit "Search" %>
<% end %>

8bit-mate avatar Mar 19 '25 20:03 8bit-mate