正确处理与虚拟属性对应的多参数属性的方法
我有一个Rails应用程序,其中包含一个包含birthdate
属性的模型。 这对应于使用ActiveRecord date
类型定义的数据库中的列。 有了这个,我可以使用date_select
表单助手方法在我的视图中将其渲染为三选输入。 然后,对应于该字段的表单参数作为birthdate(1i)
, birthdate(2i)
和birthdate(3i)
被序列化回控制器。 因此,我可以在我的模型上的控制器中使用标准的update_attributes
方法来更新模型上的所有字段。
我现在正在尝试使用attr_encrypted
gem加密这个字段。 虽然gem支持编组(这很好),但不再是类型为date
的名称birthdate
的真实列 – 而是attr_encrypted
将值公开为由真正的encrypted_birthdate
列支持的虚拟属性birthdate
。 这意味着update_attributes
无法执行先前的多参数属性分配来填充和保存此列。 相反,我得到一个MultiparameterAssignmentErrors
错误,该错误源于对内部column_for_attribute
方法的调用,该方法返回此列的nil
(来自execute_callstack_for_multiparameter_attributes
某个地方)。
我目前正在解决这个问题如下:
我在app/models/person.rb
:
class Person < ActiveRecord::Base attr_encrypted :birthdate end
我在app/controllers/people_controller.rb
:
class PeopleController < ApplicationController def update # This is the bit I would like to avoid having to do. params[:person] = munge_params(params[:person]) respond_to do |format| if @person.update_attributes(params[:person]) format.html { redirect_to @person, notice: 'Person was successfully updated.' } format.json { head :no_content } else format.html { render action: "edit" } format.json { render json: @person.errors, status: :unprocessable_entity } end end end private def munge_params(params) # This separates the "birthdate" parameters from the other parameters in the request. birthdate_params, munged_params = extract_multiparameter_attribute(params, :birthdate) # Now we place a scalar "birthdate" where the multiparameter attribute used to be. munged_params['birthdate'] = Date.new( birthdate_params[1], birthdate_params[2], birthdate_params[3] ) munged_params end def extract_multiparameter_attribute(params, name) # This is sample code for demonstration purposes only and currently # only handles the integer "i" type. regex = /^#{Regexp.quote(name.to_s)}\((\d+)i)\)$/ attribute_params, other_params = params.segment { |k, v| k =~ regex } attribute_params2 = Hash[attribute_params.collect do |key, value| key =~ regex or raise RuntimeError.new("Invalid key \"#{key}\"") index = Integer($1) [index, Integer(value)] end] [attribute_params2, other_params] end def segment(hash, &discriminator) hash.to_a.partition(&discriminator).map do |a| a.each_with_object(Hash.new) { |e, h| h[e.first] = e.last } end end end
我的观点app/views/people/_form.html.erb
:
处理这种类型的属性的正确方法是什么,而不必像这样引入params
数组的临时替换?
更新:看起来这可能是指相关问题。 这也是。
另一个更新:
这是我目前的解决方案,基于Chris Heald的回答。 此代码应添加到Person
模型类中:
class EncryptedAttributeClassWrapper attr_reader :klass def initialize(klass); @klass = klass; end end # TODO: Modify attr_encrypted to take a :class option in order # to populate this hash. ENCRYPTED_ATTRIBUTE_CLASS_WRAPPERS = { :birthdate => EncryptedAttributeClassWrapper.new(Date) } def column_for_attribute(attribute) attribute_sym = attribute.to_sym if encrypted = self.class.encrypted_attributes[attribute_sym] column_info = ENCRYPTED_ATTRIBUTE_CLASS_WRAPPERS[attribute_sym] column_info ||= super encrypted[:attribute] column_info else super end end
此解决方案按原样工作,但如果attr_encrypted
采用将动态构造ENCRYPTED_ATTRIBUTE_CLASS_WRAPPERS
哈希的:class
选项,则会更好。 我将看看我可以扩展/ monkeypatch attr_encrypted
来做到这一点。 Gist可在此处获取: https : //gist.github.com/rcook/5992293 。
您可以对模型进行column_for_attribute
以通过column_for_attribute
调用。 我没有对此进行测试,但它应该导致birthday
字段上的reflection,而是返回encrypted_birthday
字段的reflection,这应该导致multiparam属性正确分配(因为AR将能够推断字段类型):
def column_for_attribute(attribute) if encrypted = encrypted_attributes[attribute.to_sym] super encrypted[:attribute] else super end end
我们按照这一行修补了column_for_attribute
,以便AR可以推断出该列的正确类型。 它需要弄清楚“birthday”的参数应该是DateTime类型的whatnot,并且无法从虚拟属性推断出。 将reflection映射到实际列应解决该问题。