属性未通过设计和 accepts_nested_attributes_for 保存
Attributes not saving with devise and accepts_nested_attributes_for
我有一个几乎可以使用的 Devise 注册表单。虽然它似乎有效,但它并没有保存我的 Custaddress
table 中的字段,而不是 user_id
。任何有助于解决如何让它保存其余信息的帮助都会很棒。以下被大大截断了!!
User.rb 包含:
class User < ApplicationRecord
has_one :custaddress
accepts_nested_attributes_for :custaddress
end
托管地址包含:
class Custaddress < ApplicationRecord
has_many :orders
belongs_to :user
end
注册控制器包含:
如您所见,这只是建立在“标准”Devise 控制器之上。这里没有创建或新建,因为我假设它使用的是标准方法。
class Users::RegistrationsController < Devise::RegistrationsController
invisible_captcha only: :create
protected
def build_resource(hash = {})
self.resource = resource_class.new_with_session(hash, session)
resource.build_custaddress
# Jumpstart: Skip email confirmation on registration.
# Require confirmation when user changes their email only
resource.skip_confirmation!
# Registering to accept an invitation should display the invitation on sign up
if params[:invite] && (invite = AccountInvitation.find_by(token: params[:invite]))
@account_invitation = invite
# Build and display account fields in registration form if enabled
elsif Jumpstart.config.register_with_account?
account = resource.owned_accounts.first
account ||= resource.owned_accounts.new
account.account_users.new(user: resource, admin: true)
end
end
def update_resource(resource, params)
# Jumpstart: Allow user to edit their profile without password
resource.update_without_password(params)
end
def sign_up(resource_name, resource)
if cookies[:ordernum]
order = Order.where(ordernum: cookies[:ordernum]).first
if order
order.update!(user: resource, custaddress: resource.custaddress)
cookies.delete "ordernum"
end
end
sign_in(resource_name, resource)
# If user registered through an invitation, automatically accept it after signing in
if params[:invite] && (account_invitation = AccountInvitation.find_by(token: params[:invite]))
account_invitation.accept!(current_user)
# Clear redirect to account invitation since it's already been accepted
stored_location_for(:user)
end
end
end
我的 new.html.erb 包含:
<%= form_with(model: resource, as: resource_name, url: registration_path(resource_name, invite: params[:invite])) do |f| %>
<%= f.fields_for :custaddress do |cust| %>
<div class="form-group">
<%= cust.label "Apartment/Unit Number", class: "font-bold" %>
<%= cust.text_field :apartment, class: "form-control", placeholder: "Unit 2 or Apartment 307" %>
</div>
And much more!
我的应用程序控制器有:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
include SetCurrentRequestDetails
include SetLocale
include Jumpstart::Controller
include Accounts::SubscriptionStatus
include Users::NavbarNotifications
include Users::TimeZone
include Pagy::Backend
include CurrentHelper
include Sortable
before_action :configure_permitted_parameters, if: :devise_controller?
before_action :masquerade_user!
before_action :store_user_location!, if: :storable_location?
protected
# To add extra fields to Devise registration, add the attribute names to `extra_keys`
def configure_permitted_parameters
extra_keys = [:avatar, :first_name, :last_name, :time_zone, :preferred_language]
signup_keys = extra_keys + [:terms_of_service, :invite, owned_accounts_attributes: [:name], custaddress_attributes: [:address, :apartment, :city, :state, :country, :postcode, :mobile]]
devise_parameter_sanitizer.permit(:sign_up, keys: signup_keys)
devise_parameter_sanitizer.permit(:account_update, keys: extra_keys)
devise_parameter_sanitizer.permit(:accept_invitation, keys: extra_keys)
end
def after_sign_in_path_for(resource_or_scope)
stored_location_for(resource_or_scope) || super
end
# Helper method for verifying authentication in a before_action, but redirecting to sign up instead of login
def authenticate_user_with_sign_up!
unless user_signed_in?
store_location_for(:user, request.fullpath)
redirect_to new_user_registration_path, alert: t("create_an_account_first")
end
end
def require_current_account_admin
unless current_account_admin?
redirect_to root_path, alert: t("must_be_an_admin")
end
end
private
def storable_location?
request.get? && is_navigational_format? && !devise_controller? && !request.xhr?
end
def store_user_location!
# :user is the scope we are authenticating
store_location_for(:user, request.fullpath)
end
end
我的日志显示:
Processing by Users::RegistrationsController#create as JS
17:08:57 web.1 | Parameters: {"authenticity_token"=>"uVphxW4gCQntvHFxRb33dl9cqxv9vlL69Wc2zOMoF1M+pUk8c2HnHwgQFIkMbfmxYraVI7rYBVCPgfSD1u7OHg==", "user"=>{"first_name"=>"[FILTERED]", "last_name"=>"[FILTERED]", "email"=>"[FILTERED]", "password"=>"[FILTERED]", "time_zone"=>"Sydney", "custaddress_attributes"=>{"apartment"=>"", "address"=>"XXXXXXXX", "city"=>"XXXXXXX", "state"=>"XXXXX", "postcode"=>"XXXX", "mobile"=>"XXXXXXXX", "country"=>"XXXXXXX"}, "terms_of_service"=>"1"}, "enc-rmjxhdab"=>"", "button"=>""}
所以,我知道它正在从表单中获取信息。但是我不知道它在哪里保存 Custaddress
记录。它似乎只关联 user_id
:
Custaddress Create (0.2ms) INSERT INTO "custaddresses" ("user_id", "created_at", "updated_at") VALUES (, , ) RETURNING "id" [["user_id", 3], ["created_at", "2020-09-25 02:20:39.109187"], ["updated_at", "2020-09-25 02:20:39.109187"]]
12:20:39 web.1 | (33.9ms) COMMIT
谁能帮我弄清楚为什么保管地址没有保存。我在这上面花了几个小时,并阅读了 google 上的每篇文章(好吧,感觉确实如此)。
我曾尝试追踪此事,但无济于事。
为完整性添加一个答案。
Devise 的 create
操作调用您正在覆盖的 build_resurce
方法。问题是您为 new
操作覆盖了它,但没有考虑 create
操作。
所以,你正在这样做:
def build_resource(hash = {})
self.resource = resource_class.new_with_session(hash, session)
resource.build_custaddress
对于 new
操作没有问题,resource.build_custaddress
将实例化一个新的 custaddress
对象,以便您能够使用 fields_for
助手。
问题是,对于 create
操作,此时 resource
已经有一个 custaddress
具有来自请求的值(由上一行设置),然后你做 resource.build_custaddress
并用一个新的空 custaddress
替换当前的 custaddress
。
解决方案是仅在 custaddress 不为 nil 时构建它:
def build_resource(hash = {})
self.resource = resource_class.new_with_session(hash, session)
resource.build_custaddress if resource.custaddress.nil?
这样,您将为 new
操作获得一个新的空保管地址,但要尊重 create
、edit
或 update
请求中的值动作。
我有一个几乎可以使用的 Devise 注册表单。虽然它似乎有效,但它并没有保存我的 Custaddress
table 中的字段,而不是 user_id
。任何有助于解决如何让它保存其余信息的帮助都会很棒。以下被大大截断了!!
User.rb 包含:
class User < ApplicationRecord
has_one :custaddress
accepts_nested_attributes_for :custaddress
end
托管地址包含:
class Custaddress < ApplicationRecord
has_many :orders
belongs_to :user
end
注册控制器包含:
如您所见,这只是建立在“标准”Devise 控制器之上。这里没有创建或新建,因为我假设它使用的是标准方法。
class Users::RegistrationsController < Devise::RegistrationsController
invisible_captcha only: :create
protected
def build_resource(hash = {})
self.resource = resource_class.new_with_session(hash, session)
resource.build_custaddress
# Jumpstart: Skip email confirmation on registration.
# Require confirmation when user changes their email only
resource.skip_confirmation!
# Registering to accept an invitation should display the invitation on sign up
if params[:invite] && (invite = AccountInvitation.find_by(token: params[:invite]))
@account_invitation = invite
# Build and display account fields in registration form if enabled
elsif Jumpstart.config.register_with_account?
account = resource.owned_accounts.first
account ||= resource.owned_accounts.new
account.account_users.new(user: resource, admin: true)
end
end
def update_resource(resource, params)
# Jumpstart: Allow user to edit their profile without password
resource.update_without_password(params)
end
def sign_up(resource_name, resource)
if cookies[:ordernum]
order = Order.where(ordernum: cookies[:ordernum]).first
if order
order.update!(user: resource, custaddress: resource.custaddress)
cookies.delete "ordernum"
end
end
sign_in(resource_name, resource)
# If user registered through an invitation, automatically accept it after signing in
if params[:invite] && (account_invitation = AccountInvitation.find_by(token: params[:invite]))
account_invitation.accept!(current_user)
# Clear redirect to account invitation since it's already been accepted
stored_location_for(:user)
end
end
end
我的 new.html.erb 包含:
<%= form_with(model: resource, as: resource_name, url: registration_path(resource_name, invite: params[:invite])) do |f| %>
<%= f.fields_for :custaddress do |cust| %>
<div class="form-group">
<%= cust.label "Apartment/Unit Number", class: "font-bold" %>
<%= cust.text_field :apartment, class: "form-control", placeholder: "Unit 2 or Apartment 307" %>
</div>
And much more!
我的应用程序控制器有:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
include SetCurrentRequestDetails
include SetLocale
include Jumpstart::Controller
include Accounts::SubscriptionStatus
include Users::NavbarNotifications
include Users::TimeZone
include Pagy::Backend
include CurrentHelper
include Sortable
before_action :configure_permitted_parameters, if: :devise_controller?
before_action :masquerade_user!
before_action :store_user_location!, if: :storable_location?
protected
# To add extra fields to Devise registration, add the attribute names to `extra_keys`
def configure_permitted_parameters
extra_keys = [:avatar, :first_name, :last_name, :time_zone, :preferred_language]
signup_keys = extra_keys + [:terms_of_service, :invite, owned_accounts_attributes: [:name], custaddress_attributes: [:address, :apartment, :city, :state, :country, :postcode, :mobile]]
devise_parameter_sanitizer.permit(:sign_up, keys: signup_keys)
devise_parameter_sanitizer.permit(:account_update, keys: extra_keys)
devise_parameter_sanitizer.permit(:accept_invitation, keys: extra_keys)
end
def after_sign_in_path_for(resource_or_scope)
stored_location_for(resource_or_scope) || super
end
# Helper method for verifying authentication in a before_action, but redirecting to sign up instead of login
def authenticate_user_with_sign_up!
unless user_signed_in?
store_location_for(:user, request.fullpath)
redirect_to new_user_registration_path, alert: t("create_an_account_first")
end
end
def require_current_account_admin
unless current_account_admin?
redirect_to root_path, alert: t("must_be_an_admin")
end
end
private
def storable_location?
request.get? && is_navigational_format? && !devise_controller? && !request.xhr?
end
def store_user_location!
# :user is the scope we are authenticating
store_location_for(:user, request.fullpath)
end
end
我的日志显示:
Processing by Users::RegistrationsController#create as JS
17:08:57 web.1 | Parameters: {"authenticity_token"=>"uVphxW4gCQntvHFxRb33dl9cqxv9vlL69Wc2zOMoF1M+pUk8c2HnHwgQFIkMbfmxYraVI7rYBVCPgfSD1u7OHg==", "user"=>{"first_name"=>"[FILTERED]", "last_name"=>"[FILTERED]", "email"=>"[FILTERED]", "password"=>"[FILTERED]", "time_zone"=>"Sydney", "custaddress_attributes"=>{"apartment"=>"", "address"=>"XXXXXXXX", "city"=>"XXXXXXX", "state"=>"XXXXX", "postcode"=>"XXXX", "mobile"=>"XXXXXXXX", "country"=>"XXXXXXX"}, "terms_of_service"=>"1"}, "enc-rmjxhdab"=>"", "button"=>""}
所以,我知道它正在从表单中获取信息。但是我不知道它在哪里保存 Custaddress
记录。它似乎只关联 user_id
:
Custaddress Create (0.2ms) INSERT INTO "custaddresses" ("user_id", "created_at", "updated_at") VALUES (, , ) RETURNING "id" [["user_id", 3], ["created_at", "2020-09-25 02:20:39.109187"], ["updated_at", "2020-09-25 02:20:39.109187"]]
12:20:39 web.1 | (33.9ms) COMMIT
谁能帮我弄清楚为什么保管地址没有保存。我在这上面花了几个小时,并阅读了 google 上的每篇文章(好吧,感觉确实如此)。
我曾尝试追踪此事,但无济于事。
为完整性添加一个答案。
Devise 的 create
操作调用您正在覆盖的 build_resurce
方法。问题是您为 new
操作覆盖了它,但没有考虑 create
操作。
所以,你正在这样做:
def build_resource(hash = {})
self.resource = resource_class.new_with_session(hash, session)
resource.build_custaddress
对于 new
操作没有问题,resource.build_custaddress
将实例化一个新的 custaddress
对象,以便您能够使用 fields_for
助手。
问题是,对于 create
操作,此时 resource
已经有一个 custaddress
具有来自请求的值(由上一行设置),然后你做 resource.build_custaddress
并用一个新的空 custaddress
替换当前的 custaddress
。
解决方案是仅在 custaddress 不为 nil 时构建它:
def build_resource(hash = {})
self.resource = resource_class.new_with_session(hash, session)
resource.build_custaddress if resource.custaddress.nil?
这样,您将为 new
操作获得一个新的空保管地址,但要尊重 create
、edit
或 update
请求中的值动作。