如何在as_json中使用attr_encrypted并加入并获取解密属性?

时间:2014-07-28 22:00:22

标签: ruby-on-rails json

我使用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"}}

1 个答案:

答案 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

这最终成为一个更好的解决方案,更灵活,更强大,更易于理解。