Search code examples
ruby-on-railsactiveadminformtastic

Show checkboxes instead of text input in ActiveAdmin


In the model Code there is a field named week_days that could be something like this 1100110. Instead of a classic input field, I want to display 7 checkboxes in an ActiveAdmin form like this

  form do |f|
    f.inputs do
      f.input :name
      f.input :week_days
    end
    f.actions
  end

so I thought to write a mapper like this in the model

  def week_days_checkboxes
    [
        ['sunday', 1],
        ['monday', 1],
        ['tuesday', 1],
        ['wednesday', 1],
        ['thursday', 1],
        ['friday', 1],
        ['saturday', 1]
    ]
  end

  def week_days_checkboxes=(week_days_checkboxes)
    @week_days = week_days_checkboxes.reverse.join.to_i(2)
  end

and then change the form input this way

f.input :week_days_checkboxes, as: :check_boxes

but it doesn't work. What do I have to do?

PS: the second value of each pair should be 1 or 0, basing on each value of the field week_days

Thanks a lot


Solution

  • It doesn't work for several reasons:

    1. Assigning an instance variable in an AR model will not achieve what you want. You should use self[:column_name]= value or write_attribute(:column_name, value) (I think the latter is more intention revealing). For reading you could use self[:column_name] or the read_attribute method.
    2. When you post a form with check boxes or multiple selections only the selected values will be sent back to the server (this is how browsers work). This means that if you only select one day the value of the array will always be ['', '1'] regardless of what day you selected. Basically using uniform values is not the way to go in this situation.
    3. Returning an array of arrays from week_days_checkboxes method will not display the list of choices since it represents the value not the choices. You have to explicitly set the collection.

      f.input :week_days, as: :check_boxes, collection: week_days_for_select
      

      Where week_days_for_select returns an appropriate list.

      And when your are editing an existing model the previously selected days won't be selected in the checkbox list. For that to work you need to return a list of values that match to the checkbox inputs' values.

    One solution could be:

    In your view:

    f.input :week_days, as: :check_boxes, collection: Date::DAYNAMES
    

    And then in your model create a wrapper for week_days so it accepts a list and turns it into an integer and vice versa.

    def week_days
      return [] if read_attribute(:week_days).blank?
    
      # first you have to convert the integer to a binary array
      w = read_attribute(:week_days)
      days_as_binary = Math.log2(w).floor.downto(0).map { |nth_bit| w[nth_bit] }
    
      # alternatively
      # days_as_binary = read_attribute(:week_days).to_s(2).split('').map(&:to_i)
    
      # make sure it has an element for every day of the week
      padded_binary = [0] * (7 - days_as_binary.size) + days_as_binary
    
      # map the binary array to day names
      padded_binary.each_with_index.map do |d, idx|
        Date::DAYNAMES[idx] if d == 1
      end.compact
    end
    
    def week_days=(values)
      days = Date::DAYNAMES.map { |d| values.include?(d) ? 1 : 0 }
      write_attribute(:week_days, days.join.to_i(2))
    end
    

    Although I think representing the selected days as an integer is not the best option you have. If you are using PostgreSQL, AR 4+ has good support for arrays - you could save a list of days to the database.