4

我有一个带有一个包含birthdate属性的模型的 Rails 应用程序。这对应于我的数据库中使用 ActiveRecorddate类型定义的列。有了这个,我可以使用date_select表单助手方法在我的视图中将其呈现为三选输入。然后将与该字段对应的表单参数序列化回控制器,如birthdate(1i),birthdate(2i)birthdate(3i)。因此,我可以update_attributes在模型上的控制器中使用标准方法来更新模型上的所有字段。

我现在正在尝试使用attr_encryptedgem 加密这个字段。虽然 gem 支持编组(这很好),但不再有birthdate类型名称的真实列date- 相反,attr_encrypted将值公开为由真实列支持的虚拟属性。这意味着无法执行之前的多参数属性分配来填充和保存此列。相反,我得到一个错误,这是由于调用返回此列的内部方法(从内部的某处)。birthdateencrypted_birthdateupdate_attributesMultiparameterAssignmentErrorscolumn_for_attributenilexecute_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

<%= form_for @person do |f| %>
    <%= f.label :birthdate %>
    <%= f.date_select :birthdate %>

    <% f.submit %>
<% end %>

处理这种类型的属性的正确方法是什么,而不必像这样引入数组的临时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该解决方案按原样工作,但如果采用动态构造散列的:class选项会更好。ENCRYPTED_ATTRIBUTE_CLASS_WRAPPERS我将研究如何扩展/monkeypatchattr_encrypted来做到这一点。此处提供要点:https ://gist.github.com/rcook/5992293 。

4

1 回答 1

1

您可以对模型进行猴子补丁以通过column_for_attribute调用。我没有对此进行测试,但它应该会导致birthday字段上的反射,而不是返回字段的反射encrypted_birthday,这应该会导致多参数属性正确分配(因为 AR 将能够推断字段类型):

def column_for_attribute(attribute)
  if encrypted = encrypted_attributes[attribute.to_sym]
    super encrypted[:attribute]
  else
    super
  end
end

我们正在column_for_attribute根据这一行进行修补,以便 AR 可以推断出该列的正确类型。它需要弄清楚“生日”的参数应该是什么类型的 DateTime 类型,并且不能从虚拟属性中推断出来。将反射映射到实际列应该可以解决这个问题。

于 2013-07-09T17:07:59.977 回答