正确处理与虚拟属性对应的多参数属性的方法

我有一个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映射到实际列应解决该问题。