jmazzi / crypt_keeper

Transparent ActiveRecord encryption
http://jmazzi.github.com/crypt_keeper/
MIT License
288 stars 99 forks source link

ActiveRecord::SubclassNotFound: Invalid single-table inheritance type: Firm is not a subclass of #164

Closed vishaltelangre closed 5 years ago

vishaltelangre commented 6 years ago

We use crypt_keeper gem having version 1.1.1. The crypt_keeper directive is defined on the Company model. The Company model has two sub-classes sharing the same companies table as follows using single-table inheritance (STI).

class Company < ActiveRecord::Base; end
class Firm < Company; end
class Client < Company; end

Since we want to upgrade the crypt_keeper gem to 2.x version, we followed the steps provided at https://github.com/jmazzi/crypt_keeper#migrating-from-cryptkeeper-1x-to-20.

It failed to decrypt the companies table though as follows.

> Company.decrypt_table!
# ActiveRecord::SubclassNotFound: Invalid single-table inheritance type: Firm is not a subclass of
# from /Users/vishaltelangre/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/activerecord-5.1.4/lib/active_record/inheritance.rb:203:in `find_sti_class'

The ::decrypt_table! method works for models having no STI implemented on them.

Since, our Company model/table had STI implementation, we needed to modify the ::decrypt_method as follows to make the decryption work for us.

company_klass = Class.new(ActiveRecord::Base).tap { |c| c.table_name = Company.table_name }

# By default, `inheritance_column` is set to `type`.
# Since the sub-classes are descendants of `Company` 
# and not of `company_klass` class, it fails with an error:
# "ActiveRecord::SubclassNotFound: Invalid single-table inheritance type: 
# Firm is not a subclass of".
# To avoid running into this error, we needed to set the inheritance column on
# the temporary class `company_klass` to nil.
company_klass.inheritance_column = nil

company_klass.all.to_a; # establish a database connection in development mode

company_klass.find_each do |record|
  Company.crypt_keeper_fields.each do |field|
    record.send("#{field}=", Company.find(record.id)[field])
  end

  record.save!
end

We pushed above change as a migration and performed a deployment to execute that migration on our servers.

Then we pushed another change with

This approach worked for us. I am not sure if we could've done anything better though. I can submit a PR if it looks okay.

Thanks.