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.
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