我正在为Devise注册表单添加一个"帐户计划"选择字段。
我的用户模型称为User
,我的帐户计划模型称为AccountPlan
。由于预计大多数用户实际上不是账户持有人,因此User
通过PaidAccount
连接到AccountPlan
,即User
具有一个AccountPlan
到PaidAccount
。
class User < ActiveRecord::Base
has_one :paid_account
has_one :account_plan, through: :paid_account
end
然后是我表格的相关部分:
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { id: "payment-form" }) do |f| %>
<%= f.collection_select :account_plan, @account_plans, :id, :name_with_price %>
<% end %>
我的问题是,当我提交表单时(在我添加:account_plan
字段之前,表单一直运行良好),我得到了
"2"的未定义方法"id":字符串
它告诉我有问题的行是super
,这当然没有特别的帮助。我发现这句冒犯的台词是这样的,所以它显然不喜欢params的一些东西。
然而,令人困惑的是,如果我在控制台上执行User.new(account_plan: AccountPlan.new)
,它就可以了。所以,如果我的表单吐出这个HTML
<select id="user_account_plan" name="user[account_plan]"><option value="2">Lite ($10.00/mo)</option>
<option value="3">Professional ($20.00/mo)</option>
<option value="4">Plus ($30.00/mo)</option>
</select>
那么我不明白问题应该是什么。
如果有帮助的话,这是我的控制器代码:
class RegistrationsController < Devise::RegistrationsController
def new
@account_plans = AccountPlan.order(:price)
super
end
def create
super
Stripe.api_key = Rails.configuration.stripe_secret_key
Stripe::Customer.create(
card: params[:stripeToken],
description: resource.email
)
end
end
我猜PaidAccount
包含许多付费账户的额外属性,包括账户计划的具体说明,即可用计划的列表。所以你的关系应该看起来像
class PaidAccount < ActiveRecord::Base
belongs_to :account_plan
end
(这意味着PaidAccount
具有属性account_plan_id
)
有两种方法:使用嵌套形式,或者使用更特别的形式。
使用嵌套窗体
我正在使用simple_form
和haml
-->请检查一下,这使代码更容易编写和显示:)
因此,在这种情况下,我会做以下操作:
class User
has_one :paid_account
has_one :account_plan, through: :paid_account
accepts_nested_attributes_for :paid_account
end
因为我们没有创建一个新的account_plan
,所以我们正在链接到一个现有的。
你的表格会变成:
= simple_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { id: "payment-form" }) do |f|
= f.simple_fields_for :paid_account do |pa|
= pa.association :account_plan
这种方法的问题是:什么时候创建paid_account
,如果看不到代码的其余部分,就很难判断。我看到两个选项:
- 您只想在选择帐户计划时创建它
- 或者您只允许用户在已经决定付款时选择帐户计划(并且您已经可以创建
PaidAccount
对象)
我猜这是第一个选项。为了使嵌套表单工作,您必须始终创建嵌套的PaidAccount
,并在未填写帐户计划时保存时将其删除。这实际上很容易做到,因此我们添加了
accepts_nested_attributes_for :paid_account, reject_if: :blank
并且您的控制器new
动作应该变成:
def new
@account_plans = AccountPlan.order(:price)
build_resource({})
resource.build_paid_account
respond_with self.resource
end
还要注意,你必须正确地指定你的强参数,所以你的permits
应该看起来像
def post_params
params.require(:user).permit(:email, :password, :paid_account_attributes => [:account_path_id])
end
更特别的方法
- 像以前一样使用视图
- 允许
post_params
中的account_plan
(如上所述) - 在创建操作中检查
account_plan
的值,并在需要时使用正确的帐户计划构建PaidAccount
所以你的create
动作看起来像
def create
account_plan_id = params[:user].delete(:account_plan)
super
if account_plan_id.present?
resource.build_paid_account(account_plan_id: account_plan_id)
end
Stripe.api_key = Rails.configuration.stripe_secret_key
Stripe::Customer.create(
card: params[:stripeToken],
description: resource.email
)
end
我认为您有两个问题:
-
在collection_select中使用:account_plan_id作为对象的方法
-
你必须"净化"你的params(因为Rails 4中的强参数-阅读更多https://github.com/plataformatec/devise#strong-参数)用于具有设计的附加字段。因此,在您的registrations_controller中添加可接受的参数,如:
def sign_up_paramsparams.require(:user)permit(:email、:password、:password_confirmation、:current_password、:account_plan_id以及注册时所需的其他内容)结束