【发布时间】:2013-07-05 06:33:55
【问题描述】:
我有一个带有 birthdate 属性的模型的 Rails 应用程序。这对应于我的数据库中使用 ActiveRecord date 类型定义的列。有了这个,我可以使用date_select 表单辅助方法在我的视图中将其呈现为三选输入。然后将与该字段对应的表单参数序列化回控制器为birthdate(1i)、birthdate(2i) 和birthdate(3i)。因此,我可以在我的模型上的控制器中使用标准 update_attributes 方法来更新模型上的所有字段。
我现在正在尝试使用 attr_encrypted gem 加密这个字段。虽然 gem 支持编组(这很好),但不再有名称为 birthdate 类型为 date 的真实列 - 相反,attr_encrypted 将值公开为 virtual 属性 @987654336 @ 由真实的 encrypted_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:
<%= 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 哈希值会更好。我将研究如何扩展/monkeypatch attr_encrypted 来做到这一点。要点在这里:https://gist.github.com/rcook/5992293。
【问题讨论】:
标签: ruby-on-rails ruby