ruby-on-railscollection-select

collection_select in ROR form passes value as string and does not save


I am trying to use collection_select (using bootstrap_form_for) to save associated record. The save works well using rails console so I am assuming the model definitions are good (and so is the controller code).

Models: User -

class User < ApplicationRecord
    validates :name, presence: true, length: {maximum: 50}
    validates :email, presence: true, length: {maximum: 250}, uniqueness: true

    has_secure_password
    validates :password, length: {minimum: 6}

    belongs_to :role
    has_many :issues_owned, :class_name => 'Issue', :foreign_key => 'owner'
    has_many :issues_assigned, :class_name => 'Issue', :foreign_key => 'assigned_to'    
end

Model Issue -

class Issue < ApplicationRecord
    belongs_to :assigned_to, :class_name => 'User'
    belongs_to :owner, :class_name => 'User'

    validates :summary, presence: true, length: {maximum: 100}
    validates :description, presence: true, length: {maximum: 250}
    validates :severity, presence: true, length: {maximum: 10}
    validates :priority, presence: true, length: {maximum: 10}

end

Form code for Issue addition -

  <%= bootstrap_form_for(@issue, layout: :horizontal, label_col: "col-sm-2") do |form| %>

    <%= form.text_field :summary, control_col: 'col-md-4',class: 'input-sm' %>
    <%= form.text_area :description, control_col: 'col-md-8', class: 'input-sm' %>
    <%= form.text_field :severity, control_col: 'col-md-2', class: 'input-sm' %>
    <%= form.text_field :priority, control_col: 'col-md-2', class: 'input-sm' %>
    <%= form.collection_select :assigned_to,  @users, :id, :name, control_col: 'col-md-4' , class: 'input-sm' %>
    <%= form.date_field :resolved_at, control_col: 'col-md-4' , class: 'input-sm'%>
    <%= form.hidden_field :owner, control_col: ' col-md-4', value: @current_user %>
    <%= form.form_group do %>
      <%= form.primary "Create Issue", class:'btn btn-sm btn-primary' %>
    <% end %>

  <% end %>

Fields of interest are :assigned_to - using collection_select and :owner - which is initialized with @current_user.

In the controller - the code is standard as of now

  def create
    @issue = Issue.create(issue_params)

    respond_to do |format|
      if @issue.save!
        format.html { redirect_to @issue, notice: 'Issue was successfully created.' }
        format.json { render :show, status: :created, location: @issue }
      else
        format.html { render :new }
        format.json { render json: @issue.errors, status: :unprocessable_entity }
      end
    end
  end

... and

def issue_params
  params.require(:issue).permit(:summary,:description,:severity,:priority,:assigned_to, :owner,:resolved_at)
end

As I mentioned earlier, this code works thru the console - adds Issue record without any error. However, when I try to use it thru form submission, I get the following error.

User(#70029712374140) expected, got "1" which is an instance of String(#47333497052640)

I have gone thru the documentation and similar questions on SO but have not been able to find a solution. The :owner field value is passed properly as User object but the :assigned_to field is passed as string. For some reason, the ruby magic does not work for me. I am sure this is something trivial but am not able to find fault since couple of days and hence the question.

Any pointers would be appreciated.

TIA.


Solution

  • I think that your problem is probably related to the names of your foreign keys on an Issue. An Issue belongs_to an :owner and an :assigned_to but in the User model the foreign keys are also :owner and :assigned_to -- this leads me to believe that in your database you have columns named :owner and :assigned_to on your issues table

    The way to associate that an Issue object belongs_to a User is to have foreign_keys that are IDs like :owner_id and :assigned_to_id. So change your issues table foreign_keys from :object to :object_id and :assigned_to to :assigned_to_id. Forms are not meant to pass objects in as params, they should be passing in IDs.

    Your line: <%= form.collection_select :assigned_to, @users, :id, :name, control_col: 'col-md-4' , class: 'input-sm' %> is working as expected - it creates select options for the :assigned_to key where the values are the IDs of your users and the labels are the names. As mentioned, I would change your database to have a :assigned_to_id on your Issue object and then update this line to:

    <%= form.collection_select :assigned_to_id, @users, :id, :name, control_col: 'col-md-4' , class: 'input-sm' %>

    You would then also have an :owner_id on your Issue object so your hidden_field should change to:

    <%= form.hidden_field :owner_id, control_col: ' col-md-4', value: @current_user.id %>

    (note that if there is a chance that @current_user could be nil then change out @current_user.id with @current_user.try(:id))

    You will need to update your strong_params in your controller to:

    def issue_params
      params.require(:issue).permit(:summary,:description,:severity,:priority,:assigned_to_id, :owner_id,:resolved_at)
    end
    

    And finally if you change those columns to be :owner_id and :assigned_to_id then you need to update your User model to have these relationships:

        has_many :issues_owned, :class_name => 'Issue', :foreign_key => 'owner_id'
        has_many :issues_assigned, :class_name => 'Issue', :foreign_key => 'assigned_to_id'