如何添加多对多列的记录

Sal*_*lil 6 ruby many-to-many ruby-on-rails ruby-on-rails-4 accepts-nested-attributes

我有以下模特

用户

has_many :users_contacts
has_many :contacts, through: :users_contacts
accepts_nested_attributes_for :contacts, allow_destroy: true
Run Code Online (Sandbox Code Playgroud)

联系

has_many :users_contacts
has_many :users, through: :users_contacts
accepts_nested_attributes_for :users_contacts, allow_destroy: true
Run Code Online (Sandbox Code Playgroud)

UsersContact

belongs_to :users
belongs_to :contacts
Run Code Online (Sandbox Code Playgroud)

我正在使用以下强参数

params.require(:user).permit(:id, :email, 
                      contacts_attributes: [:id, :first_name, :last_name, 
                      users_contacts_attributes: [:id, :contact_id, :user_id, :order]])
Run Code Online (Sandbox Code Playgroud)

我现在面临的问题是每当我更新与用户users_contacts_attributes如{CONTACT_ID:5,顺序为:5}它会创建两个记录一个与order: nil及其他与order: 5我在PARAMS获得订单.

我想跟随

  1. 不希望创建重复记录.

  2. 使用额外列保存记录在连接表中,即 order

我的参数类似于以下内容

{"id"=>4,
 "email"=>"abc@xyz.com",
 "contacts_attributes"=>
  [{"id"=>150,
    "first_name"=>"Pqr",
    "is_shareable"=>true,
    "users_contacts_attributes"=>[{"id"=>87, "user_id"=>4, "contact_id"=>150, "order"=>100}]
   },
   {"first_name"=>"Def",
    "is_shareable"=>true,
    "users_contacts_attributes"=>[{"user_id"=>4, "order"=>101}]
   }
  ]
}
Run Code Online (Sandbox Code Playgroud)

egg*_*oll 2

没有看到您的控制器或表单,很难确定具体是什么导致了您的问题。除了上面 @surya 的评论之外,我注意到类belongs_to中的两个语句UsersContact应该是单数,而不是复数(:user, :contact)。另外,不确定您需要嵌套users_contacts_attributescontacts_attributes其中 then 使其嵌套两层深useraccepts_nested_attributes_for :users_contacts您可以在类中单独使用User

除此之外,您还必须将您的代码与我在下面提供的代码进行比较,看看是否可以找到问题所在。

注意:该形式非常基本,我修改了一些名称,以便更清楚地指示何时使用单数和复数以及可读性。

楷模

class User < ActiveRecord::Base
  has_many :user_contact_pairs, inverse_of: :user
  has_many :contacts, through: :user_contact_pairs
  accepts_nested_attributes_for :contacts, allow_destroy: true
  accepts_nested_attributes_for :user_contact_pairs, allow_destroy: true
end

class Contact < ActiveRecord::Base
  has_many :user_contact_pairs, inverse_of: :contact
  has_many :users, through: :user_contact_pairs
  accepts_nested_attributes_for :user_contact_pairs, allow_destroy: true
end

class UserContactPair < ActiveRecord::Base
  belongs_to :contact
  belongs_to :user
end
Run Code Online (Sandbox Code Playgroud)

控制器

class UsersController < ApplicationController
  before_action :set_user, only: [:show, :edit, :update, :destroy]

  def index
    @users = User.all
  end

  def show
  end

  def new
    @user = User.new
  end

  def edit
    @user.user_contact_pairs.build(user_id: @user.id)
   end

  def create
    @user = User.new(user_params)

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

  def update
    respond_to do |format|
      if @user.update(user_params)
        format.html { redirect_to @user, notice: 'User was successfully updated.' }
        format.json { render :show, status: :ok, location: @user }
      else
        format.html { render :edit }
        format.json { render json: @user.errors, status: :unprocessable_entity }
      end
    end
  end

  def destroy
    @user.destroy
    respond_to do |format|
      format.html { redirect_to users_url, notice: 'User was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private

    def set_user
      @user = User.find(params[:id])
    end

    def user_params
      params.require(:user).
          permit(:email, :first_name, :last_name, :username,
                  contacts_attributes:
                  [:id, :first_name, :last_name],
                  user_contact_pairs_attributes:
                  [:id, :contact_id, :user_id, :order_number])
    end

end
Run Code Online (Sandbox Code Playgroud)

查看(用户/index.html.erb)

<p id="notice"><%= notice %></p>

<h1>Listing Users</h1>

<table>
  <thead>
    <tr>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td><%= 'Id' %></td>
      <td><%= 'First Name' %></td>
      <td><%= 'Last Name' %></td>
      <td><%= 'Username' %></td>
      <td></td>
      <td></td>
      <td></td>
    </tr>
    <% @users.each do |user| %>
      <tr>
        <td><%= user.id %></td>
        <td><%= user.first_name %></td>
        <td><%= user.last_name %></td>
        <td><%= user.username %></td>
        <td><%= link_to 'Show', user %></td>
        <td><%= link_to 'Edit', edit_user_path(user) %></td>
        <td><%= link_to 'Destroy', user, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>

<br>

<%= link_to 'New User', new_user_path %>
Run Code Online (Sandbox Code Playgroud)

查看(用户/edit.html.erb)

<h1>Editing User</h1>

<%= render 'form' %>

<%= link_to 'Show', @user %> |
<%= link_to 'Back', users_path %>
Run Code Online (Sandbox Code Playgroud)

查看(用户/_form.html.erb)

<%= form_for(@user) do |user_form| %>
  <% if @user.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:</h2>

      <ul>
      <% @user.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= user_form.label :email %>
    <%= user_form.email_field :email %><br>
    <%= user_form.label :first_name %>
    <%= user_form.text_field :first_name %><br>
    <%= user_form.label :last_name %>
    <%= user_form.text_field :last_name %><br>
    <%= user_form.label :username %>
    <%= user_form.text_field :username %><br>
  </div>

  <h2>User Contact Pair</h2>
  <%= user_form.fields_for :user_contact_pairs do |ucp_fields| %>
    <div class="user-contact-pair">
      <div class="field">
        <%= ucp_fields.label :contact_id %>
        <%= ucp_fields.number_field :contact_id %><br>
        <%= ucp_fields.label :order_number %>
        <%= ucp_fields.number_field :order_number %><br>
      </div>
    </div>
  <% end %>

  <div class="actions">
    <%= user_form.submit %>
  </div>
<% end %>
Run Code Online (Sandbox Code Playgroud)

迁移

class CreateUser < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :email, index: { unique: true }
      t.string :first_name
      t.string :last_name
      t.string :username, index: { unique: true }
      t.timestamps null: false
    end
  end
end

class CreateContact < ActiveRecord::Migration
  def change
    create_table :contacts do |t|
      t.string :first_name
      t.string :last_name
      t.timestamps null: false
    end
  end
end

class CreateUserContactPair < ActiveRecord::Migration
  def change
    create_table :user_contact_pairs do |t|
      t.integer :contact_id
      t.integer :user_id
      t.integer :order_number
      t.timestamps null: false
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

数据库种子文件

User.create(email: 'lhawkinsa@icio.us',            first_name: 'Lisa',    last_name: 'Hawkins',  username: 'lhawkinsa')
User.create(email: 'htaylorb@imdb.com',            first_name: 'Helen',   last_name: 'Taylor',   username: 'htaylorb')
User.create(email: 'gtaylorc@unblog.fr',           first_name: 'Gregory', last_name: 'Taylor',   username: 'gtaylorc')
User.create(email: 'hlaned@whitehouse.gov',        first_name: 'Henry',   last_name: 'Lane',     username: 'hlaned')
User.create(email: 'hphillipse@howstuffworks.com', first_name: 'Harry',   last_name: 'Phillips', username: 'hphillipse')
User.create(email: 'jgonzalesf@com.com',           first_name: 'Jeffrey', last_name: 'Gonzales', username: 'jgonzalesf')
User.create(email: 'ljamesg@sfgate.com',           first_name: 'Lori',    last_name: 'James',    username: 'ljamesg')
User.create(email: 'rhillh@gnu.org',               first_name: 'Roger',   last_name: 'Hill',     username: 'rhillh')
User.create(email: 'rharveyi@tripadvisor.com',     first_name: 'Raymond', last_name: 'Harvey',   username: 'rharveyi')
User.create(email: 'sperryj@mit.edu',              first_name: 'Stephen', last_name: 'Perry',    username: 'sperryj')

Contact.create(first_name: 'Louis', last_name: 'Harris')
Contact.create(first_name: 'Fred', last_name: 'Adams')
Contact.create(first_name: 'David', last_name: 'Lane')
Contact.create(first_name: 'Kevin', last_name: 'Ryan')
Contact.create(first_name: 'Samuel', last_name: 'Jones')
Run Code Online (Sandbox Code Playgroud)

Rails 控制台(两个用户通过表单更新后)

Running via Spring preloader in process 30656
Loading development environment (Rails 4.2.7.1)

    2.3.3 :001 > UserContactPair.all
UserContactPair Load (0.7ms)  SELECT "user_contact_pairs".* FROM "user_contact_pairs"
=> #<ActiveRecord::Relation [#<UserContactPair id: 1, contact_id: 1, user_id: 1, order_number: 1, created_at: "2017-02-14 09:05:31", updated_at: "2017-02-14 09:05:31">, #<UserContactPair id: 2, contact_id: 4, user_id: 1, order_number: 4, created_at: "2017-02-14 09:05:50", updated_at: "2017-02-14 09:05:50">]> 

    2.3.3 :002 > 
Run Code Online (Sandbox Code Playgroud)

更新(回应评论):

这是更新后的代码,应该可以满足您的要求。您应该在上述代码和此附加代码中添加必要的验证,以确认存在性和唯一性。你们很多人还想排序Contact对用于填充表单选择下拉列表的集合进行排序。

(注意:由于使用嵌套属性会导致在参数哈希中添加数字键(例如 [“0”]),这取决于对象具有的嵌套属性类的关联记录的数量,我已将hashie gem添加到 gem 文件中,它提供了通过 查找哈希中深度嵌套的键的能力.deep_find,从而可以绕过这些不同数字的键,直接访问所需的键。)

模型(向联系人类添加了用于表单选择下拉列表的方法)

  def last_name_first
    self.last_name + ', ' + self.first_name
  end
Run Code Online (Sandbox Code Playgroud)

控制器(已更新编辑更新操作)

  def edit
    @contacts = Contact.all
    @user.contacts.build
    @user.user_contact_pairs.build
  end

  def update
    @user = User.find(params[:id])

    user_only_params = { email: params[:user][:email], first_name: params[:user][:first_name], last_name: params[:user][:last_name], username: params[:user][:username] }
    contact_params = params[:user][:contacts_attributes]
    user_contact_pair_params = params[:user][:user_contact_pairs_attributes]

    contact_params.extend(Hashie::Extensions::DeepFind)
    contact_first_name = contact_params.deep_find(:first_name)
    contact_last_name = contact_params.deep_find(:last_name)

    user_contact_pair_params.extend(Hashie::Extensions::DeepFind)
    user_contact_pair_contact_id = user_contact_pair_params.deep_find(:contact_id)
    user_contact_pair_order_number = user_contact_pair_params.deep_find(:order_number)

    @user.assign_attributes(user_only_params)

    if user_contact_pair_order_number != ''
      if contact_first_name != '' && contact_last_name != ''
        @contact = Contact.create(first_name: contact_first_name, last_name: contact_last_name)
        @user.user_contact_pairs.new(contact_id: @contact.id, order_number: user_contact_pair_order_number.to_i)
      elsif user_contact_pair_contact_id != ''
        @user.user_contact_pairs.new(contact_id: user_contact_pair_contact_id.to_i, order_number: user_contact_pair_order_number.to_i)
      end
    end
    respond_to do |format|
      if @user.save
        format.html { redirect_to @user, notice: 'User was successfully updated.' }
        format.json { render :show, status: :ok, location: @user }
      else
        @contact.destroy if @contact.exists?
        format.html { render :edit }
        format.json { render json: @user.errors, status: :unprocessable_entity }
      end
    end
  end
Run Code Online (Sandbox Code Playgroud)

查看(用户/_form.html.erb)

<%= form_for(@user) do |user_form| %>
  <% if @user.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:</h2>

      <ul>
      <% @user.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="user">
    <h2 style="margin-bottom: 4px">User</h2>
    <%= user_form.label :email %><br>
    <%= user_form.email_field :email %><br><br>
    <%= user_form.label :first_name %><br>
    <%= user_form.text_field :first_name %><br><br>
    <%= user_form.label :last_name %><br>
    <%= user_form.text_field :last_name %><br><br>
    <%= user_form.label :username %><br>
    <%= user_form.text_field :username %><br><br>
  </div>

  <%= user_form.fields_for :user_contact_pairs do |ucp_fields| %>
    <% if ucp_fields.object.new_record? %>
      <h2 style="margin-bottom: 4px">Use Existing Contact</h2>
      <%= ucp_fields.collection_select(:contact_id, @contacts, :id, :last_name_first, prompt: "Select...") %><br><br>
    <% end %>
  <% end %>

  <%= user_form.fields_for :contacts do |contact_fields| %>
    <% if contact_fields.object.new_record? %>
      <h2 style="margin-bottom: 4px">Or Create New Contact</h2>
      <%= contact_fields.label 'First Name' %><br>
      <%= contact_fields.text_field :first_name %><br><br>
      <%= contact_fields.label 'Last Name' %><br>
      <%= contact_fields.text_field :last_name %><br><br>
    <% end %>
  <% end %>

  <%= user_form.fields_for :user_contact_pairs do |ucp_fields| %>
    <% if ucp_fields.object.new_record? %>
      <h2 style="margin-bottom: 4px">Order</h2>
      <%= ucp_fields.label 'Order Number' %><br>
      <%= ucp_fields.number_field :order_number %><br><br>
    <% end %>
  <% end %>

  <div class="actions">
    <%= user_form.submit %>
  </div>

<% end %>
Run Code Online (Sandbox Code Playgroud)