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_accessor for 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