# Migrate from attr_encrypted to ActiveRecordEncryption

Rails 7 introduced in-built encryption called Active Record Encryption and attr\_encrypted has reached its expiry. And now many projects are on the verge of moving from attr\_encrypted to Active Record Encryption. This blog will explain how this migration can be made possible with very few steps.

**Data in attr\_encrypted and Active Record Encryption:**

attr\_encrypted uses two columns in the DB to save the data, the `encrypted_attribute` and `encrypted_attribute_iv`. Active Record Encryption uses a single column same as the name of the attribute and saves the data as a hash. For example, to encrypt `address` in the `User` model, with attr\_encrypted, there are two columns `encrypted_address` and `encrypted_address_iv` in DB. With active record encryption, there is a single column in DB, `address` where the data is saved as a hash as below

```json
{"p":"Hashed Value","h":{"iv":"Hashed Value","at":"Hashed Value"}}
```

As an example, the article will migrate the `address` attribute from the `User` model. The migration includes the following steps,

1. Add Active Record Encryption to the Rails 7 app.
    
2. Add `address` and `address_tmp` columns to DB
    
3. Create migration to fill data into columns
    
4. Remove the attr\_encrypted gem and drop columns
    

**Step 1: Add Active Record Encryption to the Rails 7 app**

The new encryption from Rails 7 uses 3 keys, `primary_key`, `deterministic_key`, and `key_derivation_salt`. Generate them using the following command

```ruby
bin/rails db:encryption:init
```

Now save them in your `application.rb`

```ruby
config.active_record.encryption.primary_key = "Primary Key"
config.active_record.encryption.deterministic_key = "Deterministic Key"
config.active_record.encryption.key_derivation_salt = "Key Derivation Salt"
```

**Step 2: Add** `address` **and** `address_tmp` **columns to DB**

As mentioned in the introduction, attr\_encrypted uses different columns in DB and uses methods to return the value of the `address` attribute. So the next step is to introduce the column with the same name as the attribute in the DB. But before that, ignore the column in the model.

```ruby
Class User < ApplicationRecord
  self.ignored_columns = %w[address]
end
```

Now create a migration to add the column in the User Table. The data can’t be moved directly to the `address` column since it's still used by attr\_encrypted, so create two columns in DB. `address` and `address_tmp`. `address_tmp` will be the temporary column to save the encrypted value. The data is first saved in `address_tmp` and then the `address` column is updated using the `update_all` command. The `update_all` command does not instantiate the `User` model or trigger Active Record callbacks and validations, which works perfectly in the current scenario.

```ruby
class AddAddressAndTmpColumnToUser < ActiveRecord::Migration[7.0]
  def change
    add_column :users, :address, :text
    add_column :users, :address_tmp, :text
  end
end
```

Now add the encrypts command on the `User` model from Active Record Encryption which does all the magic. The encrypts point to the `address_tmp` and not `address`, because the attr\_encrypted gem still uses the `address` method.

```ruby
encrypts :address_tmp
```

**Step 3: Create migration to fill data into columns**

Create a migration and fill in values into the `address_tmp` column and then to the `address` column using `update_all`. The code that goes into the migration,

```ruby
User.find_in_batches do |users_batch|
  users_batch.map do |user|
    user.address_tmp = user.address
    user.save
  end
end
User.update_all("address=address_tmp")
```

Also, add a temporary code in the `User` model which will save the value in the `address` and `address_tmp` columns during updates made by clients using your application.

```ruby
after_commit :write_address, if: -> { previous_changes[:address] }

def write_address
  self.address_tmp = address
  save!
  User.where(id: id).update_all("address=address_tmp")
end
```

Now the data is migrated to the `address` and `address_tmp` columns. Any updates made by clients are saved as well in the new columns.

**Step 4: Remove the attr\_encrypted gem and drop columns**

Remove the attr\_encrypted reference from the code before removing the gem completely. The existing code might look something like the one below

```ruby
attr_encrypted :address, key: "key"
```

While removing the above code, also point to the new column `address` with the encrypted data. In the User model add the following code,

```ruby
encrypts :address
```

Remove the references that were temporarily created in the `User` model,

```ruby
# to be removed from User model
self.ignored_columns = %w[address]
after_commit :write_address, if: -> { previous_changes[:address] }
encrypts :address_tmp
```

Also if you are using `encrypted_address` or `encrypted_address_iv` directly in your code anywhere, make sure to replace them with the new values from the `address` attribute. Once all the references are removed, it's safe to remove the gem attr\_encrypted from the code.

The final step is to remove the following columns `encrypted_address`, `encrypted_address_iv`*,* and `address_tmp` from `User` model.

To know more details on Active Record Encryption, here is the official link [https://guides.rubyonrails.org/active\_record\_encryption.html](https://guides.rubyonrails.org/active_record_encryption.html)
