【问题标题】:RoR How to access multiple model params posted through the same formRoR 如何访问通过同一表单发布的多个模型参数
【发布时间】:2017-05-22 20:48:59
【问题描述】:

我正在使用 Ruby on Rails 构建一个简单的表单来提交订单。

我的表单需要提交来自 3 个不同模型的信息:用户、catalog_item 和订单本身。

这是我的订单模型:

class Order < ApplicationRecord
    after_initialize :default_values
    validates :quantity, presence: true

    belongs_to :user
    belongs_to :catalog_item
    validates :user_id, presence: true
    validates :catalog_item_id, presence: true

    accepts_nested_attributes_for :user
    validates_associated :user
end

这是我的用户模型:

class User < ApplicationRecord
    has_many :orders
end

这是我的控制器:

class OrdersController < ApplicationController

def checkout
    @order = Order.new(catalog_item: CatalogItem.find(params[:catalog_item_id]), user: User.new)
end

def create
    @order = Order.new(order_params)
    if @order.save
        # redirect_to confirmation_path
    else
        # redirect_to error_path
    end
end

private
    def user_params
    [:name, :email, :phone_number]
  end

    def order_params
        params.require(:order).permit(:id, :catalog_item_id, user_attributes: user_params)
    end
end

这是我的视图形式:

<%= form_for @order do |order_form| %>
    <%= order_form.hidden_field :catalog_item_id %>
    <%= order_form.fields_for :user do |user_fields| %>
        <%= user_fields.label :name %>
    <%= user_fields.text_field :name %>
    <%= user_fields.label :email %>
    <%= user_fields.text_field :email %>
    <%= user_fields.label :phone_number %>
    <%= user_fields.text_field :phone_number %>
    <% end %>
    <%= order_form.submit %>
<% end %>

如果表单是 HTML:

<form class="new_order" id="new_order" action="/orders" accept-charset="UTF-8" method="post"><input name="utf8" type="hidden" value="✓"><input type="hidden" name="authenticity_token" value="+z8JfieTzJrNgsr99C4jwBtXqIrpNtiEGPdVi73qJrpiGPpjYzbLwUng+e+yp8nIS/TLODWVFQtZqS/45SUoJQ==">
    <input type="hidden" value="1" name="order[catalog_item_id]" id="order_catalog_item_id">
    <label for="order_user_attributes_name">Name</label>
    <input type="text" name="order[user_attributes][name]" id="order_user_attributes_name">
    <label for="order_user_attributes_email">Email</label>
    <input type="text" name="order[user_attributes][email]" id="order_user_attributes_email">
    <label for="order_user_attributes_phone_number">Phone number</label>
    <input type="text" name="order[user_attributes][phone_number]" id="order_user_attributes_phone_number">
    <input type="submit" name="commit" value="Create Order" data-disable-with="Create Order">

这是我的路线:

get 'checkout/:catalog_item_id', to: 'orders#checkout', as: 'checkout'
post 'orders', to: 'orders#create'

当我尝试将 @order 保存在操作 create 中时,我收到此错误:

#<ActiveModel::Errors:0x007fe95d58b698 @base=#<Order id: nil, quantity: 1, created_at: nil, updated_at: nil, user_id: nil, catalog_item_id: 1>, @messages={:user_id=>["can't be blank"]}, @details={:user_id=>[{:error=>:blank}]}>

但是,如果我这样做,它确实有效:

@catalog_item = CatalogItem.find(order_params[:catalog_item_id])
@user = User.new(order_params[:user_attributes])
@user.save
@order = Order.new(catalog_item: @catalog_item, user: @user)

这是我发布表单时在 HTTP 请求中发送的内容:

order[catalog_item_id]:1
order[user_attributes][name]:Ana
order[user_attributes][email]:ana@gmail.com
order[user_attributes][phone_number]:123123123
commit:Create Order

我是 RoR 的新手,我不明白为什么 order_params 没有用户,但它确实有 catalog_item_id。

任何帮助将不胜感激。谢谢!

【问题讨论】:

  • 你发表了什么观点?记录 params 哈希,您将看到表单实际传递的内容。表单未收集到的方法所需的任何数据,您可以在隐藏字段中传递。
  • @margo 当我记录参数时,这就是我得到的{"utf8"=&gt;"✓", "authenticity_token"=&gt;"9tXNW6m8fDha53/HGL1i+yTzDEDDsHbcx87KpqugI0dv8j5G7Rl7Y96FTNVeNIjzdFBv8h8Tu1OGkLDV828t2A==", "user"=&gt;&lt;ActionController::Parameters {"name"=&gt;"John", "email"=&gt;"john@example.com", "phone_number"=&gt;"123123123"} permitted: false&gt;, "commit"=&gt;"Create Order", "controller"=&gt;"orders", "action"=&gt;"create"} permitted: false&gt; 我从结帐视图get 'checkout/:catalog_item_id', to: 'orders#checkout', as: 'checkout' 发布了表单。希望这是有道理的。
  • 我对实际情况仍然有些困惑 - 一个订单只能有一件商品吗?
  • @max 是的,没错。这个想法是该应用程序将允许人们订购研讨会(catalog_items),但这是一个简单的工作流程,用户进入网站,选择研讨会并结帐。所以每个订单只有一个 catalog_item。也不需要登录。
  • 是的,然后肯定将其设为嵌套路由。

标签: ruby-on-rails ruby forms ruby-on-rails-5


【解决方案1】:

假设你的Order模型belongs_to :user,我的“suggested-rails-best-practice”解决方案如下:

请参阅Rails Nested Attributes 了解更多信息。基本上,嵌套属性所做的是它允许您在一个命令中“创建”一个关联记录(在您的示例中,关联用户):

# example code:
Order.create(
  catalog_item_id: 1,
  user_attributes: {
    name: 'Foo',
    email: 'foo@bar.com'
  }
)

# above will create two records (i.e.):
# 1) <Order id: 1 catalog_item_id: 1>
# 2) <User id: 1, order_id: 1, name: 'Foo', email: 'foo@bar.com'>

现在您还可以在创建订单时将user_attributes 作为哈希的一部分传入,将user_attributes 也视为请求参数的一部分就很简单了,请参阅下面的controller

型号:

# app/models/order.rb
belongs_to :user

accepts_nested_attributes_for :user

# from our discussion, the validation needs to be updated into:
validates :user, presence: true
validates :category_item, presence: true

控制器:

# app/controllers/orders_controller.rb

def create
  @order = Order.new(order_params)

  if @order.save
    # DO SOMETHING WHEN SAVED SUCCESSFULLY
  else
    # DO SOMETHING WHEN SAVING FAILED (i.e. when validation errors)
    render :checkout
  end
end

private

# "Rails Strong Params" see for more info: http://api.rubyonrails.org/classes/ActionController/StrongParameters.html
def order_params
  params.require(:order).permit(:id, :catalog_item_id, user_attributes: [:name, :email, :phone_number])
end

查看;

<%= form_for @order do |order_form| %>
  <!-- YOU NEED TO PASS IN catalog_item_id as a hidden field so that when the form is submitted the :catalog_item_id having the value pre-set on your `checkout` action, will be also submitted as part of the request -->
  <%= order_form.hidden_field :catalog_item_id %>

  <%= order_form.fields_for :user do |user_form| %>
    <%= user_form.label :name %>
    <%= user_form.text_field :name %>
    <%= user_form.label :email %>
    <%= user_form.text_field :email %>
    <%= user_form.label :phone_number %>
    <%= user_form.text_field :phone_number %>
    <% end %>
  <%= order_form.submit %>
<% end %>

【讨论】:

  • 您好,杰伊,感谢您的帮助!这对我来说很有意义。 :-) 我已按照建议在我的应用程序中进行了更改,但是当我尝试保存订单时,由于#&lt;Enumerator: ["User must exist", "User can't be blank"]:each&gt; 它会回滚。我检查了由于某种原因缺少用户的 order_params &lt;ActionController::Parameters {"catalog_item_id"=&gt;"1"} permitted: true&gt;。在我的订单模型中,我有belongs_toaccepts_nested_attributes_forvalidates_associated 用于:user。我也有validates :user_id, presence: true。这会引起问题吗?塔!
  • @Ana hi Ana,你也实现了我上面的“Strong-Params”吗? params.require(:order).permit(:id, :catalog_item_id, user_attributes: [:name, :email, :phone_number]) ?另外,你有没有把你的改成上面的&lt;%= order_form.fields_for :user do |user_form| %&gt;?这也很重要,因为这一行嵌套了来自order --to--> user 的字段。
  • 您当前的代码是&lt;%= fields_for :user, @order.user do |user_fields| %&gt;,它将生成像user: {name: 'Foo', email: 'foo@bar.com'} 这样的请求参数。但是,您不希望这样。尝试将其更改为&lt;%= order_form.fields_for :user do |user_form| %&gt;。你会注意到请求参数变成了order: { user_attributes: { name: 'Foo', email: 'foo@bar.com' }}。看到不同?另一个是嵌套的,而另一个不是。如果您已经阅读了我上面的答案,您会立即意识到为什么当参数被传递到 Order.create 时这个嵌套的会起作用
  • @Ana 如果这仍然不起作用,您能否更新您的问题,并在页面上添加表单的 HTML?如果您不知道如何执行此操作并且正在使用 Chrome,请参阅 this。此外,您还可以在问题中添加有关请求的详细信息吗? See this 基本上,我想看到的是请求参数。当您已经打开 Chrome 开发工具后,提交您的表单,然后复制请求参数。
  • 嗨,Jay,我还没有机会这样做,但我会在今天晚些时候这样做并发布结果。非常感谢您的帮助!
【解决方案2】:

User 是一个类,因此fields_for :user 为新的用户对象创建字段。

尝试调用 order_form.fields_for 而不是 fields_for 以将 fields_for 范围限定为您的订单对象。

【讨论】:

    【解决方案3】:

    如果您希望用户能够从商品的显示视图创建订单,您可以改为设置嵌套路由:

    resources :catalog_items do 
      resources :orders, only: [:create]
    end
    

    确保你有

    class Order < ApplicationRecord
      belongs_to :user
      belongs_to :catalog_item_id
      accepts_nested_attributes_for :user
      validates_associated :user # triggers user validations
    end
    
    class CatalogItem
      has_many :orders
    end
    

    那么你可以这样做:

    # /app/views/orders/_form.html.erb
    <%= form_for [@catalog_item, @order || @catalog_item.orders.new] do |order_form| %>
      <%= order_form.fields_for :user do |user_fields| %>
        <%= user_fields.label :name %>
        <%= user_fields.text_field :name %>
        <%= user_fields.label :email %>
        <%= user_fields.text_field :email %>
        <%= user_fields.label :phone_number %>
        <%= user_fields.text_field :phone_number %>
      <% end %>
      <%= order_form.submit %>
    <% end %>
    
    # /app/views/catalog_items/show 
    <%= render partial: 'orders/form' %>
    

    这会将表单网址设置为/catalog_items/:catalog_item_id/orders。这意味着我们通过 URL 而不是表单参数传递 catalog_item_id - - 这是一种更好的做法,因为它使路由具有描述性和 RESTful。

    然后设置控制器:

    class OrderController
      # POST /catalog_items/:catalog_item_id/orders
      def create
        @catalog_item = CatalogItem.find(params[:catalog_item_id])
        @order = @catalog_item.orders.new(order_params)
        # Uncomment the next line if you have some sort of authentication like Devise 
        # @order.user = current_user if user_signed_in?
        if @order.save
          redirect_to @catalog_item, success: 'Thank you for your order'
        else
          render 'catalog_items/show' # render show view with errors.
        end
      end
    
      # ...
    
      private
    
      def user_params
        [:name, :email, :phone_number]
      end
    
      def order_params
        params.require(:order)
              .permit(:id, user_attributes: user_params)
      end
    end
    

    【讨论】:

    • 你犯了另一个非常常见的菜鸟错误 - 始终使用 if 声明调用 saveupdate 并响应成功和失败。
    • 你也应该用resources :orders设置路由
    • 谢谢@max 我要试试这个。我希望隐藏 catalog_item_id 字段。
    • 已编辑。嵌套路由可能是您真正想要的。
    • 我想我越来越近了。我将 catalog_id 作为隐藏字段发送,但我会将其更改为您稍后打算使用的嵌套路由。我设法在参数&lt;ActionController::Parameters {"catalog_item_id"=&gt;"1", "user_attributes"=&gt;&lt;ActionController::Parameters {"name"=&gt;"Ana Henneberke", "email"=&gt;"ana.henneberke4@gmail.com", "phone_number"=&gt;"07501782993"} permitted: true&gt;} permitted: true&gt; 中得到了我需要的东西,但是当我尝试保存订单@order = Order.new(order_params) @order.save 时,我收到了这个错误#&lt;Enumerator: ["User can't be blank"]:each&gt;
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-10-03
    • 2013-06-12
    • 1970-01-01
    • 2014-08-18
    • 2019-07-23
    • 1970-01-01
    • 2021-12-05
    相关资源
    最近更新 更多