Rails 表单 collection_select 未保存到数据库

Rails Form collection_select not saving to database

我是一个 rails 新手,正在构建一个小应用程序来帮助我的工作。

我有客户端、站点和报价模型以及设置了视图的控制器。

我在报价模型上创建了一个表单,该表单从 collection_select 字段中的其他两个模型中提取数据。我发现关于 collection_select for rails 的文档非常糟糕。我想获取客户名称和网站名称并关联/显示报价中的名称。

我已经在表格中设置了这个,但是它不保存数据也不显示。

我真的很想了解 collection_select 的输入,因为我确信我的输入可能是错误的并导致了问题。

<%= f.collection_select :client, Client.all, :quote_client, :client_name , {:prompt => "Please select a client for the site"} %>

我做了一些研究并从@juanpastas 那里学到了这个 here

我的表格看起来像这样 quotes/views/_form.html

<%= form_for(quote) do |f| %> 
    <% if quote.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(quote.errors.count, "error") %> prohibited this quote from being saved:</h2><ul>
      <% quote.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %><div class="field">
    <%= f.label :client %>
    <%= f.collection_select :client, Client.all, :quote_client, :client_name , {:prompt => "Please select a client for the site"} %>
  </div><div class="field">
    <%= f.label :site_name %>
    <%= f.collection_select :site, Site.all, :quote_site, :site_name , {:prompt => "Please select a site for the quote"} %>
  </div><div class="field">
    <%= f.label :quote_contact %>
    <%= f.text_field :quote_contact %>
  </div><div class="field">
    <%= f.label :quote_value %>
    <%= f.text_field :quote_value %>
  </div><div class="field">
    <%= f.label :quote_description %>
    <%= f.text_field :quote_description %>
  </div><div class="actions">
    <%= f.submit %>
  </div>
<% end %>

编辑

Answers/clarifications

报价只能有一个客户和一个站点。该网站也必须属于客户。

我有一个通过 Client.all 从客户端模型调用的客户端列表和一个通过 Site.all 调用的站点模型的站点列表。我只需要一个客户的名称和每个报价的一个站点,但希望能够以级联方式 select。 Select 客户,然后是客户可用的 selects 站点。

三个模型之间的关系是这样建立的:

 class Quote < ApplicationRecord


            belongs_to :site, optional: true
            belongs_to :client, optional: true
            has_and_belongs_to_many :assets
    end

class Site < ApplicationRecord


    has_attached_file :site_image, styles: { small: "64x64", med: "100x100", large: "200x200" }
    do_not_validate_attachment_file_type :site_image


    belongs_to :client , optional: true
    has_and_belongs_to_many :assets
    has_and_belongs_to_many :quotes
end

class Client < ApplicationRecord

    has_and_belongs_to_many :sites
    has_and_belongs_to_many :assets
    has_and_belongs_to_many :quotes
end

控制器

class QuotesController < ApplicationController
  before_action :set_quote, only: [:show, :edit, :update, :destroy]

  # GET /quotes
  # GET /quotes.json
  def index
  @quotes = Quote.all
  end

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

  # GET /quotes/new
  def new
    @quote = Quote.new
  end

  # GET /quotes/1/edit
  def edit
  end

  # POST /quotes
  # POST /quotes.json
  def create
    @quote = Quote.new(quote_params)

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

  # PATCH/PUT /quotes/1
  # PATCH/PUT /quotes/1.json
  def update
    respond_to do |format|
      if @quote.update(quote_params)
        format.html { redirect_to @quote, notice: 'Quote was successfully updated.' }
        format.json { render :show, status: :ok, location: @quote }
      else
        format.html { render :edit }
        format.json { render json: @quote.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /quotes/1
  # DELETE /quotes/1.json
  def destroy
    @quote.destroy
    respond_to do |format|
      format.html { redirect_to quotes_url, notice: 'Quote was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_quote
      @quote = Quote.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def quote_params
      params.require(:quote).permit(:quote_client, :quote_site, :client_name, :site_name, :quote_contact, :quote_value, :quote_description)
    end
end


class SitesController < ApplicationController
  before_action :set_site, only: [:show, :edit, :update, :destroy]

  # GET /sites
  # GET /sites.json
  def index
    @sites = Site.all
    @clients = Client.all
  end

  # GET /sites/1
  # GET /sites/1.json
  def show
      @sites = Site.all
      @clients = Client.all
  end

  # GET /sites/new
  def new
    @site = Site.new
  end

  # GET /sites/1/edit
  def edit
  end

  # POST /sites
  # POST /sites.json
  def create
    @site = Site.new(site_params)

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

  # PATCH/PUT /sites/1
  # PATCH/PUT /sites/1.json
  def update
    respond_to do |format|
      if @site.update(site_params)
        format.html { redirect_to @site, notice: 'Site was successfully updated.' }
        format.json { render :show, status: :ok, location: @site }
      else
        format.html { render :edit }
        format.json { render json: @site.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /sites/1
  # DELETE /sites/1.json
  def destroy
    @site.destroy
    respond_to do |format|
      format.html { redirect_to sites_url, notice: 'Site was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_site
      @site = Site.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def site_params
      params.require(:site).permit(:site_client, :client_name, :site_name, :site_image, :site_address, :site_contact)
    end
end

class ClientsController < ApplicationController
  before_action :set_client, only: [:show, :edit, :update, :destroy]

  # GET /clients
  # GET /clients.json
  def index
    @clients = Client.all
    @sites = Site.all
  end

  # GET /clients/1
  # GET /clients/1.json
  def show
      @clients = Client.all
      @sites = Site.all
  end

  # GET /clients/new
  def new
    @client = Client.new
  end

  # GET /clients/1/edit
  def edit
  end

  # POST /clients
  # POST /clients.json
  def create
    @client = Client.new(client_params)

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

  # PATCH/PUT /clients/1
  # PATCH/PUT /clients/1.json
  def update
    respond_to do |format|
      if @client.update(client_params)
        format.html { redirect_to @client, notice: 'Client was successfully updated.' }
        format.json { render :show, status: :ok, location: @client }
      else
        format.html { render :edit }
        format.json { render json: @client.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /clients/1
  # DELETE /clients/1.json
  def destroy
    @client.destroy
    respond_to do |format|
      format.html { redirect_to clients_url, notice: 'Client was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_client
      @client = Client.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def client_params
      params.require(:client).permit(:client_name, :client_address, :client_phone, :client_email, :client_website)
    end
end

增加

您可能会注意到我已经尝试扩展,以便在站点中调用客户端,在引用中调用站点和客户端。

首先:我假设您在三个模型之间建立了关系!从报价到客户以及从报价到站点必须存在 has_many 关系。

有两个问题可能会阻止您保存表单。

首先是您如何创建 collection_select。集合中的第三个参数 select 是将发送到控制器的内容。这应该是一组 ID(我假设一个报价可以有多个客户)。我看到你称它为:quote_client。我将其重命名为:client_ids。最后,这就是您要发送给控制器的内容:ID 数组。

你要注意的第二件事是你的控制器。如果您共享您的控制器代码会很好,但我假设您有一个 quotes_controller 和其中的 quote_params 方法。它可能看起来像这样:

    def quote_params
      params.require(:quote).permit(:quote_contact, etc., etc.)
    end

此控制器方法必须响应您的 form_for,因此您的 form_for(如 quote_contact)中的每个字段都应在许可中,否则将无法保存.如果你想保存一个 ID 数组,你必须告诉这个方法你需要一个 ID 数组。你可以这样做:client_ids: []

所以您的新 quote_params 方法应该如下所示:

def quote_params
  params.require(:quote).permit(:quote_contact, client_ids: [], site_ids: [], all_other_fields...)
end

希望这个回答能为您提供急需的帮助。如果我需要澄清更多:请问 :)

干杯

编辑:上面的答案仍然适用于那些想要保存多条记录的人,但是因为你说你只想保存一条记录,所以我更新了答案:

我上面总结的逻辑大致相同。

您目前似乎不了解的是(IMO)对于理解 Rails 应用程序非常重要的是表单映射到控制器和控制器映射到数据库的方式。如上所述,方法 quote_params 应该允许您要保存到数据库的表单中的所有字段。这意味着您的许可部分中的所有字段都应该在您的数据库中,否则将无法保存。如果您仔细查看数据库中的报价 table,您会发现它有 client_id 和 site_id 的字段。这两个字段包含您的 quote/client 和 quote/site 关联的参考。这就是为什么您的许可证目前不起作用,因为您有 quote_client 和 quote_site。数据库没有 quote_client 或 quote_site,因此在尝试保存时不会更新关联。数据库确实有 client_id 和 site_id,所以这就是您应该传递给 quote params 方法的内容。

这当然应该对应于您 form_for 中的字段。所以你需要改变两件事来使这项工作:

  1. 更改您的两个 collection_select,将 :quote_client 换成 :client_id,将 :quote_site 换成 :site_id

  2. 更改控制器方法以反映 form_for 中的更改。这里你还必须将 quote_site 和 quote_client 换成 quote_id 和 site_id,像这样:

    def quote_params params.require(:quote).permit(:client_id, :site_id, etc.) end

使用 Rails MODELNAME_params 方法时要记住的重要事项(我们称之为强参数 -> 请阅读!http://edgeguides.rubyonrails.org/action_controller_overview.html) 是您的表单和您的许可操作都应该像它们在数据库中一样完全列出字段,否则数据库将无法理解并且您的记录将无法正确保存。

我希望通过这次编辑您能弄明白。

干杯