Rails 4.2 - как построить has_many через: ассоциацию с collection_select

В настоящее время я использую Rails 4.2 с этими моделями:

class User < ActiveRecord::Base
  has_many :user_accessories, dependent: :destroy
  accepts_nested_attributes_for :user_accessories,
    reject_if: :all_blank,
    allow_destroy: true
  has_many :accessories, through: :user_accessories
end

class UserAccessory < ActiveRecord::Base
  belongs_to :user,      required: true
  belongs_to :accessory, required: true
end

class Accessory < ActiveRecord::Base
  has_many :user_accessories, dependent: :destroy
  has_many :users, through: :user_accessories
end

Это простое отношение "многие ко многим", когда пользователь может "владеть" многими аксессуарами, а аксессуар может "принадлежать" многим пользователям.

Я настроил свои маршруты следующим образом:

resources :users, only: [ :index, :show, :edit, :update, :accessories ] do
  member do
    get 'accessories'
  end
end

Таким образом, пользователь может перейти к своему пути "аксессуаров" (/users/1/accessories), чтобы увидеть список их аксессуаров, а также добавить новые ассоциации аксессуаров в свой аккаунт.

Вот представление, которое отображается для пути аксессуаров:

<table>
  <thead>
    <tr>
      <th>Name</th>
    </tr>
  </thead>
  <tbody>
    <%= render @accessories %>
  </tbody>
</table>

<%= form_for( @user ) do |f| %>
  <%= f.collection_select( 
    :accessory_ids, 
    Accessory.all, 
    :id, 
    :name, { include_hidden: false }, { multiple: true } ) 
  %>

  <%= f.button "Add Equipment" %>
<% end %>

Это просто отображает таблицу текущих аксессуаров, а затем создает поле collection_select, где вы можете выбрать несколько аксессуаров для добавления. Я упростил это, удалив стилизацию Bootstrap.

Наконец, вот соответствующие действия контроллера:

def update
  @user = User.find( params[:id] )
  if( @user.update_attributes( update_params ) )
    redirect_to @user
  else
    render 'edit'
  end
end

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

private 
  def update_params 
    params.require( :user ).permit( 
      :first_name, 
      :last_name, 
      :region_id, 
      :country, 
      :profile_image, 
      :remove_profile_image, 
      accessory_ids: [:id], 
    )
  end

Теперь проблемы:

  1. Отправка формы, когда у меня уже есть значение, введенное вручную в базу данных, фактически просто удаляет все сохраненные в данный момент значения и не добавляет никаких новых:

    Started PATCH "/users/nuggles" for 127.0.0.1 at 2015-02-18 13:31:50 -0500
    
    Processing by UsersController#update as HTML
    
    Parameters: {
      "utf8"=>"✓",····
      "authenticity_token"=>"my_token",·
      "user"=>{
        "accessory_ids"=>["5"]
      },· 
      "button"=>"",·
      "id"=>"nuggles"
    } 
    
    User Load (40.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1  ORDER BY "users"."id" ASC LIMIT 1  [["id", 4]] 
    
    User Load (36.4ms)  SELECT  "users".* FROM "users" WHERE "users"."slug" = $1  ORDER BY "users"."id" ASC LIMIT 1  [["slug", "nuggles"]]·
    
    (33.5ms)  BEGIN
    
    Accessory Load (34.1ms)  SELECT "accessories".* FROM "accessories" INNER JOIN "user_accessories" ON "accessories"."id" = "user_accessories"."accessory_id" WHERE "user_accessories"."user_id" = $1  [["user_id", 4]]
    
    **SQL (34.1ms)  DELETE FROM "user_accessories" WHERE "user_accessories"."user_id" = $1 AND "user_accessories"."accessory_id" IN (1, 2, 3)  [["user_id", 4]]**
    
    User Exists (37.7ms)  SELECT  1 AS one FROM "users" WHERE (LOWER("users"."username") = LOWER('Nuggles') AND "users"."id" != 4) LIMIT 1
    
    (34.2ms)  COMMIT
    
    Redirected to http://localhost:3000/users/nuggles
    Completed 302 Found in 304ms (ActiveRecord: 250.0ms)
    
  2. При отправке формы без выбора я получаю следующую ошибку:

    param is missing or the value is empty: user
    
    private
      def update_params
        params.require( :user ).permit(
          :first_name,
          :last_name,
          :region_id,
    

1 ответ

Решение

Для тех, кто заинтересован, у меня есть рабочая форма. Возможно, это не идеальный способ создания нескольких записей одновременно, но сейчас это работает для меня.

Сначала я вытащил странный accessories определение в Users контроллер. Поскольку я собирался непосредственно создавать, обновлять и уничтожать пользовательские аксессуары, модель соединения заслуживает своего собственного контроллера:

class UserAccessoriesController < ApplicationController
  def index
    @user = User.friendly.find( params[:id] )
    @user_accessories = @user.user_accessories
  end

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

    create_params[:accessory].each do |accessory|
      @user_accessory = UserAccessory.new( user: @user, accessory_id: accessory )

      @user_accessory.save
    end

    redirect_to user_accessories_path( @user )
  end

  def destroy
    @user_accessory = UserAccessory.friendly.find( params[:id] )

    @user_accessory.destroy
  end

  private
    def create_params
      params.require( :user_accessory ).permit(
        :id,
        accessory: []
      )
    end
end

У меня только один взгляд на UserAccessoriesИндекс. На этой странице перечислены все аксессуары пользователя, и, если отображаемый пользователь совпадает с зарегистрированным пользователем, отображается следующая форма:

  <%= form_for @user_accessories.new(), { method: :post } do |f| %>
    <div class="form-group">
      <%= f.collection_select(
                               :accessory,
                               # Get only accessories not on the user's account
                               Accessory.where.not( id: @user.accessories ),
                               :id,
                               :name,
                               {
                                 include_hidden: false
                               },
                               {
                                 multiple:    true,
                                 class:       'form-control',
                                 placeholder: 'Select Equipment to Add'
                               }
                             ) %>
    </div>

    <div class="form-group">
      <%= f.submit "Update Equipment" %>
    </div>
  <% end %>

Это позволяет пользователю выбрать несколько аксессуаров (используя select2), а затем при отправке создает связь по одному в контроллере.

Если у кого-то есть лучший способ создания пользовательских аксессуаров (то есть не повторять мои параметры и создавать это много раз), пожалуйста, оставьте комментарий!

Другие вопросы по тегам