使用强参数和设计更新 OpenStruct 字段

Updating an OpenStruct field with strong parameters and Devise

正在尝试添加 :preferences 以设计强参数

preferences: [:gender, :location, :website, :aim, :interests]) 

产生错误

Attribute was supposed to be a OpenStruct, but was a ActionController::Parameters. -- {"gender"=>"No", "location"=>"", "website"=>"", "aim"=>"", "interests"=>""}

将 :preferences 添加为

:preferences

没有抛出错误,但是 none 的键值对在提交表单时被更新。 :preferences 以外的字段,如 :email 和 :password,已正确更新。

我已经完成了谷歌搜索,但无法找出在强参数消毒器中指示嵌套 OpenStruct 的格式。

记录器输出确认 :preferences 是不允许的。

controllers/application_controller

class ApplicationController < ActionController::Base
  before_action :configure_permitted_parameters, if: :devise_controller?

  [snip]

  def configure_permitted_parameters
    devise_parameter_sanitizer.for(:sign_up) { 
        [snip]
    }
    devise_parameter_sanitizer.for(:account_update) { 
        |u| u.permit(:name, :email,
                     :password, :password_confirmation, :current_password,
                     preferences: [:gender, :location, :website, :aim, :interests]) 
    }
  end

models/user

class User < ActiveRecord::Base
  serialize :preferences, OpenStruct
  after_create :default_preferences

  def default_preferences
    self.preferences.gender = ''
    self.preferences.location = ''
    self.preferences.website = ''
    [snip etc.]
  end

views/devise/registrations/_form

<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
  <%= devise_error_messages! %>

  <!-- Control panel -->
  <% unless params[:page] %>

    [snip default control panel page]

  <% end %>

  <% case params[:page] %>
  <% when "password" %>

    [snip fields for password change]

  <% when "email" %>
    [snip fields for email change]

  <% when "profile" %>
    <%= f.fields_for :preferences, OpenStruct.new(f.object.preferences) do |p| %>

      <div class="field">
        <%= p.label :gender %>
        <div class="input">
          <%= p.text_field :gender %>
        </div>
      </div>

      [snip etc. preferences fields, e.g. :location, :website, :aim]
    <% end %>
  <% end %>

  <!-- Submit -->
  <div class="field">
    <div class="input">
      <%= f.submit "Update profile", class: "update-button" %>
    </div>
  </div>
</div>
<% end %>

您应该能够覆盖首选项分配以按预期类型进行转换:

class User < ActiveRecord::Base
  …
  def preferences=(val)
    return super if val.is_a? OpenStruct
    super OpenStruct.new val.to_h if val
  end
end

如果值已经是 OpenStruct,此代码将调用原始设置器方法,否则将尝试将该值转换为散列,然后再将该散列转换为 OpenStruct。请注意,如果无法将值转换为散列值,这将引发错误。

我认为这是最好的解决方案,因为将 serialize 类型指定为 OpenStruct 意味着分配的值应该 alwaysOpenStruct,此代码将确保。