0

I'm currently working on a form to edit a user's information, and part of the form is to check off their roles that they have.

roles are stored in their own table that has the role type and the user's ID.

What I'm looking to do is put the checkboxes for the 3 types of roles, and the user can check off which roles they are supposed to have.

Essentially the form should look like this:

enter image description here

The problem is I can't figure out how to set this up with the form builder. I have accepts_nested_attributes_for :roles set on user, but I'm not sure how this would work with fields_for.

Any ideas?

2 Answers 2

1

You don't need nested attributes to simply assign associations to a record.

To start with you want to alter your tables and associations to create a normalization table:

class User < ApplicationRecord
  has_many :user_roles, dependent: :destroy
  has_many :roles, through: :user_roles
end

# remove user_id from the roles table
class Role < ApplicationRecord
  validates_uniqueness_of :name
  has_many :user_roles, dependent: :destroy
  has_many :users, through: :user_roles
end

# rails g model user_role user:references role:references
class UserRole < ApplicationRecord
  validates_uniqueness_of :role_id, scope: :user_id
  belongs_to :user
  belongs_to :role
end

This means the definition name of a role is only defined once in the roles table instead of being duplicated for every role applied to a user. And to assign the role to a user you're just adding a role id and user id in a join table which can be indexed much more efficiently.

Since we don't need to set that duplicate name column for every user role we can just use role_ids= to set the roles of a user with a checkbox.

<%= form_for(@user) do |f| %>
  <%= f.collection_check_boxes(:role_ids, Role.all, :id, :name) %>
  ...
<% end %>

class UsersController < ApplicationController

  # ...

  def create
    @user = User.new(user_params)
    # ...
  end

  # ...

  private
    def user_params
      params.require(:user)
            .permit(:foo, :bar, role_ids: [])
    end
end
Sign up to request clarification or add additional context in comments.

4 Comments

I'd like to be able to do this, but our roles are not normalized and I do not have the time to make changes to the already established role setup
I think you are overestimating the complexity of the change vs the cost of hanging onto a flawed solution.
Engineering a hack around accepts nested attributes is going to take quite a bit a time in this case as well as you have to make the checkboxes fill in a text input for each nested record, or create some some kind of hack in the controller to change the params.
Thanks, @max! You can't imagine how much this answer has helped me!
-1

https://guides.rubyonrails.org/form_helpers.html#nested-forms

The example given there is :

<%= form_for @person do |f| %>
  Addresses:
  <ul>
    <%= f.fields_for :addresses do |addresses_form| %>
      <li>
        <%= addresses_form.label :kind %>
        <%= addresses_form.text_field :kind %>

        <%= addresses_form.label :street %>
        <%= addresses_form.text_field :street %>
        ...
      </li>
    <% end %>
  </ul>
<% end %>

Adapting that to have I'm assuming you've setup your models:

<%= form_for @user do |f| %>
  <%= f.input :first_name %>
  <%= f.input :last_name %>
  Role:
  <ul>
    <%= f.fields_for :roles do |role_f| %>
      <li>
        <%= role_f.check_box :it %>
        <%= role_f.check_box :accounting %>
        <%= role_f.check_box :sales%>
      </li>
    <% end %>
  </ul>
<% end %>

From there you can see how the params come through and create the roles as needed

4 Comments

I'm guessing that roles has string column which contains the type and not a bunch of boolean columns which would be a really stupid design.
This would allow you to create multiple roles on one record, but thanks for your delightful comment either way
still a horrible design - imagine querying for users based on what roles they have.
It would involve adding 1 line of code to create a scope. I'm not sure which part would be challenging for you.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.