Rails nested Attributes of join table won't be

2019-09-17 14:26发布

问题:

Nested attributes of join model won't be saved. relation id's seems to be missing. The following error messages are added when the fields get validated:

* Assigned projects user can't be blank
* Assigned projects project can't be blank

The submitted params look like this ( <%= debug(params) %> )

--- !map:ActionController::Parameters 
utf8: "\xE2\x9C\x93"
authenticity_token: HrF1NHrKNTdMMFwOvbYFjhJE1ltlKbuz2nsfBYYBswg=
project: !map:ActionController::Parameters 
  name: Peter Pan
  assigned_projects_attributes: !map:ActiveSupport::HashWithIndifferentAccess 
    "0": !map:ActiveSupport::HashWithIndifferentAccess 
      position: Group Leader
  currency: " Neverland Dollars"
commit: Add Project
action: create
controller: projects

I have 3 models, as followed:

class User < ActiveRecord::Base
  has_many :assigned_projects
  has_many :projects, :through => :assigned_projects
  has_many :created_projects, :class_name => "Project", :foreign_key => :creator_id
end

class AssignedProject < ActiveRecord::Base
    belongs_to :user,    class_name: "User"
    belongs_to :project, class_name: "Project"

    attr_accessible :project_id, :user_id, :position, :project_attributes
    accepts_nested_attributes_for :project

    validates :user_id,     presence: true
    validates :project_id,  presence: true
    validates :position,    presence: true
end

class Project < ActiveRecord::Base
  belongs_to :user
  has_many :assigned_projects
  has_many :users, :through => :assigned_projects
  belongs_to :creator, :class_name => "User", :foreign_key => :creator_id

  attr_accessible :name, :creator_id, :currency :assigned_projects_attributes
  accepts_nested_attributes_for :assigned_projects

  validates :name, presence: true, length: { minimum: 3, maximum: 100 }
  validates :currency, presence: true, length: { minimum: 1, maximum: 5 }
  validates :creator_id, presence: true
end

So each User can create a Project. He can add any User to the Project through the join model. Each Project belongs to a User resp. Creator and has_many user through assigned_projects

I want to give each user of a project a "position", which should be saved in the join model: assigned_project :position

the Project controller looks like that:

class ProjectsController < ApplicationController

    def new
        @project = Project.new
        @project.assigned_projects.build(user_id: current_user)
    end

    def create
        @project = current_user.assigned_projects.build.build_project(params[:project])
        @project.creator = current_user

        if @project.save
            redirect_to current_user
        else
            render 'new'
        end
    end
end

and the project/new.html.erb form looks like that:

<%= form_for( @project ) do |f| %>

    <%= render 'shared/error_messages', object: f.object %>

    <%= f.label :name %>
    <%= f.text_field :name %>

    <%= f.fields_for :assigned_projects do |ff| %>
        <%= ff.label :position %>
        <%= ff.text_field :position%>
    <% end %>

    <%= f.label :currency %>
    <%= f.text_field :currency %>

    <%= f.submit "Add Project", class: "" %>                    
<% end %>

UPDATE: current controller & view

def create
    @project = Project.new(params[:project])
    if @project.save
        redirect_to current_user
    else
        render 'new'
    end
end

<%= form_for( @project ) do |f| %>

    <%= render 'shared/error_messages', object: f.object %>

    <%= f.label :name %>
    <%= f.text_field :name %>

    <%= f.hidden_field :creator_id, value: current_user.id %>

    <%= f.fields_for :assigned_projects, @project.assigned_projects do |ff| %>
        <%= ff.label :position %>
        <%= ff.text_field :position%>
    <% end %>

    <%= f.label :currency %>
    <%= f.text_field :currency %>

    <%= f.submit "Add Project", class: "" %>
<% end %>                   

回答1:

View:

I think you need to pass the objects collection @project.assigned_projects you built in the new action to the fields_for:

<%= render 'shared/error_messages', object: f.object %>

<%= f.label :name %>
<%= f.text_field :name %>

<%= f.fields_for :assigned_projects, @project.assigned_projects do |ff| %>
    <%= ff.label :position %>
    <%= ff.text_field :position%>
<% end %>

<%= f.label :currency %>
<%= f.text_field :currency %>

<%= f.submit "Add Project", class: "" %>                    

Controller:

If i understood the first line in the create action i think you try to re-build the project assigned_projects in-order to stamp the creator attribute !!

Instead you could remove this line and put a hidden field in the nested for, something like:

<%= ff.hidden_field :creator, current_user %>

So your controller looks pretty basic now:

def create
  @project = Project.new(params[:prject])
  if @project.save #nested objects will be saved automatically
    redirect_to current_user
  else
    render 'new'
  end
end


回答2:

What does the build_project method do? I think in your controller you should just have build, not build.build_project, so like this:

@project = current_user.assigned_projects.build(params[:project])

of if build_project is a method used to create the params then

 @project = current_user.assigned_projects.build(project_params)

in the case of rails 4 you would need something like this:

def project_params
    params.require(:project).permit(:your_params)
end

In the case of rails 3 I think you need to add

attr_accessible :param1, :param2 

in the project model for the parameters you want to set.



回答3:

Problem is solved by removing the validations for project_id and user_id in the join table "AssignedProject"

So the join Model looks like that:

# Join Model AssignedProject
class AssignedProject < ActiveRecord::Base
    belongs_to :user#,    class_name: "User"
    belongs_to :project#, class_name: "Project"

    attr_accessible :project_id, :user_id, :position, :project, :project_attributes
    accepts_nested_attributes_for :project

    validates :position,  presence: { message: " can't be blank." }
end

The New and Create methods look like that:

# Projects Controller
class ProjectsController < ApplicationController
    def new
        @project = Project.new
        @project.assigned_projects.build(user_id: current_user)
    end

    def create
        @project = Project.new(params[:project])

        if @project.save
            @project.assigned_projects.create(user_id: current_user)
            redirect_to current_user
        else
            render 'new'
        end
    end
end

And the form in the view for the new method looks like that:

<%= form_for( @project ) do |f| %>

    <%= render 'shared/error_messages', object: f.object %>

    <%= f.label :name %>
    <%= f.text_field :name %>

    <%= f.hidden_field :creator_id, value: current_user.id %>

    <%= f.fields_for :assigned_projects, @project.assigned_projects do |ff| %>

        <%= ff.hidden_field :project_id, value: @project %>
        <%= ff.hidden_field :user_id, value: current_user.id %>

        <%= ff.label :position %>
        <%= ff.text_field :position%>
    <% end %>

    <%= f.label :currency %>
    <%= f.text_field :currency %>

    <%= f.submit "Add Project", class: "" %>                    
<% end %>