Setup 🛠 Rails 8 App – Part 2: Command line, VS Code, RuboCop, Server, Action text, Image processing

In the first part of this guide, we covered setting up a Rails 8 app with essential configurations. In this follow-up, we’ll go over optimizing command-line usage, setting up VS Code for development, running migrations, styling the app, and enabling Action Text.


1. Optimizing Command-Line Usage with Aliases

One of the best ways to speed up development is to create shortcuts for frequently used commands. You can do this by adding aliases to your shell configuration.

Steps to Add an Alias:

  1. Open your shell configuration file:
    vim ~/.zshrc
  2. Search for the alias section:
    <esc> / alias <enter>
  3. Add your alias:
    alias gs="git status"
  4. Save and exit:
    <esc> :wq
  5. Reload your configuration:
    source ~/.zshrc
  6. Use your new alias:
    gs

This method saves time by allowing you to run frequently used commands more quickly.


2. Using Terminal Efficiently in VS Code

By default, VS Code uses `Ctrl + “ to toggle the terminal, which may not be intuitive. You can change this shortcut:

  1. Open VS Code.
  2. Go to Settings → Keyboard Shortcuts.
  3. Search for Toggle Terminal.
  4. Click Edit and change it to Ctrl + Opt + T for easier access.

3. Setting Up RuboCop in VS Code

RuboCop ensures your Ruby code follows best practices. Here’s how to set it up:

Checking RuboCop from the Terminal:

rubocop .

VS Code Setup:

  1. Open Command Palette (Cmd + Shift + P) and search for “Lint by RuboCop”.
  2. Go to Extensions Tab and install “VS Code RuboCop”.
  3. In VS Code Settings, search for “Rubocop” and check Ruby -> Rubocop -> Execute Path.
  4. Find the RuboCop installation path: whereis rubocop Example output: ~/.local/share/mise/installs/ruby/3.4.1/bin/rubocop/
  5. Update the Execute Path in VS Code to: ~/.local/share/mise/installs/ruby/3.4.1/bin/
  6. If RuboCop still returns an empty output, check .rubocop.yml in your project: ~/rails/design_studio/.rubocop.yml
  7. If the issue persists, ensure the gem is installed: gem install rubocop
  8. Restart VS Code from the Rails project root: code .

For more details, check the official documentation: RuboCop Usage


4. Running Migrations and Starting the Server

Running Migrations:

rails db:migrate -t

You can check the file: db/schema.rb You can see the active storage tables for attachments and other info.

Starting the Rails Server:

bin/dev

Access your application at: http://localhost:3000/

Check the product routes:


5. Adding Basic Styles

To quickly improve the appearance of your site, add this to application.html.erb:

<%= stylesheet_link_tag "https://cdn.simplecss.org/simple.css" %>

Alternatively, use Tailwind CSS for a more modern approach.

An example of convention over configurations in view file in Rails:

<div id="products">
  <%= render brand.products %>
</div>

Rails will look into the views/products/ folder and fetch right partial file that match.


6. Debugging with Rails Console

If an error occurs, you can inspect variables and data in the Rails console:

rails console


7. Installing Action Text for Rich Text Editing

Action Text is not installed by default but can be added easily:

rails action_text:install

This command:

  • Installs JavaScript dependencies
  • Creates necessary stylesheets
  • Generates database migrations for rich text

Running Migrations:

rails db:migrate -t

If you encounter an error about missing gems:

Could not find gem 'image_processing (~> 1.2)'

Run:

bundle install
rails db:migrate -t

Updating the Product Model:

Add this to app/models/product.rb:

has_rich_text :description

Updating the Form View:

In app/views/products/_form.html.erb, replace the description input with:

<%= form.rich_text_area :description %>

Now, visiting the new product page should display a rich text editor.


8. Solving Image Processing Errors

If you see an error like:

LoadError: Could not open library 'vips.42'

Install libvips to resolve it:

brew install vips


Conclusion

In this second part, we covered:

  • Optimizing terminal usage with aliases.
  • Configuring VS Code for efficient development.
  • Running migrations and starting the Rails server.
  • Enhancing the UI with styles.
  • Debugging using Rails console.
  • Installing and configuring Action Text for rich text support.

Stay tuned for more Rails 8 improvements!

Part 3: https://railsdrop.com/2025/03/25/setup-rails-8-app-git-setup-gitignore-part-3/

To be continued… 🚀

Setup 🛠 Rails 8 App – Part 1: Setup All Necessary Configurations | Ruby | Rails setup | Kamal | Rails Generations

Ruby on Rails 8 introduces several improvements that make development easier, more secure, and more maintainable. In this guide, we’ll walk through setting up a new Rails 8 application while noting the significant configurations and features that come out of the box.

1. Check Your Ruby and Rails Versions

If not installed Ruby 3.4 and Rails 8.0 please check: https://railsdrop.com/2025/02/11/installing-and-setup-ruby-3-rails-8-vscode-ide-on-macos-in-2025/

Before starting, ensure that you have the correct versions of Ruby and Rails installed:

$ ruby -v
ruby 3.4.1

$ rails -v
Rails 8.0.1

If you don’t have these versions installed, update them using your package manager or version manager (like rbenv or RVM).

2. Create a New Rails 8 Application

Run the following command to create a new Rails app:

$ rails new design_studio

Noteworthy Files and Directories Created

Here are some interesting files and directories that are generated with a new Rails 8 app:

 create  .ruby-version
 create  bin/brakeman
 create  bin/rubocop
 create  bin/docker-entrypoint
 create  .rubocop.yml
 create  .github/workflows
 create  .github/workflows/ci.yml
 create  config/cable.yml
 create  config/storage.yml
 create  config/initializers/content_security_policy.rb
 create  config/initializers/filter_parameter_logging.rb
 create  config/initializers/new_framework_defaults_8_0.rb

Key Takeaways:

  • Security & Code Quality Tools: Brakeman (security scanner) and RuboCop (code style linter) are included by default.
  • Docker Support: The presence of bin/docker-entrypoint suggests better built-in support for containerized deployment.
  • GitHub Actions Workflow: The .github/workflows/ci.yml file provides default CI configurations.
  • Enhanced Security: The content_security_policy.rb initializer helps enforce a strict security policy.
  • New Rails Defaults: The new_framework_defaults_8_0.rb initializer helps manage breaking changes in Rails 8.

Rails automatically creates the following during the creation of the rails new app.

a. Configuring Import Maps and Installing Turbo & Stimulus

Rails 8 still defaults to Import Maps for JavaScript package management, avoiding the need for Node.js and Webpack:

$ rails turbo:install stimulus:install

This creates the following files:

create    config/importmap.rb
create    app/javascript/controllers
create    app/javascript/controllers/index.js
create    app/javascript/controllers/hello_controller.js
append    config/importmap.rb

Key Takeaways:

  • Import Maps: Defined in config/importmap.rb, allowing dependency management without npm.
  • Hotwired Support: Turbo and Stimulus are automatically configured for modern front-end development.
  • Generated Controllers: Stimulus controllers are pre-configured inside app/javascript/controllers/.

b. Deploying with Kamal

Kamal simplifies deployment with Docker and Kubernetes. Rails 8 includes built-in support:

$ bundle binstubs kamal
$ bundle exec kamal init

This results in:

Created .kamal/secrets file
Created sample hooks in .kamal/hooks

Key Takeaways:

  • Automated Deployment Setup: Kamal provides easy-to-use deployment scripts.
  • Secret Management: The .kamal/secrets file ensures secure handling of credentials.
  • Deployment Hooks: Custom hooks allow pre- and post-deployment scripts for automation.

c. Setting Up Caching and Queues with Solid Cache, Queue, and Cable

NOTE: Rails automatically creates this for you while creating the rails app.

Rails 8 includes Solid Cache, Solid Queue, and Solid Cable for enhanced performance and scalability:

$ rails solid_cache:install solid_queue:install solid_cable:install

This creates:

create  config/cache.yml
create  db/cache_schema.rb
create  config/queue.yml

Key Takeaways:

  • Caching Support: config/cache.yml manages application-wide caching.
  • Database-Powered Queue System: Solid Queue simplifies background job management without requiring external dependencies like Sidekiq.
  • Real-Time WebSockets: Solid Cable offers Action Cable improvements for real-time features.

3. Rails 8 Migration Enhancements

Rails 8 provides new shortcuts and syntax improvements for database migrations:

NOT NULL Constraints with ! Shortcut

You can impose NOT NULL constraints directly from the command line using !:

# Example for not null constraints: 
➜ rails generate model User name:string!

Type Modifiers in Migrations

Rails 8 allows passing commonly used type modifiers directly via the command line. These modifiers are enclosed in curly braces {} after the field type.

# Example for model generation: 
➜ rails generate model Product name:string description:text
# Example for passing modifiers: 
➜ rails generate migration AddDetailsToProducts 'price:decimal{5,2}' supplier:references{polymorphic}

Generating a Scaffold for the Product Model

Let’s generate a complete scaffold for our Product model:

✗ rails generate scaffold product title:string! description:text category:string color:string 'size:string{10}' 'mrp:decimal{7,2}' 'discount:decimal{7,2}' 'rating:decimal{1,1}'
➜  design_studio git:(main) ✗ rails -v
Rails 8.0.1
➜  design_studio git:(main) ✗ ruby -v
ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +PRISM [arm64-darwin24]
➜  design_studio git:(main) ✗ rails generate scaffold product title:string! description:text category:string color:string 'size:string{10}' 'mrp:decimal{7,2}' 'discount:decimal{7,2}' 'rating:decimal{1,1}'

Using the Rails Resource Generator

The rails g resource command is a powerful way to generate models, controllers, migrations, and routes all in one go. This is particularly useful when setting up RESTful resources in a Rails application.

Basic Syntax

➜ rails g resource product

This command creates the necessary files for a new resource, including:

  • A model (app/models/product.rb)
  • A migration file (db/migrate/)
  • A controller (app/controllers/product_controller.rb)
  • Routes in config/routes.rb
  • A test file (test/controllers/product_controller_test.rb or spec/)

Example Usage

To generate a Post resource with attributes:

➜ rails g resource Product title:string! description:text brand:references

This will:

  1. Create a Product model with title and description attributes.
  2. Add a brand_id foreign key as a reference.
  3. Apply a NOT NULL constraint on title (! shortcut).
  4. Generate a corresponding migration file.
  5. Set up routes automatically (resources :products).

Running the Migration

After generating a resource, apply the migration to update the database:

➜ rails db:migrate


Difference Between resource and scaffold

Rails provides both rails g resource and rails g scaffold, but they serve different purposes:

Featurerails g resourcerails g scaffold
Generates a Model
Generates a Migration
Generates a Controller✅ (empty actions)✅ (full CRUD actions)
Generates Views (HTML/ERB)✅ (index, show, new, edit, etc.)
Generates Routes
Generates Helper Files
Generates Tests
  • rails g resource is minimal—it generates only essential files without view templates. It’s useful when you want more control over how your views and controller actions are built.
  • rails g scaffold is more opinionated and generates full CRUD functionality with prebuilt forms and views, making it ideal for rapid prototyping.

If you need full CRUD functionality quickly, use scaffold. If you prefer a leaner setup with manual control over implementation, use resource.

Conclusion

Rails 8 significantly enhances the development experience with built-in security tools, CI/CD workflows, streamlined deployment via Kamal, and modern front-end support with Turbo & Stimulus. It also improves caching, background jobs, and real-time features with Solid tools.

These improvements make Rails 8 a robust framework for modern web applications, reducing the need for additional dependencies while keeping development efficient and secure.

Enjoy Rails! 🚀

Writing Perfect Active Record 🗒️ Queries in Ruby on Rails 8

Active Record (AR) is the heart of Ruby on Rails when it comes to database interactions. Writing efficient and readable queries is crucial for application performance and maintainability. This guide will help you master Active Record queries with real-world examples and best practices.


Setting Up a Sample Database

To demonstrate complex Active Record queries, let’s create a Rails app with a sample database structure containing multiple tables.

Generate Models & Migrations

rails new MyApp --database=postgresql
cd MyApp
rails g model User name:string email:string
rails g model Post title:string body:text user:references
rails g model Comment body:text user:references post:references
rails g model Category name:string
rails g model PostCategory post:references category:references
rails g model Like user:references comment:references
rails db:migrate

Database Schema Overview

  • users: Stores user information.
  • posts: Stores blog posts written by users.
  • comments: Stores comments on posts, linked to users and posts.
  • categories: Stores post categories.
  • post_categories: Join table for posts and categories.
  • likes: Stores likes on comments by users.

Basic Active Record Queries

1. Fetching All Records

User.all  # Returns all users (Avoid using it directly on large datasets as it loads everything into memory)

⚠️ User.all can lead to performance issues if the table contains a large number of records. Instead, prefer pagination (User.limit(100).offset(0)) or batch processing (User.find_each).

2. Finding a Specific Record

User.find(1)  # Finds a user by ID
User.find_by(email: 'john@example.com')  # Finds by attribute

3. Filtering with where vs having

Post.where(user_id: 2)  # Fetch all posts by user with ID 2

Difference between where and having:

  • where is used for filtering records before grouping.
  • having is used for filtering after group operations.

Example:

Post.group(:user_id).having('COUNT(id) > ?', 5)  # Users with more than 5 posts

4. Ordering Results

User.order(:name)  # Order users alphabetically
Post.order(created_at: :desc)  # Order posts by newest first

5. Limiting Results

Post.limit(5)  # Get the first 5 posts

6. Selecting Specific Columns

User.select(:id, :name)  # Only fetch ID and name

7. Fetching Users with a Specific Email Domain

User.where("email LIKE ?", "%@gmail.com")

8. Fetching the Most Recent Posts

Post.order(created_at: :desc).limit(5)

9. Using pluck for Efficient Data Retrieval

User.pluck(:email)  # Fetch only emails as an array

10. Checking if a Record Exists Efficiently

User.exists?(email: 'john@example.com')

11. Including Associations (eager loading to avoid N+1 queries)

Post.includes(:comments).where(comments: { body: 'Great post!' })


Advanced Queries with Joins

1. Joining Tables (INNER JOIN)

Post.joins(:user).where(users: { name: 'John' })

2. Self Join Example

A self-join is useful when dealing with hierarchical relationships, such as an employee-manager structure.

Model Setup

class Employee < ApplicationRecord
  belongs_to :manager, class_name: 'Employee', optional: true
  has_many :subordinates, class_name: 'Employee', foreign_key: 'manager_id'
end

Sample Data

idnamemanager_id
1AliceNULL
2Bob1
3Carol1
4Dave2

Query: Find Employees Who Report to Alice

Employee.joins(:manager).where(managers_employees: { name: 'Alice' })

Result:

idnamemanager_id
2Bob1
3Carol1

This query fetches employees who report to Alice (i.e., those where manager_id = 1).

3. Fetching Users with No Posts (LEFT JOIN with NULL check)

User.left_outer_joins(:posts).where(posts: { id: nil })

4. Counting Posts Per User

User.joins(:posts).group('users.id').count

Complex Queries in Active Record

1. Fetching Posts with the Most Comments

Post.joins(:comments)
    .group('posts.id')
    .order('COUNT(comments.id) DESC')
    .limit(1)

2. Fetching Posts with More than 5 Comments

Post.joins(:comments)
    .group(:id)
    .having('COUNT(comments.id) > ?', 5)

3. Finding Users Who Liked the Most Comments

User.joins(comments: :likes)
    .group('users.id')
    .select('users.id, users.name, COUNT(likes.id) AS likes_count')
    .order('likes_count DESC')
    .limit(1)

4. Fetching Posts Belonging to Multiple Categories

Post.joins(:categories).group('posts.id').having('COUNT(categories.id) > ?', 1)

5. Fetching the Last Comment of Each Post

Comment.select('DISTINCT ON (post_id) *').order('post_id, created_at DESC')

6. Fetching Users Who Haven’t Commented on a Specific Post

User.where.not(id: Comment.where(post_id: 10).select(:user_id))

7. Fetching Users Who Have Commented on Every Post

User.joins(:comments).group(:id).having('COUNT(DISTINCT comments.post_id) = ?', Post.count)

8. Finding Posts With No Comments

Post.left_outer_joins(:comments).where(comments: { id: nil })

9. Fetching the User Who Created the Most Posts

User.joins(:posts)
    .group('users.id')
    .select('users.id, users.name, COUNT(posts.id) AS post_count')
    .order('post_count DESC')
    .limit(1)

10. Fetching the Most Liked Comment

Comment.joins(:likes)
    .group('comments.id')
    .order('COUNT(likes.id) DESC')
    .limit(1)

11. Fetching Comments with More than 3 Likes and Their Associated Posts

Comment.joins(:likes, :post)
    .group('comments.id', 'posts.id')
    .having('COUNT(likes.id) > ?', 3)

12. Finding Users Who Haven’t Liked Any Comments

User.left_outer_joins(:likes).where(likes: { id: nil })

13. Fetching Users, Their Posts, and the Count of Comments on Each Post

User.joins(posts: :comments)
    .group('users.id', 'posts.id')
    .select('users.id, users.name, posts.id AS post_id, COUNT(comments.id) AS comment_count')
    .order('comment_count DESC')

Importance of inverse_of in Model Associations

What is inverse_of?

The inverse_of option in Active Record associations helps Rails correctly link objects in memory, avoiding unnecessary database queries and ensuring bidirectional association consistency.

Example Usage

class User < ApplicationRecord
  has_many :posts, inverse_of: :user
end

class Post < ApplicationRecord
  belongs_to :user, inverse_of: :posts
end

Why Use inverse_of?

  • Performance Optimization: Prevents extra queries by using already loaded objects.
  • Ensures Data Consistency: Updates associations without additional database fetches.
  • Enables Nested Attributes: Helps when using accepts_nested_attributes_for.

Example:

user = User.new(name: 'Alice')
post = user.posts.build(title: 'First Post')
post.user == user  # True without needing an additional query

Best Practices to use in Rails Projects

1. Using Scopes for Readability

class Post < ApplicationRecord
  scope :recent, -> { order(created_at: :desc) }
end

Post.recent.limit(10)  # Fetch recent posts

2. Using find_each for Large Datasets

User.find_each(batch_size: 100) do |user|
  puts user.email
end

3. Avoiding SELECT * for Performance

User.select(:id, :name).load

4. Avoiding N+1 Queries with includes

Post.includes(:comments).each do |post|
  puts post.comments.count
end


Conclusion

Mastering Active Record queries is essential for writing performant and maintainable Rails applications. By using joins, scopes, batch processing, and eager loading, you can write clean and efficient queries that scale well.

Do you have any favorite Active Record query tricks? Share them in the comments!

Mastering 𖡎 Ruby: Exploring Syntax Sugar, Operators, and Useful Methods

Ruby is known for its elegant and expressive syntax, making it one of the most enjoyable programming languages to work with. In this blog post, we will explore some interesting aspects of Ruby, including syntax sugar, operator methods, useful array operations, and handy built-in methods.


Ruby Syntax Sugar

Ruby provides several shorthand notations that enhance code readability and reduce verbosity. Let’s explore some useful syntax sugar techniques.

Shorthand for Array and Symbol Arrays

Instead of manually writing arrays of strings or symbols, Ruby provides %w and %i shortcuts.

words = %w[one two three]   # => ["one", "two", "three"]
symbols = %i[one two three] # => [:one, :two, :three]

These notations also support interpolation:

prefix = 'item'
words = %W[#{prefix}_one #{prefix}_two #{prefix}_three]  # => ["item_one", "item_two", "item_three"]
symbols = %I[#{prefix}_one #{prefix}_two #{prefix}_three] # => [:item_one, :item_two, :item_three]

Shorthand for Mapping and Selecting Elements

Instead of using map with blocks, we can use &: shorthand.

strings = %w[one two three]
upcased = strings.map(&:upcase) # => ["ONE", "TWO", "THREE"]

This is equivalent to:

strings.map { |str| str.upcase }


Operator Method Calls

Many mathematical and array operations in Ruby are actually method calls, thanks to Ruby’s message-passing model.

puts 1.+(2)  # => 3 (Same as 1 + 2)

Array Indexing and Append Operations

words = %w[zero one two three four]
puts words.[](2)   # => "two" (Equivalent to words[2])
words.<<('five')   # => ["zero", "one", "two", "three", "four", "five"] (Equivalent to words << "five")

Avoiding Dot Notation with Operators

# Bad practice
num = 10
puts num.+(5) # Avoid this syntax

# Good practice
puts num + 5  # Preferred

Using a linter like rubocop can help enforce best practices in Ruby code.


none? Method in Ruby Arrays

The none? method checks if none of the elements in an array satisfy a given condition.

numbers = [1, 2, 3, 4, 5]
puts numbers.none? { |n| n > 10 }  # => true (None are greater than 10)
puts numbers.none?(&:odd?)          # => false (Some numbers are odd)

This method is useful for quickly asserting that an array does not contain specific values.


Exponent Operator (**) and XOR Operator (^)

Exponentiation

Ruby uses ** for exponentiation instead of ^ (which performs a bitwise XOR operation).

puts 2 ** 3  # => 8 (2 raised to the power of 3)
puts 10 ** 0.5 # => 3.162277660168379 (Square root of 10)

XOR Operator

In Ruby, ^ is used for bitwise XOR operations, which differ from exponentiation.

puts 5 ^ 3  # => 6 (Binary: 101 ^ 011 = 110)
puts 10 ^ 7 # => 13 (Binary: 1010 ^ 0111 = 1101)

This is useful in scenarios involving bitwise manipulations, such as cryptography or performance optimizations.


Safe Navigation Operator (&.)

Ruby provides the safe navigation operator (&.) to avoid NilClass errors when calling methods on potentially nil objects.

user = nil
puts user&.name  # => nil (Avoids NoMethodError)

This is helpful when dealing with uncertain data, such as API responses or optional attributes.


Useful String Methods: chop and find_all

chop: Removing the Last Character

The chop method removes the last character from a string, unlike chomp, which removes only the newline character.

str = "Hello!"
puts str.chop  # => "Hello"

find_all: Filtering Collections

The find_all method (alias of select) filters elements that match a condition.

numbers = [1, 2, 3, 4, 5]
even_numbers = numbers.find_all(&:even?) # => [2, 4]

This is particularly useful for data processing tasks.


=~ Operator in Ruby

The =~ operator is used to match a string against a regular expression and returns the index of the first match or nil if no match is found.

puts "hello" =~ /e/  # => 1 (Index of 'e' in "hello")
puts "hello" =~ /z/  # => nil (No match found)

This operator is useful for quick pattern matching in strings.


.squish Method in Ruby

The .squish method is part of ActiveSupport (Rails) and removes leading, trailing, and extra spaces within a string.

require 'active_support/all'

text = "   Hello   World!  "
puts text.squish  # => "Hello World!"

This is particularly useful for cleaning up user input or text from external sources.


Conclusion

Ruby’s syntax sugar, operator methods, and built-in methods make code more readable, expressive, and powerful. By leveraging these features effectively, you can write clean, efficient, and maintainable Ruby code.

Enjoy Ruby! 🚀

Rails 🛤 DSLs Explained: How Ruby Makes Configuration Elegant

Ruby on Rails is known for its developer-friendly syntax and expressive code structure. One of the key reasons behind this elegance is its use of Domain-Specific Languages (DSLs). DSLs make Rails configurations, routes, and testing more intuitive by allowing developers to write code that reads like natural language.

In this blog post, we’ll explore what DSLs are, how Rails implements them, and why they make development in Rails both powerful and enjoyable.


What is a DSL?

A Domain-Specific Language (DSL) is a specialized language designed to solve problems in a specific domain. Unlike general-purpose languages (like Ruby or Java), a DSL provides a more concise and readable syntax for a particular task.

Two types of DSLs exist:

  • Internal DSLs: Written using an existing programming language’s syntax (e.g., Rails DSLs in Ruby).
  • External DSLs: Separate from the host language and require a custom parser (e.g., SQL, Regular Expressions).

Rails uses Internal DSLs to simplify web development. Let’s explore some core DSLs in Rails and how they work under the hood.


1. Routes in Rails: A Classic Example of DSL

In config/routes.rb, Rails provides a DSL to define application routes in a clear and structured way.

Example:

Rails.application.routes.draw do
  resources :users do
    resources :posts
  end

  get '/about', to: 'pages#about'
  root 'home#index'
end

How Does This Work?

  • resources :users automatically generates RESTful routes for UsersController.
  • get '/about', to: 'pages#about' maps a GET request to the about action in PagesController.
  • root 'home#index' sets the default landing page.

Why Use a DSL for Routes?

  • Concise & Readable: Avoids manually defining each route.
  • Expressive Syntax: Reads like a structured list of instructions.
  • Reduces Boilerplate Code: Automates RESTful route creation.

Under the hood, Rails uses metaprogramming to convert this DSL into actual Ruby methods that map HTTP requests to controllers.


2. Configuration DSL in Rails: config/environments/development.rb

Rails also provides a DSL for application configuration using Rails.application.configure.

Example:

Rails.application.configure do
  config.cache_classes = false
  config.eager_load = false
  config.consider_all_requests_local = true
end

What is config Here?

  • config is an instance of Rails::Application::Configuration, a special Ruby object that stores settings.
  • The configure block modifies application settings dynamically using method calls.

Why a DSL for Configuration?

  • Expressiveness: Instead of setting key-value pairs in a hash, we use method calls (config.cache_classes = false).
  • Customization: Each environment (development, test, production) has its own configuration file.
  • Readability: Makes it easy to understand and modify settings.

3. RSpec’s describe Method: A DSL for Testing

RSpec, the popular testing framework for Ruby, provides a DSL for writing tests.

Example:

describe User do
  it "has a valid factory" do
    user = FactoryBot.create(:user)
    expect(user).to be_valid
  end
end

How Does This Work?

  • describe User do ... end defines a test suite for the User model.
  • it "has a valid factory" do ... end describes an individual test case.
  • expect(user).to be_valid checks if the user instance is valid.

Under the hood, describe is a method that creates a structured test suite dynamically.

Why Use a DSL for Testing?

  • Improves Readability: Tests read like English sentences.
  • Encapsulates Test Logic: Eliminates boilerplate setup code.
  • Encourages Behavior-Driven Development (BDD).

4. Defining Methods Dynamically: ActiveSupport::Concern

Rails extends DSL capabilities with ActiveSupport::Concern, which allows modular mixins in models and controllers.

Example:

module Trackable
  extend ActiveSupport::Concern

  included do
    before_save :track_changes
  end

  private
  def track_changes
    puts "Tracking changes!"
  end
end

class User < ApplicationRecord
  include Trackable
end

How This Works:

  • included do ... end executes code when the module is included in a class.
  • before_save :track_changes hooks into the Rails lifecycle to run before saving a record.

Why a DSL for Mixins?

  • Encapsulation: Keeps related logic together.
  • Reusability: Can be included in multiple models.
  • Cleaner Code: Removes redundant callbacks in models.

Conclusion: Why Rails Embraces DSLs

DSLs in Rails make the framework expressive, flexible, and developer-friendly. They provide:

Concise syntax (reducing boilerplate code). ✅ Readability (code reads like natural language). ✅ Powerful abstractions (simplifying complex tasks). ✅ Customization (tailoring behavior dynamically).

By leveraging DSLs, Rails makes web development intuitive, allowing developers to focus on building great applications rather than writing repetitive code.

So next time you’re defining routes, configuring settings, or writing tests in Rails—remember, you’re using DSLs that make your life easier!

Enjoy Ruby 🚀

Understanding Message ✉️ Passing in Ruby

Ruby is often celebrated for its expressive and dynamic nature, and one of its most fascinating yet under-appreciated feature is message passing. Unlike many other languages that focus purely on method calls, Ruby inherits the concept of sending messages from Smalltalk and embraces it throughout the language.

Let’s explore what message passing is, why Ruby uses it, and how you can leverage it effectively in your code.


What is Message Passing in Ruby?

In Ruby, every method call is actually a message being sent to an object. When you invoke a method on an object, Ruby sends a message to that object, asking it to execute a particular behavior.

Example:

str = "Hello, Ruby!"
puts str.reverse   # Standard method call

Behind the scenes, Ruby treats this as sending the :reverse message to the str object:

puts str.send(:reverse) # Equivalent to str.reverse

This concept extends beyond just methods—it applies to variable access and operators too! Even str.length is just Ruby sending the :length message to str.


Why Does Ruby Use Message Passing?

Message passing provides several advantages:

  1. Encapsulation and Flexibility
    • Instead of directly accessing methods, sending messages allows objects to decide how they respond to method calls.This abstraction makes code more modular and adaptable, allowing behavior to change at runtime without modifying existing method definitions.
class DynamicResponder
  def method_missing(name, *args)
    "I received a message: #{name}, but I don't have a method for it."
  end
end

obj = DynamicResponder.new
puts obj.some_method  # => "I received a message: some_method, but I don't have a method for it."

2. Dynamic Method Invocation

  • You can invoke methods dynamically at runtime using symbols instead of hardcoded method names.
method_name = :upcase
puts "ruby".send(method_name) # => "RUBY"

3. Metaprogramming Capabilities

  • Ruby allows defining methods dynamically using define_method, making it easier to create flexible APIs.
class Person
  [:first_name, :last_name].each do |method|
    define_method(method) do |value|
      instance_variable_set("@#{method}", value)
    end
  end
end

p = Person.new
p.first_name("John")
p.last_name("Doe")

Using send to Pass Messages Dynamically

The send method allows you to invoke methods dynamically using symbols or strings representing method names.

Example 1: Calling Methods Dynamically

str = "ruby messages"
puts str.send(:reverse)  # => "segassem ybur"
puts str.send(:[], 4..9) # => " messa"

Example 2: Checking Method Availability with respond_to?

puts str.respond_to?(:reverse) # => true
puts str.respond_to?(:last)    # => false

This ensures safe message passing by verifying that an object responds to a method before calling it.

Example 3: Avoiding Private Method Calls with public_send

class Secret
  private
  def hidden_message
    "This is private!"
  end
end

secret = Secret.new
puts secret.send(:hidden_message)      # Works! (bypasses visibility)
puts secret.public_send(:hidden_message) # Error! (Respects visibility)

Use public_send to ensure that only public methods are invoked dynamically.


Defining Methods Dynamically with define_method

Ruby also allows defining methods dynamically at runtime using define_method:

Example 1: Creating Methods on the Fly

class Person
  [:first_name, :last_name].each do |method|
    define_method(method) do |value|
      instance_variable_set("@#{method}", value)
    end
  end
end

p = Person.new
p.first_name("John")
p.last_name("Doe")

This creates first_name and last_name methods dynamically.

Example 2: Generating Getters and Setters

class Dynamic
  attr_accessor :data
end

obj = Dynamic.new
obj.data = "Dynamic Method"
puts obj.data # => "Dynamic Method"


Handling Undefined Methods with method_missing

If an object receives a message (method call) that it does not understand, Ruby provides a safety net: method_missing.

Example: Catching Undefined Method Calls

class Robot
  def method_missing(name, *args)
    "I don’t know how to #{name}!"
  end
end

r = Robot.new
puts r.dance # => "I don’t know how to dance!"
puts r.fly   # => "I don’t know how to fly!"

This feature is commonly used in DSLs (Domain Specific Languages) to handle flexible method invocations.


How Ruby Differs from Other Languages

Many languages support method calls, but few treat method calls as message passing the way Ruby does. For example:

  • Python: Uses getattr(obj, method_name) for dynamic calls, but lacks an equivalent to method_missing.
  • JavaScript: Uses obj[method_name]() for indirect invocation but doesn’t treat calls as messages.
  • Java: Requires reflection (Method.invoke), making it less fluid than Ruby.
  • Smalltalk: The original message-passing language, which inspired Ruby’s approach.

When to Use Message Passing Effectively

Use send for dynamic method invocation:

  • Useful for reducing boilerplate in frameworks like Rails.
  • Ideal when working with DSLs or metaprogramming scenarios.

Use respond_to? before send to avoid errors:

  • Ensures that the object actually has the method before attempting to call it.

Use define_method for dynamic method creation:

  • Helps in cases where multiple similar methods are needed.

Use method_missing cautiously:

  • Can be powerful, but should be handled carefully to avoid debugging nightmares.

Final Thoughts

Ruby’s message passing mechanism is one of the reasons it excels in metaprogramming and dynamic method invocation. Understanding this feature allows developers to write more flexible and expressive code while keeping it clean and readable. The best example using this features you can see here: https://github.com/rails/rails . None other than Rails Framework itself!!

By embracing message passing, you can unlock new levels of code abstraction, modularity, and intelligent method handling in your Ruby projects.

Enjoy Ruby 🚀

Understanding 💭 include, extend, and prepend in Ruby

Ruby is well known for its flexibility and expressive syntax. One of its powerful features is the ability to share functionality using modules. While many languages offer mixins, Ruby stands out with three key methods: include, extend, and prepend. Understanding these methods can significantly improve code reuse and clarity. Let’s explore each with examples!

include → Adds Module Methods as Instance Methods

When you use include in a class, it injects the module’s methods as instance methods of the class.

Example: Using include for instance methods

module Greet
  def hello
    "Hello from Greet module!"
  end
end

class User
  include Greet
end

user = User.new
puts user.hello # => "Hello from Greet module!"

Scope: The methods from Greet become instance methods in User.


extend → Adds Module Methods as Class Methods

When you use extend in a class, it injects the module’s methods as class methods.

Example: Using extend for class methods

module Greet
  def hello
    "Hello from Greet module!"
  end
end

class User
  extend Greet
end

puts User.hello # => "Hello from Greet module!"

Scope: The methods from Greet become class methods in User, instead of instance methods.


prepend → Adds Module Methods Before Instance Methods

prepend works like include, but the methods from the module take precedence over the class’s own methods.

Example: Using prepend to override instance methods

module Greet
  def hello
    "Hello from Greet module!"
  end
end

class User
  prepend Greet
  
  def hello
    "Hello from User class!"
  end
end

user = User.new
puts user.hello # => "Hello from Greet module!"

Scope: The methods from Greet override User‘s methods because prepend places Greet before the class in the method lookup chain.


Method Lookup Order

Understanding how Ruby resolves method calls is essential when using these techniques.

The method lookup order is as follows:

  1. Prepend modules (highest priority)
  2. Instance methods in the class itself
  3. Include modules
  4. Superclass methods
  5. Object, Kernel, BasicObject

To visualize this, use the ancestors method:

class User
  prepend Greet
end

puts User.ancestors
# => [Greet, User, Object, Kernel, BasicObject]


How Ruby Differs from Other Languages

  • Ruby is unique in dynamically modifying method resolution order (MRO) using prepend, include, and extend.
  • In Python, multiple inheritance is used with super(), but prepend-like behavior does not exist.
  • JavaScript relies on prototypes instead of module mixins, making Ruby’s approach cleaner and more structured.

Quick Summary

KeywordInjects Methods AsWorks OnTakes Precedence?
includeInstance methodsInstancesNo
extendClass methodsClassesN/A
prependInstance methodsInstancesYes (overrides class methods)

By understanding and using include, extend, and prepend, you can make your Ruby code more modular, reusable, and expressive. Master these techniques to level up your Ruby skills!

Enjoy Ruby 🚀

Exploring the Interesting Features of Ruby 💎 Programming Language

Ruby is an elegant and expressive language that stands out due to its simplicity and readability. While its syntax is designed to be natural and easy to use, Ruby also has several powerful and unique features that make it a joy to work with. Let’s explore some of the fascinating aspects of Ruby that set it apart from other programming languages.

Everything is an Expression

Unlike many other languages where statements exist separately from expressions, in Ruby, everything is an expression. Every line of code evaluates to something, even function definitions. For example:

result = def greet
  "Hello, World!"
end

puts result # => :greet

Here, defining the greet method itself returns a symbol representing its name, :greet. This feature makes metaprogramming and introspection very powerful in Ruby.

Other examples:

  1. Conditional expressions return values:
value = if 10 > 5
  "Greater"
else
  "Smaller"
end

puts value # => "Greater"

  1. Loops also evaluate to a return value:
result = while false
  "This won't run"
end

puts result # => nil

result = until true
  "This won't run either"
end

puts result # => nil

  1. Assignment is an expression:
x = y = 10 + 5
puts x # => 15
puts y # => 15

  1. Case expressions return values:
day = "Sunday"
message = case day
when "Monday"
  "Start of the week"
when "Friday"
  "Almost weekend!"
when "Sunday"
  "Time to relax!"
else
  "Just another day"
end

puts message # => "Time to relax!"

This characteristic of Ruby allows concise and elegant code that minimizes the need for temporary variables and makes code more readable.

The send Method for Dynamic Method Invocation

While Ruby does not allow storing functions in variables (like JavaScript or Python), you can hold their identifiers as symbols and invoke them dynamically using send:

def hello
  "Hello!"
end

method_name = :hello
puts send(method_name) # => "Hello!"

Why does Ruby use .send instead of .call? The call method is used for Proc and lambda objects, whereas send is a general method that allows invoking methods dynamically on an object. This also enables calling private methods.

So the question is can we restrict the programmer calling private methods with send? Let’s look at some examples how we can do that.

1. Use public_send Instead of send

The public_send method only allows calling public methods, preventing invocation of private methods.

class Demo
  private

  def secret_method
    "This is private!"
  end
end

d = Demo.new
puts d.public_send(:secret_method) # Raises NoMethodError

2. Override send in Your Class

You can override send in your class to restrict private method access.

class SecureDemo
  def send(method, *args)
    if private_methods.include?(method)
      raise NoMethodError, "Attempt to call private method `#{method}`"
    else
      super
    end
  end

  private

  def secret_method
    "This is private!"
  end
end

d = SecureDemo.new
puts d.send(:secret_method) # Raises NoMethodError
Use private_method_defined? to Manually Check for Access Level

Before calling a method via send, verify if it is public.

class SecureDemo
  def send(method, *args)
    if self.class.private_method_defined?(method)
      raise NoMethodError, "private method `#{method}` called"
    else
      super
    end
  end

  private

  def secret_method
    "This is private!"
  end
end

d = SecureDemo.new
puts d.send(:secret_method) # Raises NoMethodError

Why private_method_defined? Instead of method_defined??
  • method_defined? checks for public and protected methods but ignores private ones.
  • private_method_defined? explicitly checks for private methods, which is what we need here.

This ensures only public methods are called dynamically.

puts, p, and print – What’s the Difference?

Ruby provides multiple ways to output text:

  • puts calls .to_s on its argument and prints it, followed by a newline, but always returns nil.
  • p prints the argument using .inspect, preserving its original representation and also returns the argument.
  • print outputs text without adding a newline.

Example:

puts "Hello" # Output: Hello (returns nil)
p "Hello"   # Output: "Hello" (returns "Hello")
print "Hello" # Output: Hello (returns nil)

Variable Assignment and Storage Behavior

Ruby variables work with references, not direct values. Consider the following:

name = "Alice"
a = name
name = ["A", "l", "i", "c", "e"]

puts name # => ["A", "l", "i", "c", "e"]
puts a    # => "Alice"

Here, a still points to the original string, whereas name is reassigned to an array.

The next Keyword

Ruby’s next keyword is used to skip to the next iteration in a loop:

(1..5).each do |i|
  next if i.even?
  puts i
end

This prints:

1
3
5

Method Naming Conventions

Ruby allows method names with punctuation like ?, !, and =:

def valid?
  true
end

def modify!
  @value = 42
end

def name=(new_name)
  @name = new_name
end

  • ? indicates a method that returns a boolean.
  • ! signals a method that modifies the object in place.
  • = denotes an assignment-like method.

The Power of Symbols

Symbols in Ruby are immutable, memory-efficient string-like objects commonly used as keys in hashes:

user = { name: "John", age: 30 }
puts user[:name] # => "John"

Symbols don’t get duplicated in memory, making them faster than strings for certain use cases.

Converting Strings to Symbols with to_sym

string_key = "username"
hash = { string_key.to_sym => "johndoe" }
puts hash[:username] # => "johndoe"

Delegation in Ruby with Forwardable

Ruby provides the Forwardable module to simplify delegation by forwarding method calls to another object:

require 'forwardable'

class User
  extend Forwardable
  attr_reader :profile

  def_delegators :@profile, :email, :age

  def initialize(email, age)
    @profile = Profile.new(email, age)
  end
end

class Profile
  attr_reader :email, :age

  def initialize(email, age)
    @email = email
    @age = age
  end
end

user = User.new("john@example.com", 30)
puts user.email # => "john@example.com"
puts user.age   # => 30

This approach avoids unnecessary method redefinitions and keeps code clean.

You can read more about Ruby’s Forwardable module here:
https://ruby-doc.org/stdlib-2.5.1/libdoc/forwardable/rdoc/Forwardable.html

Ruby’s include, extend and prepend

This post is getting bigger. I have added it in a separate post.

Check the article: https://railsdrop.com/2025/03/05/understanding-include-extend-and-prepend-in-ruby/

Writing Clean Ruby Code

Using a linter and following a style guide ensures consistency and readability. Tools like RuboCop help enforce best practices.


These features showcase Ruby’s power and expressiveness. With its readable syntax, metaprogramming capabilities, and intuitive design, Ruby remains a top choice for developers who value simplicity and elegance in their code.

Enjoy Ruby 🚀

Exploring Rails 8: Powerful 💪 Features, Deployment & Real-Time Updates

Introduction

Rails 8.x has arrived, bringing exciting new features and enhancements to improve productivity, performance, and ease of development. From built-in authentication to real-time WebSocket updates, this latest version of Rails continues its commitment to being a powerful and developer-friendly framework.

Let’s dive into some of the most significant features and improvements introduced in Rails 8.


Rails 8 Features & Enhancements

1. Modern JavaScript with Importmaps & Hotwire

Rails 8 eliminates the need for Webpack and Node.js, allowing developers to manage JavaScript dependencies more efficiently. Importmaps simplify dependency management by fetching JavaScript packages directly and caching them locally, removing runtime dependencies.

Key Benefits:

  • Faster page loads and reduced complexity
  • No need for Node.js or Webpack
  • Dependencies are cached locally and loaded efficiently

Example: Pinning a Package

bin/importmap pin local-time

This command fetches the package from npm and stores it locally for future use.

Hotwire Integration

Hotwire enables dynamic page updates without requiring heavy JavaScript frameworks. Rails 8 fully integrates Turbo and Stimulus, making frontend interactivity more seamless.

Importing Dependencies in application.js:
import "trix";

With this setup, developers can create reactive UI elements with minimal JavaScript.


2. Real-Time WebSockets with Action Cable & Turbo Streams

Rails 8 enhances real-time functionality with Action Cable and Turbo Streams, allowing WebSocket-based updates across multiple pages without additional JavaScript libraries.

Setting Up Turbo Streams in Views:

<%= turbo_stream_from @object %>

This creates a WebSocket channel tied to the object.

Broadcasting Updates from Models:

broadcast_to :object, render(partial: "objects/object", locals: { object: self })

Any changes to the object will be instantly reflected across all connected clients.

Why This Matters:

  • No need for third-party WebSocket npm packages
  • Real-time updates are built into Rails
  • Simplifies building interactive applications

3. Rich Text with ActionText

Rails 8 continues to support ActionText, making it easy to handle rich text content within models and views.

Model Level Implementation:

has_rich_text :body

This enables rich text storage and formatting for the body attribute of a model.

View Implementation:

<%= form.rich_text_area :body %>

This adds a full-featured WYSIWYG text editor to the form, allowing users to create and edit rich text content seamlessly.

Displaying Updated Timestamps:

<%= time_tag post.updated_at %>

This helper formats timestamps cleanly, improving date and time representation in views.


4. Deployment with Kamal – Simpler & Faster

Rails 8 introduces Kamal, a modern deployment tool that simplifies remote deployment by leveraging Docker containers.

Deployment Steps:

  1. Setup Remote Serverkamal setup
    • Installs Docker (if missing) and configures the server.
  2. Deploy the Applicationkamal deploy
    • Builds and ships a Docker container using Rails’ default Dockerfile.

File Uploads with Active Storage

By default, Kamal stores uploaded files in Docker volumes, but this can be customized based on specific deployment needs.


5. Built-in Authentication – No Devise Needed

Rails 8 introduces native authentication, reducing reliance on third-party gems like Devise. This built-in system manages password encryption, user sessions, and password resets while keeping signup flows flexible.

Generating Authentication:

rails g authentication
rails db:migrate

Creating a User for Testing:

User.create(email: "user@example.com", password: "securepass")

Managing Authentication:

  • Uses bcrypt for password encryption
  • Provides a pre-built sessions_controller for handling authentication
  • Allows remote database changes via: kamal console

6. Turning a Rails App into a PWA

Rails 8 makes it incredibly simple to transform any app into a Progressive Web App (PWA), enabling offline support and installability.

Steps to Enable PWA:

  1. Modify application.html.erb: <%= tag.link pwa_manifest_path %>
  2. Ensure manifest and service-worker routes are enabled.
  3. Verify PWA files: pwa/manifest.json.erb and pwa/service-worker.js.
  4. Deploy and restart the application to see the Install button in the browser.

Final Thoughts

Rails 8 is packed with developer-friendly features that improve security, real-time updates, and deployment workflows. With Hotwire, Kamal, and native authentication, it’s clear that Rails is evolving to reduce dependencies while enhancing performance.

Are you excited about Rails 8? Let me know your thoughts and experiences in the comments below!

Why Ruby begin Block with ensure is Important

Ruby provides a powerful way to handle exceptions using the begin block. One of the key features of this block is ensure, which ensures that a certain section of code runs no matter what happens in the begin block. This is particularly useful when dealing with resource management, such as file handling, database connections, and network requests.

Understanding begin, rescue, and ensure

The begin block in Ruby is used to handle potential exceptions. It works alongside rescue, which catches exceptions, and ensure, which executes code regardless of whether an exception occurs.

Basic Syntax:

begin
  # Code that might raise an exception
rescue SomeError => e
  # Handle the exception
ensure
  # Code that will always execute
end

Why is ensure Important?

  1. Guaranteed Execution – Code inside ensure runs no matter what, ensuring cleanup actions always occur.
  2. Resource Cleanup – Ensures that resources like file handles, database connections, or network sockets are properly closed.
  3. Prevents Leaks – Helps avoid memory or resource leaks by making sure cleanup is performed.

Example 1: File Handling

One of the most common uses of ensure is closing a file after performing operations.

file = nil
begin
  file = File.open("example.txt", "r")
  puts file.read
rescue StandardError => e
  puts "An error occurred: #{e.message}"
ensure
  file.close if file
  puts "File closed."
end

Explanation:

  • The begin block opens a file and reads its contents.
  • If an error occurs (e.g., file not found), the rescue block catches it.
  • The ensure block ensures that the file is closed, preventing resource leaks.

Example 2: Database Connection Handling

Handling database connections properly is crucial to avoid locked or hanging connections.

require 'sqlite3'

db = nil
begin
  db = SQLite3::Database.open("test.db") # Open database connection
  db.execute("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)")
  db.execute("INSERT INTO users (name) VALUES ('Alice')")
  puts "User added successfully."
rescue SQLite3::Exception => e
  puts "Database error: #{e.message}"
ensure
  db.close if db # Ensure the database connection is closed
  puts "Database connection closed."
end

Explanation:

  • Opens a database connection and executes SQL statements.
  • If an error occurs, such as a syntax error in SQL, rescue catches it.
  • The ensure block ensures the database connection is closed, preventing connection leaks.

Example 3: Network Request Handling

When making HTTP requests, errors like timeouts or invalid URLs can occur. Using ensure, we can ensure proper handling.

require 'net/http'

url = URI("http://example.com")
response = nil

begin
  response = Net::HTTP.get(url)
  puts "Response received: #{response[0..50]}..." # Print a snippet of the response
rescue StandardError => e
  puts "Network error: #{e.message}"
ensure
  puts "Request complete. Cleanup actions (if any) can be performed here."
end

Explanation:

  • Makes an HTTP request to a given URL.
  • If an error occurs (e.g., network failure), rescue handles it.
  • The ensure block ensures any necessary final actions, such as logging, happen.

Key Takeaways

  • The ensure block always executes, making it essential for cleanup tasks.
  • It helps prevent resource leaks by ensuring proper closure of files, database connections, and network requests.
  • Using ensure makes your Ruby code robust and reliable, handling errors gracefully while ensuring necessary actions take place.

By incorporating ensure in your Ruby code, you can improve reliability, maintainability, and efficiency in handling critical resources.