ruby-on-railserbadministrate

How do you display multiple checkbox fields in the edit view of the Administrate dashboard?


I have multiple checkboxes for one of my fields on a user model. The checkbox selections are currently stored as an array. I'm trying to recreate the multiple checkboxes in the edit view of the Administrate dashboard for the user but not sure how (options in the docs don't seem to cover this).

Here is migration introducing the field:

class AddEmploymentTypesToUser < ActiveRecord::Migration[6.0]
  def change
    add_column :users, :employment_types, :text, array:true, default: []
  end
end

The UI from my User form that I want to recreate in the dashboard edit view: enter image description here

Code for that form:

      <div class="form-group">
        <label class="font-weight-bold" for="">What type of employment are you looking for?</label>
        <br>
        <%= f.check_box :employment_types, { :multiple => true }, 'Full-time', nil%> Full-time
        <br>
        <%= f.check_box :employment_types, { :multiple => true }, 'Part-time', nil%> Part-time
        <br>
        <%= f.check_box :employment_types, { :multiple => true }, 'Freelance', nil%> Freelance
        <br>
        <%= f.check_box :employment_types, { :multiple => true }, 'Contract-to-hire', nil%> Contract-to-hire
      </div>

Solution

  • You will have to provide a custom field type for this, as the ones that Administrate provide are not good here. I'm pretty sure that the reason for this is that this sort of field can be implemented in many ways, so Administrate can't provide a single, unified solution.

    To create a new field, inherit from Administrate::Field::Base:

    ### app/fields/checkbox_list.rb
    class CheckboxList < Administrate::Field::Base
      def self.permitted_attribute(attr, _options = nil)
        # Yes, this has to be a hash rocket `=>`,
        # not a colon `:`. Otherwise it will be the key
        # `attr` (literally) as opposed to a key whose name
        # is the value of the argument `attr`.
        { attr => [] }
      end
    
      def choices
        options[:choices]
      end
    end
    

    For your specific case, I'm implementing two methods. I'll explain them individually.

    First, there is self.permitted_attribute. This is an API that Administrate internally uses to figure out how to translate your new field type into terms of params.require(...).permit(...).

    Because your field is modelled as a list of checkboxes, params will see it as an array:

    params[:user]
    # => { name: "Conway Anderson", employment_types: ["Freelance", "Contract-to-hire"] }
    

    To tell permit to accept this, normally you do this in a Rails app:

    params.require(:user).permit(:name, employment_types: [])
    

    By implementing the CheckboxList.permitted_attributes as I did above, Administrate passes the right information (employment_types: []) to permit: it's saying to permit employment_types which will be an array value. You are probably doing this in your application somewhere else already?

    That's for the first method! Now the second: choices. This reads from options, which is the list of options provided to the field in the dashboard defintion. So for example here:

    ATTRIBUTE_TYPES = {
      id: Field::Number,
      name: Field::String,
      # ...
      employment_types: CheckboxList.with_options(choices: ['Full-time', 'Part-time', 'Freelance', 'Contract-to-hire']),
    }.freeze
    

    That way, CheckboxList can be reused for different lists of choices. Note that I'm not using the word options because it's used internally by Administrate::Field::Base already, and that would conflict.

    Moving on, your field also requires template partials, to tell Rails how to render it. These go in the views/ folder, and can look for example like this:

    ### app/views/fields/checkbox_list/_index.html.erb
    <%= field.data.join(', ') %>
    
    ### app/views/fields/checkbox_list/_show.html.erb
    <%= field.data.join(', ') %>
    
    ### app/views/fields/checkbox_list/_form.html.erb
    <div class="field-unit__label">
      <%= f.label field.attribute %>
    </div>
    <div class="field-unit__field">
      <%= f.collection_check_boxes field.attribute, field.choices, :itself, :itself %>
    </div>
    

    The trickiest one is the form. Note that I use field.choices, which is the same method choices defined in the class CheckboxList, and that reads from the options given in the dashboard.

    And I think that's it! Add the new field and type to your dashboard (and don't forget to add it to SHOW_PAGE_ATTRIBUTES, FORM_ATTRIBUTES, etc), and you should be good to go.