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

It's important to set the 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.

Belongs to index

On the Show view, you'll see a link to the associated model.

Belongs to show

On the Edit and Create views, you'll see a drop-down element with the available records. Here you may change the associated model.

Belongs to edit

# 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
Belongs to ploymorphic help

# Searchable belongs_to

Requires V 1.21 +

Searchable associations are available as a pro feature

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
Belongs to searchable Belongs to searchable

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
Avo uses the search feature behind the scenes, so make sure the target resource has the `search_query` option configured.
# 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.

Demo video (opens new window)

# 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
Has one

# Has Many

The HasMany field is visible 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
Has many table

Here you may attach more records by clicking the "Attach" button.

Has many attach

In a similar fashion, you may detach a model using the detach button.

Has many detach

# Has many through

The HasMany association also supports the :through option.

field :members, as: :has_many, through: :memberships

# Has And Belongs To Many

The HasAndBelongsToMany association works similarly to HasMany.

field :users, as: :has_and_belongs_to_many

# Searchable has_many

Requires V 1.25 +

Searchable associations are available as a pro feature

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
Avo uses the search feature behind the scenes, so make sure the target resource has the `search_query` option configured.
# 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 video

When 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/resource/user_resource.rb
class UserResource < Avo::BaseResource
  # Version before v2.5.0
  field :comments, as: :has_many, scope: -> { approved }
end

# app/avo/resource/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.

Association scope

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.

Associations authorization

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

Custom label