Matthew Lang avatar

Table of Contents


Encryption with ActiveRecord

Rails 7 introduces Active Record Encryption, allowing you to seamlessly encrypt attributes in your database. This feature ensures sensitive data is secure without requiring third-party gems like attr_encrypted.

How It Works:

You define which attributes should be encrypted, and Rails automatically encrypts the data before saving it to the database and decrypts it when loading the record.

Example:

Suppose you have a User model and want to encrypt the ssn and email attributes:

class User < ApplicationRecord
  encrypts :ssn, :email
end

Now, any data assigned to these fields will be encrypted:

user = User.create!(ssn: "123-45-6789", email: "user@example.com")
puts user.ssn  # => "123-45-6789" (decrypted)
puts User.find(user.id).ssn  # => "123-45-6789" (decrypted)

Meanwhile, the database stores the encrypted values:

SELECT ssn, email FROM users;
# Outputs unreadable encrypted data

Features:

  • Deterministic Encryption: Enables searching encrypted data, but you must trade off some security. Use encrypts :attribute, deterministic: true.
  • Automatic Key Management: Rails uses config/credentials.yml.enc for managing encryption keys.
  • Per-attribute Control: You can enable or disable encryption on a per-attribute basis.

Benefits:

  • Seamless Integration: Works out of the box without major changes to your codebase.
  • Built-in Security: Uses modern encryption standards managed by Rails.

This feature is a big win for protecting user data and meeting compliance requirements like GDPR or HIPAA.


Order of ActiveRecord components

Here’s my preferred order of components in ActiveRecord:

  1. Constants: Any constants relevant to the model.
  2. Enum Definitions: If you’re using enums in the model, these typically go right after constants.
  3. Attributes: Attributes like attr_accessorfor virtual attributes.
  4. Associations: belongs_to, has_many, has_one, has_one_attached, etc.
  5. Nested Attributes: accepts_nested_attributes_for if needed.
  6. Delegations: delegate calls if using them to delegate methods to associated objects.
  7. Scopes: Custom scopes defined with scope or class methods.
  8. Validations: validates and other validation-related methods.
  9. Callbacks: before_save, after_create, and other callbacks.
  10. Class Methods: Any custom methods defined on the model’s class.
  11. Instance Methods: Custom methods that operate on instances of the model.
  12. Private Methods: Often go at the end if used for internal model logic.

Simple search in ActiveRecord

Sometimes, we just need a simple search function in ActiveRecord. To do this, we can use the LIKE function to express our search query. The LIKE function is supported in most RDBMS, including MySQL, SQLite, and PostgreSQL.

def self.search(query, limit_count = LIMIT_COUNT)
  search_query = 'prompt LIKE :query'
  where(search_query, query: "%#{query}%")
end

The LIKE function is case-sensitive and will return results matching our query param’s case. For a case-insensitive search, use LOWER(column), LIKE LOWER(pattern) or a similar construct.

def self.search(query, limit_count = LIMIT_COUNT)
  search_query = 'prompt LIKE LOWER(:query)'
  where(search_query, query: "%#{query}%")
end

The % operators will help us match more entries by matching all records that contain our query.


Single file Rails application

Single-file Rails applications are suitable for more straightforwardly exploring specific parts of the framework and debugging issues.

Create a file a file and call it app.ru. ru is a Rackup file and will allow for inline gem dependencies.

require 'bundler/inline'

gemfile(true) do
  source 'https://rubygems.org'

  gem 'rails', '~> 7.0.4'
  gem 'sqlite3'
end

require 'rails/all'
database = 'development.sqlite3'

ENV['DATABASE_URL'] = "sqlite3:#{database}"
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: database)
ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Schema.define do
  create_table :posts, force: true do |t|
  end

  create_table :comments, force: true do |t|
    t.integer :post_id
  end
end

class App < Rails::Application
  config.root = __dir__
  config.consider_all_requests_local = true
  config.secret_key_base = 'i_am_a_secret'
  config.active_storage.service_configurations = { 'local' => { 'service' => 'Disk', 'root' => './storage' } }

  routes.append do
    root to: 'welcome#index'
  end
end

class WelcomeController < ActionController::Base
  def index
    render inline: 'Hi!'
  end
end

App.initialize!

run App