在SQL中的where子句中使用之前修改值?

时间:2018-10-21 02:03:38

标签: ruby-on-rails rails-activerecord

WHERE子句中,有没有一种方法可以修改模型中的值,然后再使用MySQL?

具体来说,我正在使用电话号码,并且为了确保用户隐私,我只在数据库中存储SHA256哈希电话号码。 但是,我希望能够对模型进行处理,就好像它没有被散列一样。

例如:

def phone_number=(number)
    write_attribute(:phone_number, OpenSSL::HMAC.hexdigest('SHA256', 'salt', number))
end

这使我可以保存原始电话号码并自动将其散列。但是有办法吗

PhoneNumbers.where(phone_number: '555-1234')

在将'555-1234'转换为SQL之前是否对其进行了哈希处理?

2 个答案:

答案 0 :(得分:3)

选中此https://api.rubyonrails.org/classes/ActiveRecord/Attributes/ClassMethods.html,尤其是“创建自定义类型”标题。

您定义一个自定义类型,并将其设置为属性的类型。您定义了一个“序列化”方法,该方法会将您的值转换为使用SQL语句。

从文档中

class Money < Struct.new(:amount, :currency)
end

class MoneyType < Type::Value
  def initialize(currency_converter:)
    @currency_converter = currency_converter
  end

  # value will be the result of +deserialize+ or
  # +cast+. Assumed to be an instance of +Money+ in
  # this case.
  def serialize(value)
    value_in_bitcoins = @currency_converter.convert_to_bitcoins(value)
    value_in_bitcoins.amount
  end
end

# config/initializers/types.rb
ActiveRecord::Type.register(:money, MoneyType)

# app/models/product.rb
class Product < ActiveRecord::Base
  currency_converter = ConversionRatesFromTheInternet.new
  attribute :price_in_bitcoins, :money, currency_converter: currency_converter
end

Product.where(price_in_bitcoins: Money.new(5, "USD"))
# => SELECT * FROM products WHERE price_in_bitcoins = 0.02230

Product.where(price_in_bitcoins: Money.new(5, "GBP"))
# => SELECT * FROM products WHERE price_in_bitcoins = 0.03412

如您所见,where方法接收Money对象,但是SQL语句具有十进制值。

答案 1 :(得分:0)

一个定制的序列化程序完美地解决了我的用例!

# lib/serializers/phone_hash_serializer.rb
class PhoneHashSerializer
  def self.load(value)
    value.to_s
  end

  def self.dump(value)
    value = value.to_s
    if value.start_with? 'hashed!'
      value
    else
      # Rails serializer's do not handle one-way hashing well, as the value ends up getting
      # getting dumped/hashed twice (once on the type-cast, then again before storing to DB).
      # So, we "tag" the value as "hashed!" to ensure we don't double hash since search's
      # only get hashed once, and would therefore never match the DB value
      'hashed!' + hash_string(value)
    end
  end

  private

  def self.hash_string(value)
    OpenSSL::HMAC.hexdigest('SHA256', 'hash_salt', value)
  end
end


# models/phone_number.rb
require 'serializers/phone_hash_serializer'
...
serialize :phone_number, PhoneHashSerializer
...

我以http://ruby-journal.com/how-to-write-custom-serializer-for-activerecord-number-serialize/作为缪斯。感谢那些帮助过的人!