TIL - Rails edition
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:
- Constants: Any constants relevant to the model.
- Enum Definitions: If you’re using enums in the model, these typically go right after constants.
- Attributes: Attributes like
attr_accessor
for virtual attributes. - Associations:
belongs_to
,has_many
,has_one
,has_one_attached
, etc. - Nested Attributes:
accepts_nested_attributes_for
if needed. - Delegations: delegate calls if using them to delegate methods to associated objects.
- Scopes: Custom scopes defined with scope or class methods.
- Validations: validates and other validation-related methods.
- Callbacks:
before_save
,after_create
, and other callbacks. - Class Methods: Any custom methods defined on the model’s class.
- Instance Methods: Custom methods that operate on instances of the model.
- 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