Field::HasMany

The HasMany association field wraps a has_many association on your model. By default it adds a list of associated records to the show page and on edit and new pages it renders a searchable dropdown field with checkboxes to select multiple options.

HasMany field for a Project association filtered by "bi" input

How to add a HasMany field

To add the basic HasMany field to a repository, return it as part of the #fields method:

def fields
  [
    Field::HasMany.new(:projects)
  ]
end

HasMany fields use the same searchable options as the associated repository. This also means that if the repository has no searchable fields, the filter input doesn’t work.

Not searchable by default. The field isn’t backed by a real column, so enabling it needs a searchable lambda - see Search for columns in an associated table.

#collection_query

The chainable #collection_query method lets you control what records are included in the dropdown. It accepts a proc, which is called when the user opens the dropdown or changes the filter input. The proc receives an ActiveRecord::Relation with all records matching the filter query.

Field::HasMany.new(:projects)
  .collection_query(->(query:) {
    query.some_scope
  })

The proc can accept the following keyword arguments:

  1. context: The Uchi::Context we’re currently processing.
  2. query: The ActiveRecord::Relation described above.

How to limit what records are returned

You can use #collection_query to limit what records are returned. The currently logged-in user is available as context.user, see Current user.

For example, if your User model has a projects method that returns the Project records the current user is allowed to access, you could do something like:

Field::HasMany.new(:projects)
  .collection_query(->(context:, query:) {
    query.where(id: context.user.projects)
  })

How to control the order of records in dropdowns

When opening the record selector of a HasMany field the records are returned in the default order defined by the repository. To do something else, pass a proc to the #collection_query method:

Field::HasMany.new(:projects)
  .collection_query(->(query:) {
    query.reorder(budget: :desc)
  })

The proc receives an ActiveRecord::Relation with all records returned from the repository. Note that you might have to use #reorder, not just #order, since the relation may already have an order defined.

How to change titles in the dropdown

The dropdown displays the title of the record. For example, a Person repository may use the name attribute as its title. To customize the title for a record, implement Repository#title, see repositories documentation for details.

#nested_fields

By default, a HasMany field lets users pick from existing associated records. The chainable #nested_fields method switches it into an inline editor instead: it creates, edits, and deletes associated records directly within the parent form.

Your model must use accepts_nested_attributes_for for this to work:

class Book < ApplicationRecord
  has_many :titles
  accepts_nested_attributes_for :titles, allow_destroy: true
end

Configure #nested_fields with the fields you want to edit for each associated record:

module Uchi
  module Repositories
    class Book < Repository
      def fields
        [
          Field::String.new(:original_title),
          Field::HasMany.new(:titles).nested_fields(
            Field::String.new(:title),
            Field::String.new(:locale)
          )
        ]
      end
    end
  end
end

This also accepts an array:

Field::HasMany.new(:titles).nested_fields([
  Field::String.new(:title),
  Field::String.new(:locale)
])

Once #nested_fields is configured, the field switches its rendering on both the edit and the show pages: the show page lists each associated record’s configured fields inline instead of loading a scoped index view in a turbo-frame, and the edit/new pages render an inline editor instead of the searchable dropdown. #collection_query has no effect when #nested_fields is configured.

Supported nested field types

  • String - single-line text
  • Text - multi-line text
  • Number - integers, decimals
  • Boolean - checkboxes
  • Date - date picker
  • DateTime - datetime picker

Advanced model options

Reject blank records to avoid creating empty associated records:

class Book < ApplicationRecord
  accepts_nested_attributes_for :titles,
    allow_destroy: true,
    reject_if: :all_blank
end

Limit nested records to prevent abuse:

accepts_nested_attributes_for :titles,
  allow_destroy: true,
  limit: 10

Read more in Rails’ NestedAttributes documentation.