Wire Active Record relationships into Avo resources as association fields (`field :x, as: :belongs_to | :has_one | :has_many | :has_and_belongs_to_many`) inside `def fields` in `app/avo/resources/<model>.rb`, including polymorphic and `has_many :through` relations, type-to-search pickers, extra join-table fields at attach time, and nested create-in-form. Use when the user wants to link one record to another or surface related records — whether they say it in Avo terms ("add a belongs_to field", "show a has_many panel", "make the association searchable", "polymorphic belongs_to", "has_many through with attach_fields", "use_resource / attach_scope on an association") or in plain Rails/product terms with no Avo vocabulary: "a post belongs to a user", "show all of a user's orders on their page", "add a profile to the user", "connect users and teams with a join table", "the user dropdown is too long — make it type-to-search", "a comment can belong to a post or a project", "add extra fields when attaching a member
Wire Active Record relationships into Avo resources as association fields (`field :x, as: :belongs_to | :has_one | :has_many | :has_and_belongs_to_many`) inside `def fields` in `app/avo/resources/<model>.rb`, including polymorphic and `has_many :through` relations, type-to-search pickers, extra join-table fields at attach time, and nested create-in-form. Use when the user wants to link one record to another or surface related records — whether they say it in Avo terms ("add a belongs_to field", "show a has_many panel", "make the association searchable", "polymorphic belongs_to", "has_many through with attach_fields", "use_resource / attach_scope on an association") or in plain Rails/product terms with no Avo vocabulary: "a post belongs to a user", "show all of a user's orders on their page", "add a profile to the user", "connect users and teams with a join table", "the user dropdown is too long — make it type-to-search", "a comment can belong to a post or a project", "add extra fields when attaching a member to a team", "create the related record right from the form", "let admins pick the author while editing the post", or "link these two models in the admin".
allowed-tools
Read, Edit, Write, Glob, Grep, Bash, WebFetch
metadata
{"requires-gem":"avo-advanced_search (https://avohq.io/addons/searchable-associations) — only for searchable associations; the rest is Community"}
Avo Associations
Avo turns Rails Active Record associations into fields. You declare one line in the resource's def fields and Avo renders it per view: a becomes a link on Show/Index and a dropdown (or search picker) on the forms; , , and render as panels below the record's fields with attach / detach / create controls.
belongs_to
has_one
has_many
has_and_belongs_to_many
These fields live in app/avo/resources/<model>.rb, the same place as every other field. This skill is where relationship-shaped requests land — "a post belongs to a user", "show a user's orders", "connect users and teams", "make the picker type-to-search". For non-association fields (text, number, select, file…) use avo-fields; for creating or configuring the resource shell itself use avo-resources; for who can attach/detach/create use avo-authorization.
Docs (fetch on demand with WebFetch — verify option names and edge cases against the live pages, don't guess):
Use this skill when the request is about one record relating to another, in any phrasing:
Direct Avo asks: "add a belongs_to", "show a has_many panel", "make it searchable", "polymorphic belongs_to", "has_many :through with attach_fields".
Rails-shaped asks without Avo words: "a post belongs to a user", "a user has one profile", "show all of a user's orders on their page", "connect users and teams with a join table".
Product-shaped asks: "the author dropdown is too long — let me type to search", "a comment can belong to a post or a project", "add a rating when I attach a member to a team", "create the related record right from the form".
If the request is about a scalar attribute (a string, a number, a file, an enum select) rather than a link between records, it's an avo-fields task, not this one.
Workflow
Confirm the Rails association exists. Association fields are a view of an Active Record association — Avo does not create it. Check the model (app/models/<model>.rb) for the matching belongs_to / has_one / has_many / has_and_belongs_to_many (and the inverse on the other model). If it's missing, add it to the model first, and set inverse_of on both sides.
Pick the field type from the Association types table — it mirrors the Rails macro one-to-one.
Add the field to def fields in app/avo/resources/<model>.rb:
# app/avo/resources/post.rbclassAvo::Resources::Post < Avo::BaseResourcedeffields
field :id, as::id
field :title, as::text
field :user, as::belongs_to# Post belongs_to :user
field :comments, as::has_many# Post has_many :commentsendend
The field name is the association name, not a column — field :user maps to belongs_to :user (the user_id column is implicit). A has_many field's counterpart lives on the other resource: Post has_many :comments is field :comments, as: :has_many on the Post resource, and the reciprocal field :post, as: :belongs_to on the Comment resource.
Layer options as needed (see Key options) — scope the attach list, point at a different resource, make it searchable, allow nested creation, etc.
Verify buttons follow the target resource's policy. Attach/detach/create/destroy visibility comes from the target resource's Pundit policy, using plural method names (see Gotchas). If a button must appear or disappear, that's an avo-authorization change, not an option here.
Association types
Rails association on the model
Avo field declaration
belongs_to :user
field :user, as: :belongs_to
belongs_to :commentable, polymorphic: true
field :commentable, as: :belongs_to, polymorphic_as: :commentable, types: [::Post, ::Project]
has_one :admin
field :admin, as: :has_one
has_many :comments
field :comments, as: :has_many
has_many :members, through: :memberships
field :members, as: :has_many, through: :memberships
has_and_belongs_to_many :teams
field :teams, as: :has_and_belongs_to_many
has_one, has_many, and has_and_belongs_to_many render only on the Show view by default; add show_on: :edit to also surface them on the edit form (still rendered with the show-view component — for editable-in-form use nested).
Key options
Most options are shared across the association fields; a handful are type-specific. Confirm exact behavior against the linked docs before relying on an edge case.
Shared by all association fields
Option
What it does
searchable
Replaces the <select> / attach picker with a type-as-you-search input. Paid add-on + needs a query source — see Searchable.
attach_scope
-> { query.non_admins } — scopes the records offered in the attach modal / dropdown (locals: query, parent). Note: does not filter the listed rows of a has_many/HABTM — use scope or a Pundit policy scope for that.
use_resource
Render/redirect through a different resource, e.g. use_resource: Avo::Resources::PhotoComment (class or string).
name
Overrides the panel/label text (has_many, HABTM). Also achievable via field i18n.
description
Sub-title text under the panel title. Keep it cheap under loading: :manual — the lambda runs on the placeholder too.
scope
-> { query.approved } — scopes the rows displayed in a has_many/HABTM table (locals: query, parent, resource, parent_resource).
loading
:manual defers the frame fetch behind a Load button (heavy associations); :lazy is the default.
linkable
Makes the panel title open the association table on its own page.
reloadable
Adds a reload icon on the panel (boolean or a lambda gated on e.g. current_user.admin?).
nested
Create/edit the related record inline in the parent form. Requires the avo-nested gem.nested: true = { on: :forms }; limit: caps rows on has_many/HABTM.
attach_using
:checkbox_list renders the attach modal as a multi-select checkbox list instead of a single-select dropdown (has_many, HABTM).
discreet_pagination
Hides pagination chrome when there's only one page.
hide_search_input / hide_filter_button
Hide the search box / filters button on the association table.
link_to_child_resource
STI: link rows to the child resource instead of the parent (see Gotchas).
for_attribute
Point a differently-named field at the same association (declare it twice with different scopes/names).
belongs_to-only
Option
What it does
polymorphic_as: + types:
Turns the field polymorphic. polymorphic_as: :commentable, types: [::Post, ::Project] — the two must be used together, matching a belongs_to :commentable, polymorphic: true on the model. Renders two dropdowns: pick the type, then the record.
polymorphic_help
Help text for the type dropdown (use help: for the record dropdown).
can_create
true/false — show/hide the "create new" link on the form. Overridden by the target resource's create? policy (see Gotchas).
allow_via_detaching
Keeps the field editable when you reached the record through that association (which otherwise disables it).
link_to_record
true makes the Index cell link to the current row's record instead of the associated one.
has_many :through and attach_fields
For a join model with its own columns, expose those columns at attach time with attach_fields (a lambda that declares fields, evaluated against the join model):
# Team has_many :members, through: :memberships — Membership has a `role` column
field :members,
as::has_many,
through::memberships,
attach_fields: -> {
field :role, as::text
}
The extra fields render inside the attach modal and their values persist on the join row. attach_fieldsonly persists on has_many :through — it exists but is a no-op on a plain has_many / HABTM. If the through model is polymorphic, add the type as a hidden field: field :membership_type, as: :hidden, default: "TheType".
Searchable associations
When the target has too many records for a dropdown, searchable swaps it for a type-to-search picker.
Paid add-on (avo-advanced_search gem) — searchable-associations. Confirm the license before proposing it.
Needs a query source, or the picker is silently empty. Supply it either on the target resource (self.search = { query: -> { ... } }, shared by every picker pointing at it) or field-level inside searchable: { query: -> { ... } } (that picker only; overrides the resource-level one).
Boolean form — reuse the target resource's self.search:
field :links, as::has_many, searchable:true
Hash form — override per picker, or gate it:
field :user, as::belongs_to,
searchable: {
query: -> { query.ransack(name_cont: q).result(distinct:false) },
item: -> { { title:"Reviewer: #{record.first_name}", description: record.email } },
enabled: -> { current_user.admin? } # falsy → falls back to the plain <select>
}
Polymorphic belongs_to runs the search once per declared type. With the boolean form, define self.search[:query] on each target resource. With a single field-level proc, branch on query.klass (or query.klass.name):
field :commentable, as::belongs_to,
polymorphic_as::commentable, types: [::Post, ::Project],
searchable: {
query: -> {
case query.klass.name
when"Post"then query.ransack(body_cont: q).result(distinct:false)
when"Project"then query.ransack(name_cont: q).result(distinct:false)
end
}
}
query: locals include q, query, params, parent_record (can be nil on create forms — guard with &.); item: locals are record, resource.
Gotchas
Set inverse_of on the model association. Avo relies on it to resolve the reciprocal; missing it causes wrong/empty attach lists and save bugs. Set it on both sides.
The Rails association must exist first. Adding field :x, as: :has_many does nothing if the model has no has_many :x.
has_* fields are hidden on Edit by default. Add show_on: :edit to surface them on the form. For editing the related record in the form (not just displaying it), use nested — which needs the avo-nested gem.
Attach/detach/create/destroy buttons come from the target resource's Pundit policy, and the method names are plural, matching the association name: attach_users?, detach_users?, create_users?, destroy_users?, view_users?, show_users? — not the singular detach_user?. This is the #1 "why isn't my button showing" cause. Cross-link avo-authorization.
can_create: true is still vetoed by the policy. If the target resource's create? returns false, no create link appears regardless of can_create.
A belongs_to field is disabled when you arrive through that same association. Editing a Comment reached via a Post's comments panel disables the post field. Set allow_via_detaching: true to re-enable it.
polymorphic_as and types are a pair — using one without the other raises. types is an array of model classes ([::Post, ::Project]), matching a polymorphic: truebelongs_to.
attach_scope does not filter listed rows, only the attach picker. To hide rows from a has_many/HABTM table, use scope or a Pundit policy scope.
STI child resources must set self.model_class. When a parent class and STI children share a table, give each child resource self.model_class = "SuperUser" so Avo maps it correctly. To route association rows / index clicks to the child resource instead of the parent, use self.link_to_child_resource = true on the parent resource or link_to_child_resource: on the field.
Searchable can look "broken" but be misconfigured — no query source means an empty picker, not an error. And it's a paid add-on.
Report
After wiring the association, tell the user:
The field(s) added and to which resource file (absolute path), with the as: type.
Any model change you made or that's still required (the Rails association, inverse_of), since Avo only renders the association.
Options applied and why (searchable, scope, nested, attach_fields, polymorphic, etc.).
Any paid add-on dependency introduced (searchable → advanced-search; nested → avo-nested gem) and whether it still needs installing / a query source.
If button visibility matters, a pointer that attach/detach/create/destroy are governed by the target resource's plural policy methods — hand off to avo-authorization if changes are needed.