edge badge
Skip to Content Skip to Search

Automatically expand encrypted arguments to support querying both encrypted and unencrypted data

Active Record Encryption supports querying the db using deterministic attributes. For example:

Contact.find_by(email_address: "jorge@hey.com")

The value “jorge@hey.com” will get encrypted automatically to perform the query. But there is a problem while the data is being encrypted. This won't work. During that time, you need these queries to be:

Contact.find_by(email_address: [ "jorge@hey.com", "<encrypted jorge@hey.com>" ])

This patches ActiveRecord to support this automatically. It addresses both:

  • ActiveRecord::Base: Used in +Contact.find_by_email_address(…)+

  • ActiveRecord::Relation: Used in +Contact.internal.find_by_email_address(…)+

ActiveRecord::Base relies on ActiveRecord::Relation (ActiveRecord::QueryMethods) but it does some prepared statements caching. That's why we need to intercept ActiveRecord::Base as soon as it's invoked (so that the proper prepared statement is cached).

When modifying this file run performance tests in test/performance/extended_deterministic_queries_performance_test.rb to

make sure performance overhead is acceptable.

We will extend this to support previous “encryption context” versions in future iterations

@TODO Experimental. Support for every kind of query is pending @TODO It should not patch anything if not needed (no previous schemes or no support for previous encryption schemes)

Namespace
Methods
G
V

Constants

MigrationProxy = Struct.new(:name, :version, :filename, :scope) do def initialize(name, version, filename, scope) super @migration = nil end def basename File.basename(filename) end delegate :migrate, :announce, :write, :disable_ddl_transaction, to: :migration private def migration @migration ||= load_migration end def load_migration Object.send(:remove_const, name) rescue nil load(File.expand_path(filename)) name.constantize.new(name, version) end end
 

MigrationProxy is used to defer loading of the actual migration classes until they are needed

Point = Struct.new(:x, :y)
 
UnknownAttributeError = ActiveModel::UnknownAttributeError
 

Raised when unknown attributes are supplied via mass assignment.

class Person
  include ActiveModel::AttributeAssignment
  include ActiveModel::Validations
end

person = Person.new
person.assign_attributes(name: 'Gorby')
# => ActiveModel::UnknownAttributeError: unknown attribute 'name' for Person.

Class Public methods

gem_version()

Returns the version of the currently loaded Active Record as a Gem::Version

# File activerecord/lib/active_record/gem_version.rb, line 5
def self.gem_version
  Gem::Version.new VERSION::STRING
end

version()

Returns the version of the currently loaded ActiveRecord as a Gem::Version

# File activerecord/lib/active_record/version.rb, line 7
def self.version
  gem_version
end