我使用attr_encrypted加密了一个属性,我正在使用as_json
。在某些情况下,我不希望ssn成为API响应的一部分,有时我希望它包含但使用名称ssn
而不是encrypted_ssn
并显示解密的值。在我的所有情况下,encrypted_ssn
的结果中都不应包含as_json
。
我的第一个问题是,如何让as_json
返回解密的ssn
字段?
使用此代码
class Person
attr_encrypted :ssn, key: 'key whatever'
end
我想要这个
Person.first.as_json
=> {"id"=>1,
"ssn"=>"333-22-4444"}
我不想要的是:
Person.include_ssn.first.as_json
=> {"id"=>1,
"encrypted_ssn"=>"mS+mwRIsMI5Y6AzAcNoOwQ==\n"}
我的第二个问题是,如何使用模型的控制器可以选择在JSON中包含解密的ssn("ssn"=>"333-22-4444"
)或排除字段(no "encrypted_ssn"=>"mS+mwRIsMI5Y6AzAcNoOwQ==\n"
)?如果控制器没有明确指定包含它,我甚至不希望加密值传递给客户端。
这是我到目前为止所做的工作:
class Person
attr_encrypted :ssn, key: 'key whatever'
scope :without_ssn, -> { select( column_names - [ 'encrypted_ssn' ]) }
default_scope { without_ssn }
end
Person.first.as_json
=> {"id"=>1}
我还没有弄清楚如何以第一个问题中包含解密的ssn
字段的方式进行此工作。我想要的是这样的:
Person.include_ssn.first.as_json
=> {"id"=>1,
"ssn"=>"333-22-4444"}
我的最后一个问题是,如何通过连接完成上述工作?如何指定在连接中包含或排除加密值(或范围)?
使用此代码:
class Person
has_many :companies
attr_encrypted :ssn, key: 'key whatever'
scope :without_ssn, -> { select( column_names - [ 'encrypted_ssn' ]) }
default_scope { without_ssn }
end
class Company
belongs_to :person
end
这似乎像我想要的那样工作
Company.where(... stuff ...).joins(:person).as_json(include: [ :person ])
=> {"id"=>1,
"person"=>
{"id"=>1}}
但我不知道如何实现include_ssn
如下所示,或者告诉人员模型包含ssn解密的替代方案。
Company.where(... stuff ...).joins(:person).include_ssn.as_json(include: [ :person ])
=> {"id"=>1,
"person"=>
{"id"=>1,
"ssn"=>"333-22-4444"}}
答案 0 :(得分:0)
我以不同的方式解决了这个问题。最初我是这样做的:
app/models/company.rb
class Company
# ...
def self.special_get_people
people = Company.where( ... ).joins(:person)
# I was doing this in the Company model
people.instance_eval do
def as_json_with_ssn
self.map do |d|
d.as_json(except: [:encrypted_ssn] ).merge('ssn' => d.person.ssn)
end
end
def as_json(*params)
if params.empty?
super(except: [:encrypted_ssn] ).map{ |p| p.merge('ssn' => nil) }
else
super(*params)
end
end
end
return people
end
end
app/controllers/person_controller.rb
class PersonController < ApplicationController
def index
@people = Company.special_get_people
# Then manually responding with JSON
respond_to do |format|
format.html { render nothing: true, status: :not_implemented }
format.json do
render json: @people.as_json and return unless can_view_ssn
render json: @people.as_json_with_ssn
end
end
end
end
然而,这很脆弱且容易出错。我重构了上面的代码,看起来更像是这样:
app/models/company.rb
class Company
# ...
def self.special_get_people
Company.where( ... ).joins(:person)
end
end
app/controllers/person_controller.rb
class PersonController < ApplicationController
def index
@people = Company.special_get_people
end
end
app/views/person/index.jbuilder
json.people do
json.array!(@people) do |person|
json.extract! person, :id # ...
json.ssn person.ssn if can_view_ssn
end
end
这最终成为一个更好的解决方案,更灵活,更强大,更易于理解。