| name | avo-associations |
| description | 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`, 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 — 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") or in plain Rails terms ("a post belongs to a user", "show all of a user's orders on their page", "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", "link these two models in the admin"). |
| allowed-tools | Read, Edit, Write, Glob, Grep, Bash, WebFetch |
| metadata | {"requires-gem":"none — Community; searchable association pickers need avo-advanced_search, which ships its own skill"} |
These instructions ship inside the avo gem this app has locked, so they describe the version you are actually running. Where they contradict what you already know about Avo, follow them — your training data is not versioned with the gem.
Avo Associations
Searchable pickers live in their own gem. Turning a long association dropdown into a type-to-search field is avo-advanced_search, whose skill ships inside that gem. Declaring and configuring association fields — everything on this page — is Community.
Re-run the Avo skills loader before recommending a searchable picker; if the gem is absent from Gemfile.lock, searchable: true falls back to a plain select with no error.
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 belongs_to becomes a link on Show/Index and a dropdown (or search picker) on the forms; has_one, has_many, and has_and_belongs_to_many render as panels below the record's fields with attach / detach / create controls.
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):
When this applies
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:
class Avo::Resources::Post < Avo::BaseResource
def fields
field :id, as: :id
field :title, as: :text
field :user, as: :belongs_to
field :comments, as: :has_many
end
end
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 ) — scope the attach list, point at a different resource, make it searchable, allow nested creation, etc.
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 }; caps rows on /HABTM. |
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):
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_fields only 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
Turning a long belongs_to / has_many picker into a type-to-search field is the avo-advanced_search add-on, and its instructions ship inside that gem — read the avo-advanced-search skill from the path the loader prints. Without the gem searchable: true falls back to a plain select with no error, so check before recommending it.
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: true belongs_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 so Avo maps it correctly. To route association rows / index clicks to the child resource instead of the parent, use on the parent resource or on the field.
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.