【问题标题】:collection_select with has_many and belongs_to带有 has_many 和 belongs_to 的 collection_select
【发布时间】:2015-12-15 22:12:44
【问题描述】:

我有一个简单的脚手架项目来测试此功能。 它有两个模型,User 模型和Fruit 模型。

我正在尝试实现一个功能,用户可以使用 collection_select 从数据库中选择现有水果,然后将其与他的名字一起列出,还可以根据水果查询用户(例如:列出所有有苹果的用户)

注意:我用水果样本填满了水果表来测试它。不确定这是否是正确的方法,但我让 collection_select 工作。

我的问题

问题有两个方面。一个水果没有保存给特定用户(即使参数具有必要的属性),第二个如果我尝试调用 User.fruits.name 我得到“水果”和 User.fruits 给我一个“#”的意外结果。

我一直在阅读并尝试我的主要项目的解决方案,该项目具有精确的结构,但它似乎不起作用。如果有更高效的版本,我也在寻找替代方案。

这里是sn-ps的代码:

参数哈希

class User < ActiveRecord::Base
  has_many :fruits
end

users/index.html.erb

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

<h1>Listing Users</h1>

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

  <tbody>
    <% @users.each do |user| %>
      <tr>
        <td><%= user.name %></td>
        <<td><%= user.fruits%></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 %>

users/_form.html.erb

<%= form_for(@user) do |f| %>
  <% 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">
    <%= f.label :name %><br>
    <%= f.text_field :name %>
  </div>

  <div class="field">
    <%= f.label :fruits %>
    <%= f.collection_select(:fruits, Fruit.all, :id, :name, :include_blank => "Please select") %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

users_controller

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

  # GET /users
  # GET /users.json
  def index
    @users = User.all
  end

  # GET /users/1
  # GET /users/1.json
  def show
  end

  # GET /users/new
  def new
    @fruit = Fruit.new
    @user = User.new
  end

  # GET /users/1/edit
  def edit
  end

  # POST /users
  # POST /users.json
  def create
    debugger
    @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

  # PATCH/PUT /users/1
  # PATCH/PUT /users/1.json
  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

  # DELETE /users/1
  # DELETE /users/1.json
  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
    # Use callbacks to share common setup or constraints between actions.
    def set_user
      @user = User.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def user_params
      params.require(:user).permit(:name, :fruit_ids)
    end
end

fruit.rb

class Fruit < ActiveRecord::Base
  belongs_to :user
end

user.rb

class User < ActiveRecord::Base
  has_many :fruits
end

schema.rb

ActiveRecord::Schema.define(version: 20151215214130) do 

create_table "fruits", force: :cascade do |t|
    t.string   "name"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.integer  "user_id"
  end

  create_table "users", force: :cascade do |t|
    t.string   "name"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

end

老实说,我已经不知所措了。我觉得我错过了一些非常基本和明显的东西。

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-4 activerecord associations


    【解决方案1】:

    似乎对user.fruits 是什么有误解。 user 上的 fruits 集合本质上是一个 Fruit 对象数组。你不想这样做

    = @user.fruits
    

    或者这个

    = User.fruits # I don't think this method on the `User` class exists
    

    也许你想这样做

    = @user.fruits.map{|f| f.name}
    

    或者这个

    = render @user.fruits
    

    后者将调用 _fruit.erb 部分并根据其中的代码进行渲染。

    # _fruit.erb
    = fruit.name
    

    要设置 user.fruits,您需要在 db 中设置它。有两种方法可以做到这一点

    1) 将水果 id 发送给 user#fruit_ids= 方法

    # for example
    @user.fruit_ids = [1,2,3,4]
    

    2) 在水果对象上设置 user_id

    @fruit.user = @user # or @fruit.user_id = @user.id
    

    您似乎想将fruit_ids 传递给用户。为此,一种常见的方法是设置复选框网格。他们需要有参数名称“user[fruit_ids][]”。末尾的额外 [] 是为了向 rails 表明它是一个数组。

    另一种方法,也许是你想要的,是设置一个多选框

    <%= f.collection_select(:fruit_ids, Fruit.all, :id, :name, { :include_blank => "Please select")}, { :multiple => true } %>
    

    在控制器中,确保强参数接受这个参数——语法是这样的

    params.require(:user).permit(:name, :fruit_ids => [])
    

    然后您可以将 ids 数组传递给控制器​​,它会在所有水果对象上正确设置 user_id。

    最后 - 我不确定你的所作所为是你的意思。 has_many、belongs_to 关系通常是父子关系。我没有看到您提前设置水果然后将其分配给用户的情况。也许这只是一个练习?另一种在水果上设置 user_id 的方法 - 您可以在用户下创建水果。

    @user.fruits << Fruit.new(:name => "Orange")
    

    如果您希望有一个水果对象(系统中只有一个橙子或一个苹果),用户可以在其中收藏或选择它而不编辑水果记录,您可以这样做。

    有另一个表格代表最喜欢的水果,看起来像这样:

    class UserFruit < ActiveRecord::Base
    
      belongs_to :user
      belongs_to :fruit
    
    end
    

    使用此模型,您可以表示所有权或最喜欢的水果,并重用水果对象,以便其他用户也可以喜欢或拥有它。

    你可以这样设置

    class User < ActiveRecord::Base
      has_many :user_fruits, :dependent => :destroy
      has_many :fruits, :through => :user_fruits
    end
    

    并更改fruit.rb

    class Fruit < ActiveRecord::Base
      has_many :user_fruits, :dependent => :destroy
      has_many :users, :through => :user_fruits
    end
    

    您仍然可以使用@user.fruit_ids=(array) 方法来创建/销毁连接表记录,您的用户表单根本不会真正改变。

    别忘了迁移

    create_table :user_fruits do |t|
      t.references :user
      t.references :fruit
    end
    

    【讨论】:

    • 看来我肯定是误会了,你帮了大忙。感谢您的整个回答。关于关联,我想要预定的水果列表,我不希望用户添加更多,我希望他能够从现有的水果行中进行选择。这是正确的方法还是让用户选择其他方法更好?我希望能够通过他们的水果找到这些用户,所以我认为有一个带有预填充行的单独模型是要走的路。
    • 关于:fruit_ids 的复数形式是Rails 约定优于配置的结果吗?我可以将其称为 :fruit_id 因为它是一个数组吗?如果您能谈到这一点,我将不胜感激(即使它可能是另一个问题的主题)
    • 是的 - rails 为你提供了 has_many 的方法 - api.rubyonrails.org/classes/ActiveRecord/Associations/…。听起来你想要连接表 - 这样水果记录就不会改变。你可以有很多 user_fruits 代表用户和水果之间的关系。例如,您也可以在 user_fruit 上进行评级。我将在如何设置连接表的答案中添加更多内容。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-07
    • 2013-05-28
    • 1970-01-01
    • 1970-01-01
    • 2015-11-27
    • 2014-09-01
    相关资源
    最近更新 更多