# Associations
One of the most amazing things about Ruby on Rails is how easy it is to create Active Record associations (opens new window) between models. We try to keep the same simple approach in Avo too.
inverse_of
as often as possible to your model's association attribute.
# Belongs to
field :user, as: :belongs_to
When you add a BelongsTo
association to a model, you will see three different field types.
On the Index view, you'll see a column with the @title
value of the associated model.
On the Show view, you'll see a link to the associated model.
On the Edit and Create views, you'll see a drop-down element with the available records. Here you may change the associated model.
# Polymorphic belongs_to
To use a polymorphic relation you need to add the polymorphic_as
and types
properties.
class CommentResource < Avo::BaseResource
self.title = :id
field :id, as: :id
field :body, as: :textarea
field :excerpt, as: :text, show_on: :index, as_description: true do |model|
ActionView::Base.full_sanitizer.sanitize(model.body).truncate 60
rescue
""
end
field :commentable, as: :belongs_to, polymorphic_as: :commentable, types: [::Post, ::Project]
end
# Polymorphic help
Requires V 2.5 +
When displaying a polymorphic association, you will get two dropdowns. One selects the polymorphic type (Post
or Project
), and one for choosing the actual record. You may want to give the user explicit information about those dropdowns. For example, you can use the polymorphic_help
option for the first dropdown and help
for the second.
class CommentResource < Avo::BaseResource
self.title = :id
field :id, as: :id
field :body, as: :textarea
field :excerpt, as: :text, show_on: :index, as_description: true do |model|
ActionView::Base.full_sanitizer.sanitize(model.body).truncate 60
rescue
""
end
field :reviewable,
as: :belongs_to,
polymorphic_as: :reviewable,
types: [::Post, ::Project, ::Team],
polymorphic_help: "Choose the type of record to review",
help: "Choose the record you need"
end
# Searchable belongs_to
Requires V 1.21 +
There might be the case that you have a lot of records for the parent resource, and a simple drop-down won't cut it. This is where you can use the searchable
option to get a better search experience for that resource.
class CommentResource < Avo::BaseResource
self.title = :id
field :id, as: :id
field :body, as: :textarea
field :user, as: :belongs_to, searchable: true
end
searchable
works with polymorphic
belongs_to
associations too.
class CommentResource < Avo::BaseResource
self.title = :id
field :id, as: :id
field :body, as: :textarea
field :commentable, as: :belongs_to, polymorphic_as: :commentable, types: [::Post, ::Project], searchable: true
end
# app/avo/resources/post_resource.rb
class PostResource < Avo::BaseResource
self.search_query = ->(params:) do
scope.ransack(id_eq: params[:q], name_cont: params[:q], body_cont: params[:q], m: "or").result(distinct: false)
end
end
# app/avo/resources/project_resource.rb
class ProjectResource < Avo::BaseResource
self.search_query = ->(params:) do
scope.ransack(id_eq: params[:q], name_cont: params[:q], country_cont: params[:q], m: "or").result(distinct: false)
end
end
Watch the video below to get an ideea on how it works.
# Belongs to attach scope
When you edit a record that has a belongs_to
association, on the edit screen, you will have a list of records from which you can choose a record to associate with.
Let's take, for example, a Post
belongs to a User
. On the post edit screen, you will have a dropdown (or a search field if it's searchable) with all the available users. But that's not ideal. Maybe you don't want to show all the users in your app but only those who are not admins.
You can use the attach_scope
option to keep only the users you need in the belongs_to
dropdown field.
You have access to the query
that you can alter and return it and the parent
object which is the actual record where you want to assign the association (the actual Post
in the below example).
# app/models/user.rb
class User < ApplicationRecord
scope :non_admins, -> { where "(roles->>'admin')::boolean != true" }
end
# app/avo/resources/post_resource.rb
class PostResource < Avo::BaseResource
field :user, as: :belongs_to, attach_scope: -> { query.non_admins }
end
For the scenarios where you need to add a record associated to that resource (you create a Post
through a Category
), the parent
is not available (the Post
is not persisted in the database). Avo makes the parent
an instantiated object with it's parent populated (a Post
with the category_id
populated with the parent Category
from which you started the creation process) so you can better scope out the data (you know from which Category
it was initiated).
# Allow detaching via the association
By default, when you visit a record through an association that belongs_to
field is disabled. There might be cases where you'd like that field not to be disabled and allow your users to change that association.
You can instruct Avo to keep that field enabled in this scenario using allow_via_detaching
.
class CommentResource < Avo::BaseResource
self.title = :id
field :id, as: :id
field :body, as: :textarea
field :commentable,
as: :belongs_to,
polymorphic_as: :commentable,
types: [::Post, ::Project],
allow_via_detaching: true
end
# Has One
The HasOne
association shows the unfolded view of you HasOne
association. It's like peaking on the Show view of that association. You also get the Attach/Detach button to easily switch records.
field :admin, as: :has_one
# Show on edit screens
By default, has_one
is only visible on the Show page. If you want to enable it on the Form pages as well you need to add the show_on: :edit
option.
# Has Many
The HasMany
field is visible, by default, only on the Show page. Below the regular fields panel, you will see a new panel with the model's associated records.
field :projects, as: :has_many
Here you may attach more records by clicking the "Attach" button.
In a similar fashion, you may detach a model using the detach button.
# Show on edit screens
By default, has_many
is only visible on the Show page. If you want to enable it on the Form pages as well you need to add the show_on: :edit
option.
# Discreet pagination
By default, has_many
shows the pagination options and details. If you want to hide the pagination information when there are less than one page of records you need to add the discreet_pagination: true
option.
# Use resource
By default, has_many
will use the field resource.
For example field :comments, as: :has_many
will use CommentResource
.
If you want to use a custom resource you need to add the use_resource: YourCustomResource
option.
Make sure to have the custom resource prepared to be used by adding self.model_class = ::YourModel
# Has Many Through
The HasMany
association also supports the :through
option.
field :members, as: :has_many, through: :memberships
# Show on edit screens
By default, has_many
is only visible on the Show page. If you want to enable it on the Form pages as well you need to add the show_on: :edit
option.
Adding associations on the New
screen is not supported at the moment. The association needs some information form the parent record that hasn't been created yet (because the user is on the New
screen).
You may use the redirect helpers to have the following flow:
- User on the
New
screen. They can't see the association panels yet. - User creates the record. They get redirected to the
Show
/Edit
screen where they can see the association panels. - User attaches associations.
# Discreet pagination
By default, has_many
shows the pagination options and details. If you want to hide the pagination information when there are less than one page of records you need to add the discreet_pagination: true
option.
# Has And Belongs To Many
The HasAndBelongsToMany
association works similarly to HasMany
.
field :users, as: :has_and_belongs_to_many
# Show on edit screens
By default, has_and_belongs_to_many
is only visible on the Show page. If you want to enable it on the Form pages as well you need to add the show_on: :edit
option.
# Discreet pagination
By default, has_and_belongs_to_many
shows the pagination options and details. If you want to hide the pagination information when there are less than one page of records you need to add the discreet_pagination: true
option.
# Use resource
By default, has_and_belongs_to_many
will use the field resource.
For example field :teams, as: :has_and_belongs_to_many
will use TeamResource
.
If you want to use a custom resource you need to add the use_resource: YourCustomResource
option.
Make sure to have YourCustomResource
prepared to be used by adding self.model_class = ::YourModel
# Searchable has_many
Requires V 1.25 +
Similar to belongs_to
, the has_many
associations support the searchable
option.
class CourseLink < Avo::BaseResource
field :links, as: :has_many, searchable: true, placeholder: "Click to choose a link"
end
# app/avo/resources/course_link_resource.rb
class CourseLinkResource < Avo::BaseResource
self.search_query = ->(params:) do
scope.ransack(id_eq: params[:q], link_cont: params[:q], m: "or").result(distinct: false)
end
end
# Single Table Inheritance (STI)
When you have models that share behavior and fields through with STI, Rails will cast the model as the final class no matter how you query it.
# app/models/user.rb
class User < ApplicationRecord
end
# app/models/super_user.rb
class SuperUser < User
end
# User.all.map(&:class) => [User, SuperUser]
For example, when you have two models, User
and SuperUser
with STI, when you call User.all
Rails will return an instance of User
and an instance of SuperUser
. That confuses Avo in producing the proper resource of User
. That's why when you deal with STI, the final resource SuperUserResource
should receive the underlying model_class
so Avo knows which model it represents.
class SuperUserResource < Avo::BaseResource
self.title = :name
self.includes = []
self.model_class = ::SuperUser
field :id, as: :id
field :name, as: :text
end
# Add scopes to associations
Demo videoWhen displaying has_many
associations, you might want to scope out some associated records. For example a user might have multiple comments, but on the user's Show
page you don't want to display all the comments, but only the ones that have been approved beforehand.
# app/models/comment.rb
class Comment < ApplicationRecord
belongs_to :user, optional: true
scope :approved, -> { where(approved: true) }
end
# app/models/user.rb
class User < ApplicationRecord
has_many :comments
end
# app/avo/resources/user_resource.rb
class UserResource < Avo::BaseResource
# Version before v2.5.0
field :comments, as: :has_many, scope: -> { approved }
end
# app/avo/resources/user_resource.rb
class UserResource < Avo::BaseResource
# Version after v2.5.0
field :comments, as: :has_many, scope: -> { query.approved }
end
Now, the comments
query on the user Index
page will have the approved
scope attached.
With version 2.5.0 you'll also have access to the parent
record so you can use that to scope your associated models even better.
All the has_many
associations have the attach_scope
option available too.
# Show/hide buttons
You will want to control the visibility of the attach/detach/create/destroy/actions buttons that are visible throughout your app. You can use the policy methods to do that.
Find out more on the authorization page.
# Add custom labels to the associations pages
You might want to change the name that appears on the association page. For example, if you're displaying a team_members
associations, by default your users will see Team members
as the title, but you'd like to show them Members
.
You can customize that using fields localization.