Post

Search Through Associated Model Fields in Administrate

How to include the child model's fields in the dashboard and be able to search through them.

Search Through Associated Model Fields in Administrate

The Problem

Consider two models, User and Organization, where an Organization belongs_to a User and a User has_one Organization. User has a location field and the Organization has a title field.

You might want to search for organizations based on the associated user’s location. In this case the location of the Organization is the location of the owner(User). You can copy the whole example from here.

1
2
3
class User < ApplicationRecord
  has_one :organization
end
1
2
3
4
5
6
7
8
class Organization < ApplicationRecord
  belongs_to :user

  # We use this to display the location on the Organization's index page.
  def location
    user&.location
  end
end

Now, when you perform a search, Administrate’s default behaviour would be to query organization.location, which results in a no column error, because the column location doesn’t exist for organization.

What I tried

I tried overriding the def filter_resources(resources, search_term:) and it worked, but seemed to hack-y, so I went on to open an issue in Administrate, as one does.

The Solution

While writing out the issue I stumbled across some of the documentation and discovered that I can specify :searchable_fields of a Field::BelongsTo

By setting searchable: true and specifying searchable_fields (an array of column names on the associated model), Administrate will correctly perform the necessary SQL queries.

1
2
3
4
5
6
7
8
9
10
11
12
require "administrate/base_dashboard"

class OrganizationDashboard < Administrate::BaseDashboard
  ATTRIBUTE_TYPES = {
    # ...
    user: Field::BelongsTo.with_options(
      searchable: true,
      searchable_fields: ["location"]
    ),
    location: Field::String.with_options(searchable: false),
  }.freeze
end

Now you can go to http://localhost:3002/admin/organizations and see the location of each organization and you can use the search without encountering errors.

Conclusion

This is working fine, but you have to define a custom method for each field so it can get bulky. As seen in #2362 Administrate’s team may improve the search functionality. We wait…

Code

Here is the full setup for the example. Just copy paste and run it in the terminal. I am using rails 8.0.2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
rails new child_fields_search
cd child_fields_search
echo 'gem "administrate", "0.19.0"' >> Gemfile
bundle install

mkdir -p app/assets/config
echo '//= link_tree ../images
//= link_directory ../stylesheets .css' > app/assets/config/manifest.js

rails g model user location:string
rails g model organization title:string user:references
rails db:migrate
rails generate administrate:install

echo 'class User < ApplicationRecord
  has_one :organization, dependent: :destroy
end' > app/models/user.rb
echo 'class Organization < ApplicationRecord
  belongs_to :user

  def location
    user&.location
  end
end' > app/models/organization.rb
echo 'require "administrate/base_dashboard"

class UserDashboard < Administrate::BaseDashboard
  ATTRIBUTE_TYPES = {
    id: Field::Number,
    location: Field::String,
    organization: Field::HasOne,
    created_at: Field::DateTime,
    updated_at: Field::DateTime,
  }.freeze

  COLLECTION_ATTRIBUTES = %i[
    id
    location
    organization
    created_at
  ].freeze

  SHOW_PAGE_ATTRIBUTES = %i[
    id
    location
    organization
    created_at
    updated_at
  ].freeze

  FORM_ATTRIBUTES = %i[
    location
  ].freeze

  COLLECTION_FILTERS = {}.freeze
end' > app/dashboards/user_dashboard.rb
echo 'require "administrate/base_dashboard"

class OrganizationDashboard < Administrate::BaseDashboard
  ATTRIBUTE_TYPES = {
    id: Field::Number,
    title: Field::String,
    user: Field::BelongsTo,
    location: Field::String, # Defined for display purposes. Calls the .location method on Organization
    created_at: Field::DateTime,
    updated_at: Field::DateTime,
  }.freeze

  # Including location in COLLECTION_ATTRIBUTES will cause the search to
  # query organization.location, which doesnt exist, thus resulting in an error
  COLLECTION_ATTRIBUTES = %i[
    id
    title
    user
    location
    created_at
  ].freeze

  SHOW_PAGE_ATTRIBUTES = %i[
    id
    title
    user
    location
    created_at
    updated_at
  ].freeze

  FORM_ATTRIBUTES = %i[
    title
    user
  ].freeze

  COLLECTION_FILTERS = {}.freeze
end' > app/dashboards/organization_dashboard.rb
echo 'user1 = User.create!(location: "New York")
Organization.create!(title: "NY Awesome Company", user: user1)

user2 = User.create!(location: "Los Angeles")
Organization.create!(title: "LA Brilliant Business", user: user2)' > db/seeds.rb
rails db:seed
rails s -p 3002
This post is licensed under CC BY-NC 4.0 by the author.