Use multiple resources for the same model

By Adrian Marin

- August 26, 2022

Usually, an Avo Resource maps to one Rails model. So there will be a one-to-one relationship between them. But there will be scenarios where you'd like to create another resource for the same model.

Let's take as an example the User model. You'll have an UserResource associated with it.

# app/models/user.rb
class User < ApplicationRecord
end

# app/avo/resources/user_resource.rb
class UserResource < Avo::BaseResource
  self.title = :name

  field :id, as: :id, link_to_resource: true
  field :email, as: :gravatar, link_to_resource: true, as_avatar: :circle
  field :first_name, as: :text, required: true, placeholder: "John"
  field :last_name, as: :text, required: true, placeholder: "Doe"
end

So when you click on the Users sidebar menu item, you get to the Index page where all the users will be displayed. The information displayed will be the gravatar image, the first and the last name.

Let's say we have a Team model with many Users. You'll have a TeamResource like so:

# app/models/team.rb
class Team < ApplicationRecord
end

# app/avo/resources/team_resource.rb
class TeamResource < Avo::BaseResource
  self.title = :name

  field :id, as: :id, link_to_resource: true
  field :name, as: :text
  field :users, as: :has_many
end

From that configuration, Avo will figure out that the users field points to the UserResource and will use that one to display the users.

But, let's imagine that we don't want to display the gravatar on the has_many association, and we want to show the name on one column and the number of projects the user has on another column.
We can create a different resource named TeamUserResource and add those fields.

# app/avo/resources/team_user_resource.rb
class TeamUserResource < Avo::BaseResource
  self.title = :name

  field :id, as: :id, link_to_resource: true
  field :name, as: :text
  field :projects_count, as: :number
end

We also need to update the TeamResource to use the new TeamUserResource for reference.

# app/avo/resources/team_resource.rb
class TeamResource < Avo::BaseResource
  self.title = :name

  field :id, as: :id, link_to_resource: true
  field :name, as: :text
  field :users, as: :has_many, use_resource: TeamUserResource
end

But now, if we visit the Users page, we will see the fields for the TeamUserResource instead of UserResource, and that's because Avo fetches the resources in an alphabetical order, and TeamUserResource is before UserResource. That's definitely not what we want.
The same might happen if you reference the User in other associations throughout your resource files.

To mitigate that, we are going to use the model_resource_mapping option to set the "default" resource for a model.

# config/initializers/avo.rb
Avo.configure do |config|
  config.model_resource_mapping = {
    'User': 'UserResource'
  }
end

That will "shortcircuit" the regular alphabetical search and use the UserResource every time we don't specify otherwise.

We can still tell Avo which resource to use in other has_many or has_and_belongs_to_many associations with the use_resource option.

Build your next rails app 10x faster with Avo

Avo dashboard showcasing data visualizations through area charts, scatterplot, bar chart, pie charts, custom cards, and others.

Find out how Avo can help you build admin experiences with Rails faster, easier and better.