Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CSV export of has_many association fails with searchable set to an array #3700

Open
rnevius opened this issue Sep 20, 2024 · 1 comment
Open

Comments

@rnevius
Copy link

rnevius commented Sep 20, 2024

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 table

This 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" asc

Reproduction 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
end

Expected behavior

There should be a SQL JOIN happening.

Additional context

  • rails version: 6.1.7.8
  • rails_admin version: 3.2.0
  • rails_admin npm package version: 3.2.0

image
image
image

@benhutton
Copy link

We are seeing the same thing! In our case, searchable is set to a jsonb query:

searchable "groupings.document ->> 'name'"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants