CSV export of `has_many` association fails with `searchable` set to an array
Author: rneviusCreated Sep 20, 2024Updated May 21, 2025
Describe the bug
As above, when a has_many association is included as a field in the list view, and searchable is set to an array (not true) export to CSV fails with:
ActiveRecord::StatementInvalid in RailsAdmin::MainController#export
PG::UndefinedTable: ERROR: missing FROM-clause entry for tableThis happens only when the list has been filtered by the has_many association, and we are "Exporting found" records. It does not happen if we do not filter by the association and export all records.
Here is the log output:
Processing by RailsAdmin::MainController#export as HTML
Parameters: {"authenticity_token"=>"fake", "send_data"=>"true", "all"=>"true", "schema"=>{"only"=>["id", "created_at", "updated_at", "pinned"], "include"=>{"project"=>{"only"=>["id", "full_name", "preferred_name", "pronoun", "slug", "date_of_birth", "date_of_passing", "living", "prefix", "obituary", "welcome_message", "privacy_mode", "role", "created_at", "updated_at", "status", "webcrawler_status", "notification_status", "subdomain", "legacy_id", "embedding_enabled", "plan", "obituary_url"], "methods"=>["cover_image"]}, "group"=>{"only"=>["id", "name", "title", "slug", "description", "group_website_url", "cta_url", "cta_text", "primary_color", "secondary_color", "button_color", "reflections_enabled", "privacy_mode", "created_at", "updated_at", "subgroup_name", "contributions_enabled", "member_name", "stay_connected_enabled", "reporting_enabled", "memory_card_color", "banner_columns", "memories_enabled", "experimental_stay_connected_jotform_id"], "methods"=>["logo", "project_image", "banner_image", "banner_image_mobile", "themes"]}, "group_categories"=>{"only"=>["id", "name", "created_at", "updated_at", "priority", "enabled", "subcategory_limit", "featured", "featured_color", "link", "private_name"], "methods"=>["featured_icon"]}, "group_project_attributes"=>{"only"=>["id", "key", "value", "notes", "created_at", "updated_at"]}}}, "csv_options"=>{"encoding_to"=>"", "generator"=>{"col_sep"=>","}}, "return_to"=>"http://localhost:3000/admin/group_project?f%5Bgroup_project_attributes%5D%5B80623%5D%5Bv%5D=asdf&query=", "csv"=>"", "f"=>{"group_project_attributes"=>{"80623"=>{"v"=>"asdf"}}}, "model_name"=>"group_project"}
ERROR: missing FROM-clause entry for table "group_project_attributes" at character 2396
STATEMENT: SELECT "group_projects"."id" AS t0_r0, "group_projects"."project_id" AS t0_r1, "group_projects"."group_id" AS t0_r2, "group_projects"."created_at" AS t0_r3, "group_projects"."updated_at" AS t0_r4, "group_projects"."pinned" AS t0_r5, "projects"."id" AS t1_r0, "projects"."account_id" AS t1_r1, "projects"."creator_id" AS t1_r2, "projects"."full_name" AS t1_r3, "projects"."preferred_name" AS t1_r4, "projects"."pronoun" AS t1_r5, "projects"."slug" AS t1_r6, "projects"."cover_image_data" AS t1_r7, "projects"."date_of_birth" AS t1_r8, "projects"."date_of_passing" AS t1_r9, "projects"."living" AS t1_r10, "projects"."prefix" AS t1_r11, "projects"."obituary" AS t1_r12, "projects"."welcome_message" AS t1_r13, "projects"."privacy_mode" AS t1_r14, "projects"."role" AS t1_r15, "projects"."created_at" AS t1_r16, "projects"."updated_at" AS t1_r17, "projects"."status" AS t1_r18, "projects"."webcrawler_status" AS t1_r19, "projects"."notification_status" AS t1_r20, "projects"."subdomain" AS t1_r21, "projects"."legacy_id" AS t1_r22, "projects"."embedding_enabled" AS t1_r23, "projects"."plan" AS t1_r24, "projects"."obituary_url" AS t1_r25, "groups"."id" AS t2_r0, "groups"."title" AS t2_r1, "groups"."slug" AS t2_r2, "groups"."description" AS t2_r3, "groups"."group_website_url" AS t2_r4, "groups"."logo_data" AS t2_r5, "groups"."cta_url" AS t2_r6, "groups"."cta_text" AS t2_r7, "groups"."primary_color" AS t2_r8, "groups"."secondary_color" AS t2_r9, "groups"."button_color" AS t2_r10, "groups"."reflections_enabled" AS t2_r11, "groups"."privacy_mode" AS t2_r12, "groups"."created_at" AS t2_r13, "groups"."updated_at" AS t2_r14, "groups"."subgroup_name" AS t2_r15, "groups"."contributions_enabled" AS t2_r16, "groups"."member_name" AS t2_r17, "groups"."name" AS t2_r18, "groups"."themes" AS t2_r19, "groups"."project_image_data" AS t2_r20, "groups"."stay_connected_enabled" AS t2_r21, "groups"."banner_image_data" AS t2_r22, "groups"."banner_image_mobile_data" AS t2_r23, "groups"."reporting_enabled" AS t2_r24, "groups"."memory_card_color" AS t2_r25, "groups"."banner_columns" AS t2_r26, "groups"."memories_enabled" AS t2_r27, "groups"."experimental_stay_connected_jotform_id" AS t2_r28 FROM "group_projects" LEFT OUTER JOIN "projects" ON "projects"."status" != $1 AND "projects"."id" = "group_projects"."project_id" LEFT OUTER JOIN "groups" ON "groups"."id" = "group_projects"."group_id" WHERE ((group_project_attributes.key ILIKE '%asdf%') OR (group_project_attributes.value ILIKE '%asdf%') OR (group_project_attributes.notes ILIKE '%asdf%')) ORDER BY "group_projects"."id" ascReproduction steps
Here is the relevant model config. Again, if I change it to searchable true, there is no problem.
class GroupProject < ApplicationRecord
belongs_to :group, inverse_of: :group_projects
belongs_to :project, inverse_of: :group_projects
has_and_belongs_to_many :group_categories
has_many :group_project_attributes, dependent: :destroy, inverse_of: :group_project
default_scope { order(pinned: :desc, created_at: :desc) }
accepts_nested_attributes_for :group_categories, :project
rails_admin do
object_label_method :rails_admin_label
list do
include_all_fields
field :group_project_attributes do
label "Attributes"
pretty_value do
value.map do |attribute|
# Generate the URL to the GroupProjectAttributes record
attribute_link = bindings[:view].show_path(model_name: "group_project_attribute", id: attribute.id)
# Escape the key, value, and notes to prevent XSS
safe_key = ERB::Util.html_escape(attribute.key)
safe_value = ERB::Util.html_escape(attribute.value)
safe_notes = attribute.notes ? "(#{ERB::Util.html_escape(attribute.notes)})" : ""
# Create the link for each attribute's key and display the value and notes
bindings[:view].link_to("#{safe_key}: #{safe_value} #{safe_notes}", attribute_link).html_safe
end.join(" <br> ").html_safe
end
filterable true
queryable true
searchable [:key, :value, :notes]
end
end
end
def rails_admin_label
"#{project.full_name} <GroupProject \##{id}>"
end
end
class GroupProjectAttribute < ApplicationRecord
belongs_to :group_project, inverse_of: :group_project_attributes
endExpected behavior
There should be a SQL JOIN happening.
Additional context
railsversion: 6.1.7.8rails_adminversion: 3.2.0rails_adminnpm package version: 3.2.0
Source: railsadminteam/rails_admin