【问题标题】:Rails - Insert on a has_many through tableRails - 通过表插入 has_many
【发布时间】:2018-08-18 19:14:15
【问题描述】:

我有两个类,用户和机会,它们利用带有 has_many :through 关系的连接表来允许用户注册一对多机会(并允许一个机会让多个用户注册)。

class User < ApplicationRecord
  has_many :opportunity_enrolments, :class_name => 'OpportunityEnrolment', foreign_key: "user_id"
  has_many :opportunities, through: :opportunity_enrolments, foreign_key: "opportunity_id"
  has_secure_password
  validates :email, presence: true, uniqueness: true
end
class Opportunity < ApplicationRecord
  has_many :opportunity_enrolments, :class_name => 'OpportunityEnrolment'
  has_many :users, through: :opportunity_enrolments
end
class OpportunityEnrolment < ApplicationRecord
  belongs_to :opportunity
  belongs_to :user
end

仅当用户和机会都存在时,用户才会注册机会。因此,注册功能将在用户和机会的创建之外进行,它们分别发生并且工作正常。我认为我可以在查看 Opportunity 时使用 OpportunityEnrolment.create 方法使用以下表单和控制器向 opportunity_enrolments 表添加新行。

机会/show.html.erb

<%= form_with(model: OpportunityEnrolment.create, local: true) do |form| %>
<p>
  <strong>Name:</strong>
  <%= @opportunity.voppname %>
</p>
<p>
  <strong>Shortdescr:</strong>
  <%= @opportunity.shortdescr %>
</p>
<p>
  <%= form.submit %>
</p>
<% end %>

opportunity_enrolments_controller.rb

def create      
   @opportunity_enrolment = OpportunityEnrolment.new(user_id: current_user.id, opportunity_id: @opportunity.id)

  error checking and .save...
end

但是,我的表单中的 @opportunity.id 没有被传递给 OpportunityEnrolment.create 方法,所以我在提交时收到“nil:NilClass 的未定义方法‘id’”错误。我尝试了一种不同的方式,在表单中有一个隐藏字段(理解它不安全)并且对象仍然没有通过,但是我克服了未定义的方法错误。

我如何将对象信息 (Opportunity) 传递给另一个类 (OpportunityEnrolments),以便我可以在 opportunity_enrolments 表中添加一行?

谢谢

【问题讨论】:

    标签: ruby-on-rails has-many-through


    【解决方案1】:

    这应该使用嵌套路由来完成,而不是传递机会 表格中的id。

    # config/routes.rb
    Rails.application.routes.draw do
    
      # create the route: GET /opportunities/:id
      # helper: opportunity_path(opportunity || opportunity_id)
      # handled by: OpportunitiesController#show
      resources :opportunities, only: :show do
    
        # create the route: POST /opportunities/:opportunity_id/opportunity_enrolments
        # helper: opportunity_opportunity_enrolments_path(opportunity || opportunity_id)
        # handled by: OpportunityEnrolmentsController#create
        resources :opportunity_enrolments, only: :create
      end
    
    end
    

    # app/controllers/opportunity_enrolments_controller.rb
    class OpportunityEnrolmentsController < ApplicationController
      # opportunity should be set for every nested action, create in this scenario
      before_action :set_opportunity, only: :create
    
      def create
        # initialize a new opportunity enrolment with opportunity id set to
        # the id of the current opportunity
        @opportunity_enrolment = @opportunity.opportunity_enrolments.build
        # set the user id equal to the current user
        @opportunity_enrolment.user = current_user
    
        # assign the passed attributes by the form and try to save the record
        # if your form doesn't contain any attributes, call #save instead
        if @opportunity_enrolment.update(opportunity_enrolment_params)
          redirect_to @opportunity
        else
          # display errors using @opportunity_enrolment.errors in the form, see note
          render 'opportunities/show' 
        end
      end
    
      private
    
      def opportunity_enrolment_params
        # if you only need to set the attributes opportunity_id and user_id
        # you can leave this call out and call #save instead of #update
        # ...
      end
    
      def set_opportunity
        # params[:opportunity_id] is retrieved from the current path, it is not
        # a query or request body param
        @opportunity = Opportunity.find(params[:opportunity_id])
      end
    end
    

    <% # app/views/opportunities/show.html.erb %>
    
    <% # If rendered from opportunity show: opportunity enrolment is not set thus a new     %>
    <% # opportunity enrolment will be initialized. If rendered from the opportunity        %>
    <% # enrolment create action: opportunity enrolment will already be present with errors %>
    <% # set, no new opportunity will be initialized.                                       %>
    <% @opportunity_enrolment ||= @opportunity.opportunity_enrolments.build %>
    
    <% # Passing an array containing an opportunity and an opportunity enrolment will build  %>
    <% # the path in 3 steps. 1) Is opportunity a new record? Use /opportunities, if not use %>
    <% # /opportunities/:id. 2) Is opportunity enrolment a new record? Use                   %>
    <% # /opportunity_enrolments, if not use /opportunity_enrolments/:id. 3) Is the last     %>
    <% # element in the array a new record? Use POST, if not use PUT.                        %>
    <% # Combining the above together you get the path:                                      %>
    <% # POST /opportunities/:opportunity_id/opportunity_enrolments                          %>
    <% # Handled by OpportunityEnrolmentsController#create (see routes).                     %>
    <%= form_with model: [@opportunity, @opportunity_enrolment], local: true do |form| %>
      <p><strong>Name:</strong><%= @opportunity.voppname %></p>
      <p><strong>Shortdescr:</strong><%= @opportunity.shortdescr %></p>
      <p><%= form.submit %></p>
    <% end %>
    

    注意:如果您要阅读nested resources,请不要跳过 关于浅嵌套的部分。它使您的路线和应用程序保持清洁。 错误访问页面可以找到here。

    【讨论】:

    • 太棒了,我会试试这个!
    • 我快要让它工作了,但是在opportunity_enrolment_params 私有方法中出现错误(参数丢失或值为空:opportunity_enrolment)。我在私有方法中的代码是 params.require(:opportunity_enrolment).permit(:user_id, :opportunity_id)。目前正在调试,但我猜可能 user_id 参数为空,所以这就是我收到错误的原因?
    • 将 params.require 更改为 params.permit,这似乎已经解决了这个问题。现在我正在尝试解决 RecordNotUnique 错误,但是从错误中吐出的参数来看,看起来一切都正确传递了。
    • 我会添加一些解释性的 cmets。
    • @NathanM 如果您只需要设置 opportunity_id 和 user_id 属性,则可以省略 @opportunity_enrolment.update(opportunity_enrolment_params),而只需调用 @opportunity_enrolment.save。
    【解决方案2】:

    如果您使用隐藏字段,则需要在机会_enrolments_controller 的创建操作中实例化机会。

    def create
      @opportunity = Opportunity.find(params[:opportunity_id])
      @opportunity_enrolment = OpportunityEnrolment.new(user_id: current_user.id, opportunity_id: @opportunity.id)
    end
    

    形式:

    <%= form_with(model: OpportunityEnrolment.create, local: true) do |form| %>
    <p>
      <strong>Name:</strong>
      <%= @opportunity.voppname %>
    </p>
    <p>
      <strong>Shortdescr:</strong>
      <%= @opportunity.shortdescr %>
      <%= form.hidden_field :opportunity_id, value: @opportunity.id %>
    </p>
    <p>
      <%= form.submit %>
    </p>
    <% end %>
    

    【讨论】:

    • 感谢您的评论。接近解决方案。 Rails 不喜欢@opportunity = Opportunity.find(params[:opportunity_id])。即使参数似乎正在传递,也获得“无法找到没有 ID 的机会” - 参数:{"utf8"=>"✓", "authenticity_token"=>"8QB1soyRpqGPeoy98vjRXcVNtFaJRjnZT7oNSmHnPd2TJxAWS9hT6PYqYuEAHToDDiAtH1cE6sSGbLVNyJteVg==","opportunity"机会_id"=>"2"}, "commit"=>"注册"}
    • @NathanM,你需要做 params[:opportunity_enrolment][:opportunity_id]
    • 感谢 cmets,改用嵌套资源。
    猜你喜欢
    • 2016-03-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-19
    • 1970-01-01
    • 1970-01-01
    • 2013-11-18
    相关资源
    最近更新 更多