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]
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.
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.
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.
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:
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!
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.
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:
Setup Remote Serverkamal setup
Installs Docker (if missing) and configures the server.
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.
Ensure manifest and service-worker routes are enabled.
Verify PWA files: pwa/manifest.json.erb and pwa/service-worker.js.
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!
Ruby on Rails is a powerful framework for building web applications. If you’re setting up your development environment on macOS in 2025, this guide will walk you through installing Ruby 3.4, Rails 8, and a best IDE for development.
1. Installing Ruby and Rails
“While macOS comes with Ruby pre-installed, it’s often outdated and can’t be upgraded easily. Using a version manager like Mise allows you to install the latest Ruby version, switch between versions, and upgrade as needed.” – Rails guides
Install Dependencies
Run the following command to install essential dependencies (takes time):
zsh completions have been installed to: /opt/homebrew/share/zsh/site-functions ==> Summary 🍺 /opt/homebrew/Cellar/rust/1.84.1: 3,566 files, 321.3MB ==> Running brew cleanup rust… ==> openssl@3 A CA file has been bootstrapped using certificates from the system keychain. To add additional certificates, place .pem files in /opt/homebrew/etc/openssl@3/certs
and run /opt/homebrew/opt/openssl@3/bin/c_rehash ==> rust zsh completions have been installed to: /opt/homebrew/share/zsh/site-functions
By following this guide, you’ve successfully set up a robust Ruby on Rails development environment on macOS. With Mise for version management, Rails installed, and VS Code configured with essential extensions, you’re ready to start building Ruby on Rails applications.
When a client request comes into a Rails application, it doesn’t always go directly to the MVC (Model-View-Controller) layer. Instead, it might first pass through middleware, which handles tasks such as authentication, logging, and static asset management.
Rails uses middleware like ActionDispatch::Static to efficiently serve static assets before they even reach the main application.
ActiveRecord: Object-relational mapping (ORM) system for database interactions.
Action Pack: Handles the controller and view layers.
Active Support: A collection of utility classes and standard library extensions.
Action Mailer: A framework for designing email services.
The Role of Browsers in Asset Management
Web browsers cache static assets to improve performance. The caching strategy varies based on asset types:
Images: Rarely change, so they are aggressively cached.
JavaScript and CSS files: Frequently updated, requiring cache-busting mechanisms.
The Era of Sprockets
Historically, Rails used Sprockets as its default asset pipeline. Sprockets provided:
Conversion of CoffeeScript to JavaScript and SCSS to CSS.
Minification and bundling of assets into fewer files.
Digest-based caching to ensure updated assets were fetched when changed.
The Rise of JavaScript & The Shift Towards Webpack
The release of ES6 (2015-2016) was a turning point for JavaScript, fueling the rise of Single Page Applications (SPAs). This marked a shift from traditional asset management:
Sprockets was effective but became complex and difficult to configure for modern JS frameworks.
Projects started including package.json at the root, indicating JavaScript dependency management.
Webpack emerged as the go-to tool for handling JavaScript, offering features like tree-shaking, hot module replacement, and modern JavaScript syntax support.
The Landscape in 2024: A More Simplified Approach
Recent advancements in web technology have drastically simplified asset management:
ES6 Native Support in All Major Browsers
No need for transpilation of modern JavaScript.
CSS Advancements
Features like variables and nesting eliminate the need for preprocessors like SASS.
HTTP/2 and Multiplexing
Enables parallel loading of multiple assets over a single connection, reducing dependency on bundling strategies.
Enter Propshaft: The Modern Asset Pipeline
Propshaft is the new asset management solution introduced in Rails, replacing Sprockets for simpler and faster asset handling. Key benefits include:
Digest-based file stamping for effective cache busting.
Direct and predictable mapping of assets without complex processing.
Better integration with HTTP/2 for efficient asset delivery.
Rails 8 Precompile Uses Propshaft
What is Precompile? A Reminder
Precompilation hashes all file names and places them in the public/ folder, making them accessible to the public.
Propshaft improves upon this by creating a manifest file that maps the original filename as a key and the hashed filename as a value. This significantly enhances the developer experience in Rails.
Propshaft ultimately moves asset management in Rails to the next level, making it more efficient and streamlined.
The Future of Asset Management in Rails
With advancements like native ES6 support and CSS improvements, Rails continues evolving to embrace simpler, more efficient asset management strategies. Propshaft, combined with modern browser capabilities, makes asset handling seamless and more performance-oriented.
As the web progresses, we can expect further simplifications in asset pipelines, making Rails applications faster and easier to maintain.
Stay tuned for more innovations in the Rails ecosystem!
Today we move from basic filtering and aggregation into query composition.
This is an important topic for senior Rails interviews because many real production queries can be expressed in several ways:
JOIN
IN
EXISTS
Subquery
ActiveRecord association queries
A senior engineer should know not only how to write them, but also:
Which query expresses the business requirement most clearly?
Does the query preserve duplicates?
Does NULL affect the result?
Does PostgreSQL need to calculate one value or evaluate rows repeatedly?
What SQL is ActiveRecord generating?
We’ll build on the users and orders tables from Day 4.
Today’s Goals
By the end of Day 5, you should understand:
What a subquery is
Scalar subqueries
Multi-row subqueries
Subqueries in WHERE
Subqueries in FROM
Correlated subqueries
IN
EXISTS
NOT EXISTS
NOT IN and the NULL trap
ANY
ALL
JOIN vs IN vs EXISTS
ActiveRecord equivalents
Common mistakes
Senior interview questions
Part 1 – Prepare the Practice Data
We’ll use the same domain from Day 4, but add a few more rows to make today’s queries more interesting.
First, inspect your current data:
SELECT*FROM users ORDERBY id;
SELECT*FROM orders ORDERBY id;
If you want to recreate everything from scratch, run:
DROP TABLE IF EXISTS orders;
DROP TABLE IF EXISTS users;
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
city VARCHAR(100)
);
CREATE TABLE orders (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL,
amount NUMERIC(10,2) NOT NULL,
status VARCHAR(20) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
CONSTRAINT fk_orders_user
FOREIGN KEY (user_id)
REFERENCES users(id)
);
Insert users:
INSERTINTO users(name, city)
VALUES
('John','New York'),
('Mary','Chicago'),
('Bob','Chicago'),
('Alice','Boston'),
('David','Mumbai'),
('Sara','Boston');
Insert orders:
INSERTINTO orders(user_id, amount, status)
VALUES
(1,100,'completed'),
(1,250,'completed'),
(1,75,'pending'),
(2,500,'completed'),
(2,300,'completed'),
(3,200,'pending'),
(4,800,'completed'),
(4,150,'cancelled'),
(6,1000,'completed'),
(6,1200,'completed');
Now our data looks conceptually like this:
User
Orders
John
100, 250, 75
Mary
500, 300
Bob
200
Alice
800, 150
David
No orders
Sara
1000, 1200
This dataset is intentionally designed so we can practice:
users with orders
users without orders
users above average spending
users with expensive orders
correlated subqueries
EXISTS and NOT EXISTS
Part 2 – What Is a Subquery?
A subquery is a query nested inside another SQL statement.
Example:
SELECT*
FROM orders
WHERE amount >(
SELECT AVG(amount)
FROM orders
);
The inner query is:
SELECT AVG(amount)
FROM orders;
The outer query is:
SELECT*
FROM orders
WHERE amount >(...);
Conceptually:
Inner query
↓
Calculate average order amount
↓
Return the result
↓
Outer query
↓
Find orders greater than that value
Let’s run the inner query separately first:
SELECT AVG(amount)
FROM orders;
Total amount:
4575
Number of orders:
10
Average:
457.5
Now the outer query becomes conceptually:
SELECT*
FROM orders
WHERE amount >457.5;
Result:
500
800
1000
1200
Rails Equivalent
Order.where(
"amount > (?)",
Order.select("AVG(amount)")
)
However, in Rails you may also see:
average=Order.average(:amount)
Order.where("amount > ?", average)
These are not exactly the same approach.
The first can produce one SQL statement containing a subquery.
The second executes:
Query 1 → Calculate average
Query 2 → Find orders above average
That distinction can matter when data changes between queries and when minimizing database round trips.
Part 3 – Scalar Subqueries
A scalar subquery returns:
One row
One column
Therefore, it produces a single value.
Example:
SELECT AVG(amount)
FROM orders;
Result:
457.5
We can use that result with operators such as:
=
>
<
>=
<=
<>
Example:
SELECT
id,
user_id,
amount
FROM orders
WHERE amount >(
SELECT AVG(amount)
FROM orders
);
What Happens if the Subquery Returns Multiple Rows?
Try:
SELECT*
FROM orders
WHERE amount =(
SELECT amount
FROM orders
);
The inner query returns many rows.
PostgreSQL will raise an error similar to:
more than one row returned by a subquery used as an expression
Why?
Because:
amount = ???
expects one value.
But the subquery returned:
100
250
75
500
300
...
PostgreSQL cannot compare one amount against multiple scalar values using =.
This leads us to IN.
Part 4 – IN with a Subquery
Suppose the requirement is:
Find users who have placed at least one order.
We can write:
SELECT*
FROM users
WHERE id IN(
SELECT user_id
FROM orders
);
Run the subquery separately:
SELECT user_id
FROM orders;
Result:
1
1
1
2
2
3
4
4
6
6
Conceptually:
SELECT*
FROM users
WHERE id IN(1,1,1,2,2,3,4,4,6,6);
Result:
John
Mary
Bob
Alice
Sara
David is excluded because he has no orders.
Rails Equivalent
A good ActiveRecord version is:
User.where(
id:Order.select(:user_id)
)
Conceptually, Rails can generate:
SELECT*
FROM users
WHERE id IN(
SELECT user_id
FROM orders
);
Notice the difference between:
Order.select(:user_id)
and:
Order.pluck(:user_id)
This is important.
Using select
User.where(id:Order.select(:user_id))
can remain a single SQL statement with a subquery.
Using pluck
User.where(id:Order.pluck(:user_id))
executes the inner query immediately.
Conceptually:
Query 1
SELECT user_id FROM orders;
Then Rails constructs another query:
Query 2
SELECT *
FROM users
WHERE id IN (1, 1, 1, 2, 2, 3, ...);
For a large dataset, that can be undesirable.
Senior-Level Insight
When building SQL subqueries in ActiveRecord, don’t automatically reach for pluck.
Ask:
Do I want Ruby to materialize these IDs?
Or:
Can PostgreSQL keep the work inside one SQL statement?
Part 5 – NOT IN
Suppose the requirement is:
Find users who have never placed an order.
You might write:
SELECT*
FROM users
WHERE id NOTIN(
SELECT user_id
FROM orders
);
Result:
David
With our current schema, this works because:
orders.user_id BIGINT NOTNULL
Therefore, the subquery cannot return NULL.
But NOT IN has a famous SQL trap.
Part 6 – The NOT IN + NULL Trap
Let’s create a small demonstration table.
DROPTABLEIFEXISTS order_users_demo;
CREATETABLE order_users_demo (
user_id BIGINT
);
Insert:
INSERTINTO order_users_demo(user_id)
VALUES
(1),
(2),
(NULL);
Now run:
SELECT*
FROM users
WHERE id NOTIN(
SELECT user_id
FROM order_users_demo
);
You might expect:
Bob
Alice
David
Sara
But you get:
0 rows
Why?
Because SQL uses three-valued logic:
TRUE
FALSE
UNKNOWN
Conceptually:
id NOTIN(1,2,NULL)
behaves like:
id <>1
AND id <>2
AND id <>NULL
But:
id <>NULL
is not TRUE.
It is:
UNKNOWN
And:
TRUE AND TRUE AND UNKNOWN
results in:
UNKNOWN
WHERE only keeps rows where the condition evaluates to TRUE.
This is one of the most important SQL interview traps to remember.
Part 7 – EXISTS
Now let’s solve:
Find users who have at least one order.
Using EXISTS:
SELECT*
FROM users u
WHEREEXISTS(
SELECT1
FROM orders o
WHERE o.user_id = u.id
);
Result:
John
Mary
Bob
Alice
Sara
David is excluded.
How Does EXISTS Work?
Look carefully:
SELECT*
FROM users u
WHEREEXISTS(
SELECT1
FROM orders o
WHERE o.user_id = u.id
);
The inner query references:
u.id
But u was defined in the outer query.
Therefore, this is a:
Correlated Subquery
Conceptually PostgreSQL evaluates:
John
↓
Does an order exist with user_id = John's id?
↓
Yes
↓
Keep John
Then:
Mary
↓
Does an order exist?
↓
Yes
↓
Keep Mary
Then:
David
↓
Does an order exist?
↓
No
↓
Remove David
Important: this is a useful conceptual model, but it does not mean PostgreSQL must literally execute the inner query once per outer row. The optimizer can transform correlated EXISTS queries into efficient semi-join plans.
We’ll inspect that later using:
EXPLAIN ANALYZE
Why SELECT 1?
You commonly see:
EXISTS(
SELECT1
FROM orders
...
)
Why 1?
Because EXISTS doesn’t care what columns are returned.
It only asks:
Does at least one matching row exist?
These are semantically equivalent:
EXISTS(
SELECT1
FROM orders
WHERE ...
)
EXISTS(
SELECT*
FROM orders
WHERE ...
)
EXISTS(
SELECT amount
FROM orders
WHERE ...
)
SELECT 1 communicates intent clearly.
Part 8 – NOT EXISTS
Requirement:
Find users who have never placed an order.
SELECT*
FROM users u
WHERENOTEXISTS(
SELECT1
FROM orders o
WHERE o.user_id = u.id
);
Result:
David
This is called an anti-join pattern.
Conceptually:
For each user:
Does a matching order exist?
YES → reject
NO → keep
Compare With LEFT JOIN
We learned this yesterday:
SELECT u.*
FROM users u
LEFTJOIN orders o
ON o.user_id = u.id
WHERE o.id ISNULL;
And today:
SELECT u.*
FROM users u
WHERENOTEXISTS(
SELECT1
FROM orders o
WHERE o.user_id = u.id
);
Both express:
Find users without orders.
The PostgreSQL optimizer may produce similar execution strategies.
However, NOT EXISTS often expresses the business requirement more directly:
Keep the user if no matching order exists.
Rails Equivalent: where.missing
Rails provides a very readable API:
User.where.missing(:orders)
Conceptually, Rails generates a LEFT OUTER JOIN with an IS NULL condition.
Another option is to build a NOT EXISTS query using Arel, but for standard Rails association queries, where.missing is usually clearer.
Rails Equivalent: where.associated
Find users who have orders:
User.where.associated(:orders)
Depending on Rails version and query construction, this uses an association join and filters out missing related rows.
You may also write:
User.joins(:orders).distinct
Remember why distinct can be needed:
John has 3 orders
JOIN result:
John
John
John
EXISTS does not duplicate John because it tests existence rather than returning matching order rows.
This is a major conceptual difference.
Part 9 – IN vs EXISTS
Let’s compare them.
IN
SELECT*
FROM users
WHERE id IN(
SELECT user_id
FROM orders
);
Conceptually:
Is this user’s ID present in the set of order user IDs?
EXISTS
SELECT*
FROM users u
WHEREEXISTS(
SELECT1
FROM orders o
WHERE o.user_id = u.id
);
Conceptually:
Does at least one matching order exist for this user?
Which Is Faster?
A common junior-level answer is:
EXISTS is always faster.
That’s incorrect.
Modern PostgreSQL can rewrite IN and EXISTS into similar plans, such as semi-joins.
Performance depends on:
table sizes
indexes
statistics
data distribution
selectivity
query structure
PostgreSQL planner decisions
The correct senior-level approach is:
Choose the query that expresses the requirement clearly, then inspect the execution plan when performance matters.
Later we’ll compare:
EXPLAIN ANALYZE
SELECT ...
WHERE id IN(...);
with:
EXPLAIN ANALYZE
SELECT ...
WHEREEXISTS(...);
Part 10 – Correlated Subqueries
We’ve already seen one:
SELECT*
FROM users u
WHEREEXISTS(
SELECT1
FROM orders o
WHERE o.user_id = u.id
);
The inner query depends on the outer query.
Let’s look at another important example.
Requirement:
Find orders whose amount is greater than the average order amount for that particular user.
This is different from:
Find orders above the global average.
Global average:
SELECT*
FROM orders
WHERE amount >(
SELECT AVG(amount)
FROM orders
);
Per-user average:
SELECT
o.id,
o.user_id,
o.amount
FROM orders o
WHERE o.amount >(
SELECT AVG(o2.amount)
FROM orders o2
WHERE o2.user_id = o.user_id
);
Notice:
o2.user_id = o.user_id
The inner query references the outer row.
Let’s manually reason through John.
John’s orders:
100
250
75
Average:
141.67
Which John’s orders are above John’s average?
250
Mary:
500
300
Average:
400
Above average:
500
Alice:
800
150
Average:
475
Above average:
800
Sara:
1000
1200
Average:
1100
Above average:
1200
Bob has only one order:
200
Average:
200
Condition:
200 > 200
False.
So Bob has no matching result.
Rails Equivalent
A direct SQL fragment is often the clearest ActiveRecord solution:
Order.where(<<~SQL)
orders.amount> (
SELECTAVG(o2.amount)
FROMorderso2
WHEREo2.user_id=orders.user_id
)
SQL
Senior Rails Insight:
Not every query should be forced into a chain of ActiveRecord methods.
For complex database logic, readable SQL inside ActiveRecord can be better than complicated Arel code.
The important things are:
parameterize external input
keep the SQL understandable
test it
inspect its execution plan when needed
Part 11 – Subqueries in FROM
A subquery can also act like a temporary result set.
Requirement:
Calculate each user’s total spending, then return only users whose total spending exceeds 500.
First calculate totals:
SELECT
user_id,
SUM(amount)AS total_spent
FROM orders
GROUPBY user_id;
Now use that result as a derived table:
SELECT*
FROM(
SELECT
user_id,
SUM(amount)AS total_spent
FROM orders
GROUPBY user_id
) user_totals
WHERE total_spent >500;
Important:
PostgreSQL requires an alias for the derived table:
user_totals
Conceptually:
orders
↓
GROUP BY user_id
↓
temporary result set
user_id | total_spent
↓
filter temporary result
↓
total_spent > 500
Of course, for this particular query, HAVING is simpler:
SELECT
user_id,
SUM(amount)AS total_spent
FROM orders
GROUPBY user_id
HAVING SUM(amount)>500;
So why learn subqueries in FROM?
Because derived tables become useful when:
aggregating in multiple stages
joining against aggregated results
ranking data
reporting queries
building complex analytical queries
Part 12 – ANY
ANY compares a value against values returned by a subquery.
Example:
SELECT*
FROM orders
WHERE amount >ANY(
SELECT amount
FROM orders
WHERE user_id =1
);
John’s order amounts:
100
250
75
The condition is:
amount > ANY (100, 250, 75)
This means:
The amount must be greater than at least one value.
Effectively:
amount > 75
because being greater than the smallest value is enough to satisfy the condition.
Therefore:
>ANY
can often be thought of as:
Greater than at least one value
Part 13 – ALL
Now:
SELECT*
FROM orders
WHERE amount >ALL(
SELECT amount
FROM orders
WHERE user_id =1
);
John’s amounts:
100
250
75
Condition:
amount > ALL (100, 250, 75)
The amount must be greater than every value.
Effectively:
amount > 250
Therefore:
>ALL
means:
Greater than every value returned by the subquery.
Important ANY / ALL Mental Model
Given:
10
20
30
Then:
value>ANY(10,20,30)
means:
value > at least one of them
Equivalent threshold:
value > 10
But:
value>ALL(10,20,30)
means:
value > every one of them
Equivalent threshold:
value > 30
Be careful: this shortcut depends on the comparison operator. For example, < ANY and < ALL have different effective thresholds.
Part 14 – ANY with ActiveRecord Arrays
You may occasionally see PostgreSQL queries like:
SELECT*
FROM users
WHERE id =ANY(ARRAY[1,2,3]);
However, normal Rails code would usually use:
User.where(id: [1, 2, 3])
which generates an IN condition.
Don’t use PostgreSQL-specific syntax unless it provides a real advantage.
Part 15 – JOIN vs IN vs EXISTS
Requirement:
Find users who have completed orders.
JOIN
SELECTDISTINCT u.*
FROM users u
JOIN orders o
ON o.user_id = u.id
WHERE o.status ='completed';
Potential issue:
The join produces one row per matching order.
Therefore, duplicates may occur.
We use:
DISTINCT
IN
SELECT*
FROM users
WHERE id IN(
SELECT user_id
FROM orders
WHERE status ='completed'
);
No duplicate users in the outer result.
EXISTS
SELECT*
FROM users u
WHEREEXISTS(
SELECT1
FROM orders o
WHERE o.user_id = u.id
AND o.status ='completed'
);
Also no duplicate users.
How Should You Choose?
Use JOIN when:
You need columns from both tables.
You need to aggregate related rows.
You intentionally need matching rows.
Use EXISTS when:
You're asking whether a related row exists.
You don't need columns from the related table.
You want existence semantics without row multiplication.
Use IN when:
You're checking membership in a set of values.
The query reads naturally as "value belongs to this result set."
Do not choose solely based on old rules such as:
EXISTS is always faster than IN.
PostgreSQL’s optimizer is smarter than that.
Part 16 – Practical PostgreSQL Exercises
Let’s practice one query at a time.
Exercise 1
Find all orders above the global average order amount.
SELECT*
FROM orders
WHERE amount >(
SELECT AVG(amount)
FROM orders
);
Rails:
Order.where(
"amount > (?)",
Order.select("AVG(amount)")
)
Exercise 2
Find users who have orders.
SQL using IN:
SELECT*
FROM users
WHERE id IN(
SELECT user_id
FROM orders
);
Rails:
User.where(id:Order.select(:user_id))
Exercise 3
Find users who have orders using EXISTS.
SELECT*
FROM users u
WHEREEXISTS(
SELECT1
FROM orders o
WHERE o.user_id = u.id
);
Rails association-oriented alternative:
User.where.associated(:orders)
or:
User.joins(:orders).distinct
Exercise 4
Find users without orders.
SELECT*
FROM users u
WHERENOTEXISTS(
SELECT1
FROM orders o
WHERE o.user_id = u.id
);
Rails:
User.where.missing(:orders)
Exercise 5
Find users who have at least one completed order greater than 400.
SELECT*
FROM users u
WHEREEXISTS(
SELECT1
FROM orders o
WHERE o.user_id = u.id
AND o.status ='completed'
AND o.amount >400
);
Try writing the ActiveRecord version yourself before looking below.
One option:
User
.joins(:orders)
.where(orders: { status:"completed" })
.where("orders.amount > ?", 400)
.distinct
Exercise 6
Find orders above the average order amount for that order’s user.
SELECT*
FROM orders o
WHERE o.amount >(
SELECT AVG(o2.amount)
FROM orders o2
WHERE o2.user_id = o.user_id
);
This is today’s most important correlated subquery exercise.
Run it and manually verify every returned row.
Part 17 – Common Mistakes
Mistake 1: Using = with a Multi-Row Subquery
Wrong:
WHERE id =(
SELECT user_id
FROM orders
)
If multiple rows are returned, PostgreSQL raises an error.
Use:
IN
or:
EXISTS
depending on the requirement.
Mistake 2: Using NOT IN Without Considering NULL
Potentially dangerous:
WHERE id NOTIN(
SELECT user_id
FROM some_table
)
If the subquery can return NULL, the result may surprise you.
Safer existence-oriented query:
WHERENOTEXISTS(...)
Mistake 3: Using pluck When You Want a SQL Subquery
Potentially inefficient:
User.where(id:Order.pluck(:user_id))
Better:
User.where(id:Order.select(:user_id))
when you want PostgreSQL to handle the operation as a subquery.
Mistake 4: Using JOIN + DISTINCT for Every Existence Check
User.joins(:orders).distinct
works.
But if your requirement is simply:
Does a matching row exist?
EXISTS more directly expresses the requirement.
Mistake 5: Assuming a Correlated Subquery Always Executes Once Per Row
Conceptually, we reason about it that way.
Physically, PostgreSQL may optimize it into:
Semi Join
Anti Join
Hash Join
Nested Loop
other execution strategies
Always distinguish:
SQL semantics
from:
physical execution plan
This distinction is very important for senior-level interviews.
Part 18 – Senior Interview Questions
Try answering these without looking back.
Q1
What is the difference between a normal subquery and a correlated subquery?
Q2
What happens if a scalar subquery returns multiple rows?
Q3
What’s the difference between:
Order.select(:user_id)
and:
Order.pluck(:user_id)
when used to build another query?
Q4
Why can NOT IN return zero rows when the subquery contains NULL?
Q5
What’s the difference between:
JOIN
and:
EXISTS
when one user has many matching orders?
Q6
Is EXISTS always faster than IN in PostgreSQL?
Q7
What is a semi-join?
Q8
What is an anti-join?
Q9
When would you use a subquery in the FROM clause instead of HAVING?
Q10
What is the difference between:
>ANY
and:
>ALL
Part 19 – Today’s Interview Challenge
Do not run this immediately.
First predict the result.
SELECT
u.id,
u.name
FROM users u
WHEREEXISTS(
SELECT1
FROM orders o
WHERE o.user_id = u.id
AND o.amount >(
SELECT AVG(o2.amount)
FROM orders o2
WHERE o2.user_id = u.id
)
);
Questions:
What does the innermost query calculate?
Is the innermost query correlated?
What does the middle EXISTS query check?
Which users will be returned?
Will a user with exactly one order be returned?
Why does this query not need DISTINCT?
Try to manually execute it for:
John
Mary
Bob
Alice
David
Sara
If you can reason through this query confidently, your understanding of SQL has moved beyond basic CRUD querying.
Homework
Use the users and orders tables.
Write both SQL and ActiveRecord for each exercise.
Find users who have at least one completed order.
Find users who have no completed orders.
Find orders greater than the global average order amount.
Find orders greater than the average order amount for their respective user.
Find users whose total order amount is greater than the average total spending across all users who have orders.
Find users who have an order greater than every order placed by John. Use ALL.
Find users who have an order greater than at least one order placed by Sara. Use ANY.
Rewrite “users without orders” using:
LEFT JOIN
NOT EXISTS
NOT IN
Then explain the NULL behavior of each approach.
Write a query using a subquery in FROM to calculate user totals, then join the derived table with users to display:
user name
total spent
Use EXPLAIN ANALYZE to compare:
IN
versus:
EXISTS
for finding users with orders.
Don’t worry if you can’t interpret the complete execution plan yet. Save the output – we’ll learn how to read it systematically.
Day 6 Preview
On Day 6, we’ll cover Indexes and EXPLAIN ANALYZE.
This is one of the most important transitions in the course because we’ll move from:
“Can I write the correct query?”
to:
“Can I explain why this query is fast or slow?”
We’ll cover:
How PostgreSQL stores tables and indexes conceptually
B-tree indexes
Single-column indexes
Composite indexes
Index selectivity
Sequential Scan
Index Scan
Bitmap Index Scan
EXPLAIN
EXPLAIN ANALYZE
Why PostgreSQL sometimes ignores an index
Indexes for foreign keys
Rails migrations for indexes
Query optimization interview questions
For a senior Rails interview, Day 6 is one of the highest-value lessons in the entire course.
If I had to choose one SQL topic that appears most frequently in Senior Developer interviews, it would be:
JOINs
Most Rails developers know:
User.joins(:orders)
But many cannot explain:
What SQL Rails generates
How PostgreSQL executes it
Why duplicates occur
When to use joins
When to use includes
When JOINs become slow
A senior engineer should be comfortable with all of these.
Today’s Goals
By the end of Day 3, you’ll understand:
INNER JOIN
LEFT JOIN
RIGHT JOIN
FULL OUTER JOIN
CROSS JOIN
Self JOIN
How Rails translates associations into JOINs
N+1 query problem
joins vs includes
Interview questions
Step 1: Create Fresh Tables
Let’s create a simple system.
Users
DROPTABLEIFEXISTS orders;
DROPTABLEIFEXISTS users;
CREATETABLE users (
id BIGSERIAL PRIMARYKEY,
name VARCHAR(100)
);
Orders
CREATETABLE orders (
id BIGSERIAL PRIMARYKEY,
user_id BIGINT NOTNULL,
amount NUMERIC(10,2),
CONSTRAINT fk_orders_user
FOREIGNKEY(user_id)
REFERENCES users(id)
);
Insert Sample Data
Users:
INSERTINTO users(name)
VALUES
('John'),
('Mary'),
('Bob'),
('Alice');
Orders:
INSERTINTO orders(user_id, amount)
VALUES
(1,100),
(1,200),
(2,300),
(2,400),
(2,500);
Current data:
users
id
name
1
John
2
Mary
3
Bob
4
Alice
orders
id
user_id
amount
1
1
100
2
1
200
3
2
300
4
2
400
5
2
500
Notice:
Bob has no orders
Alice has no orders
This becomes important.
What is a JOIN?
A JOIN combines rows from multiple tables.
Think:
users
+
orders
=
business information
The database uses a common column:
users.id
=
orders.user_id
1. INNER JOIN
Most common JOIN.
Returns only matching rows.
Query
SELECT
users.id,
users.name,
orders.amount
FROM users
INNERJOIN orders
ON users.id = orders.user_id;
Result:
name
amount
John
100
John
200
Mary
300
Mary
400
Mary
500
Notice:
Bob disappeared
Alice disappeared
Why?
Because they have no matching order.
Visual
users orders
John <-> 100
John <-> 200
Mary <-> 300
Mary <-> 400
Mary <-> 500
Bob X
Alice X
Only matches survive.
Rails Equivalent
User.joins(:orders)
Generated SQL:
SELECT users.*
FROM users
INNERJOIN orders
ON orders.user_id = users.id;
Interview Question
What type of JOIN does Rails joins use?
Answer:
INNER JOIN
Many candidates miss this.
2. LEFT JOIN
Returns:
All rows from LEFT table
+
matching rows from RIGHT table
Query
SELECT
users.name,
orders.amount
FROM users
LEFTJOIN orders
ON users.id = orders.user_id;
Result:
name
amount
John
100
John
200
Mary
300
Mary
400
Mary
500
Bob
NULL
Alice
NULL
Notice:
Bob exists
Alice exists
Even without orders.
Visual
LEFT TABLE = users
Keep everything
John -> order
Mary -> order
Bob -> NULL
Alice -> NULL
Rails Equivalent
User.left_joins(:orders)
Generated SQL:
LEFTOUTERJOIN
Practical Example
Find users without orders.
SELECT users.*
FROM users
LEFTJOIN orders
ON users.id = orders.user_id
WHERE orders.id ISNULL;
Result:
Bob
Alice
Rails:
User.left_joins(:orders)
.where(orders: { id:nil })
Common Interview Question
Find customers who never placed an order.
Expected answer:
LEFTJOIN
+
ISNULL
3. RIGHT JOIN
Opposite of LEFT JOIN.
Keep all rows from right table.
SELECT*
FROM users
RIGHTJOIN orders
ON users.id = orders.user_id;
In real-world Rails projects:
Rarely used
Most engineers rewrite it as LEFT JOIN.
4. FULL OUTER JOIN
Keep everything.
SELECT*
FROM users
FULLOUTERJOIN orders
ON users.id = orders.user_id;
Returns:
All users
+
All orders
matched where possible.
Used occasionally for:
reporting
analytics
reconciliation
Rare in Rails applications.
5. CROSS JOIN
Creates every possible combination.
Example:
CREATETABLE colors (
color VARCHAR(20)
);
INSERTINTO colors
VALUES('Red'),('Blue');
Sizes:
CREATETABLE sizes (
sizeVARCHAR(20)
);
INSERTINTO sizes
VALUES('S'),('M');
Query:
SELECT*
FROM colors
CROSSJOIN sizes;
Result:
Red S
Red M
Blue S
Blue M
Every row paired with every row.
Formula:
RowsA × RowsB
Interview Question:
10 rows × 100 rows
How many rows?
Answer:
1000
6. Self JOIN
A table joins itself.
Very common interview topic.
Create employees:
CREATETABLE employees (
id BIGSERIAL PRIMARYKEY,
name VARCHAR(100),
manager_id BIGINT
);
Insert:
INSERTINTO employees
(name, manager_id)
VALUES
('CEO',NULL),
('Manager1',1),
('Manager2',1),
('Developer1',2),
('Developer2',2);
Query:
SELECT
e.name AS employee,
m.name AS manager
FROM employees e
LEFTJOIN employees m
ON e.manager_id = m.id;
Result:
employee
manager
CEO
NULL
Manager1
CEO
Developer1
Manager1
Rails
classEmployee<ApplicationRecord
belongs_to:manager,
class_name:"Employee",
optional:true
has_many:subordinates,
class_name:"Employee",
foreign_key::manager_id
end
Why Duplicates Occur
Look at:
SELECT*
FROM users
INNERJOIN orders
ON users.id = orders.user_id;
Mary has:
3 orders
Therefore:
Mary appears 3 times
JOINs multiply rows.
This is one of the most misunderstood SQL concepts.
DISTINCT After JOIN
Sometimes we want unique users.
SELECTDISTINCT users.*
FROM users
JOIN orders
ON users.id = orders.user_id;
Rails
User.joins(:orders).distinct
The N+1 Query Problem
Every Rails interview asks this.
Suppose:
users=User.all
users.eachdo |user|
putsuser.orders.count
end
Queries:
SELECT*FROM users;
Then:
SELECT*FROM orders WHERE user_id=1;
SELECT*FROM orders WHERE user_id=2;
SELECT*FROM orders WHERE user_id=3;
...
100 users:
101 queries
Called:
N+1 problem
Fix Using includes
User.includes(:orders)
Rails loads:
SELECT*FROM users;
and
SELECT*FROM orders
WHERE user_id IN(...);
Only 2 queries.
joins vs includes
This is a favorite interview question.
joins
Used for filtering.
User.joins(:orders)
SQL:
INNERJOIN
Purpose:
Filter data
includes
Used for eager loading.
User.includes(:orders)
Purpose:
Avoid N+1
Example
Find users with orders.
User.joins(:orders)
Display users and orders.
User.includes(:orders)
Interview Question
Which is better?
joins
or
includes
Answer:
Depends on the problem.
Different purposes.
Real Interview Queries
Find users with orders
SELECTDISTINCT users.*
FROM users
JOIN orders
ON users.id = orders.user_id;
Rails:
User.joins(:orders).distinct
Find users without orders
SELECT users.*
FROM users
LEFTJOIN orders
ON users.id = orders.user_id
WHERE orders.id ISNULL;
Rails:
User.left_joins(:orders)
.where(orders: { id:nil })
Find total orders per user
SELECT
users.name,
COUNT(orders.id)
FROM users
LEFTJOIN orders
ON users.id = orders.user_id
GROUPBY users.name;
We’ll study GROUP BY in Day 4.
Senior-Level Insights
1. Most Rails JOINs are INNER JOINs
joins
means:
INNERJOIN
2. LEFT JOIN + IS NULL
is the standard pattern for:
Find missing records
Examples:
users without orders
customers without subscriptions
products without inventory
3. JOINs Multiply Rows
One user:
3 orders
becomes:
3 result rows
Always remember this.
4. Understand the Generated SQL
When writing:
User.joins(:orders)
you should mentally see:
INNERJOIN orders
ON orders.user_id = users.id
immediately.
Practical Exercises
Exercise 1
Find all users with orders.
Exercise 2
Find all users without orders.
Exercise 3
Add 3 more orders for John.
Run:
INNERJOIN
Observe duplicates.
Exercise 4
Use:
DISTINCT
to remove duplicates.
Exercise 5
Create:
categories
products
and practice:
INNERJOIN
LEFTJOIN
Homework
Create the following schema:
authors
books
publishers
Relationships:
Author has many Books
Publisher has many Books
Book belongs to Author
Book belongs to Publisher
Insert sample data.
Write queries:
Books with author names
Books with publisher names
Authors without books
Publishers without books
Unique authors who have books
Write both:
SQL
ActiveRecord
Question:
I cannot see any difference in output between left join and full outer join with our example. can u create a new data to see me correct difference between them?
This is actually something that confuses many developers in interviews.
The reason you don’t see any difference is because our current data doesn’t have any “orphan” records in the orders table.
Let’s understand why.
Our Current Data
users
id
name
1
John
2
Mary
3
Bob
4
Alice
orders
id
user_id
amount
1
1
100
2
1
200
3
2
300
4
2
400
5
2
500
Every order belongs to an existing user.
So there are:
Users without orders (Bob, Alice)
No orders without users
That’s why LEFT JOIN and FULL OUTER JOIN appear almost identical.
Let’s Create a Better Example
To see the difference, we need an order that doesn’t match any user.
However…
Our foreign key prevents that.
FOREIGNKEY(user_id)
REFERENCES users(id)
This is a good thing because it maintains data integrity.
So for learning purposes, we’ll create another table without a foreign key.
Step 1
DROPTABLEIFEXISTS orders_demo;
CREATETABLE orders_demo (
id BIGSERIAL PRIMARYKEY,
user_id BIGINT,
amount NUMERIC(10,2)
);
Notice:
❌ No foreign key.
Step 2
Insert data
INSERTINTO orders_demo(user_id, amount)
VALUES
(1,100),
(1,200),
(2,300),
(999,400);
Now we have:
users
id
name
1
John
2
Mary
3
Bob
4
Alice
orders_demo
id
user_id
amount
1
1
100
2
1
200
3
2
300
4
999
400
Notice:
user_id = 999
There is no matching user.
This is our orphan order.
INNER JOIN
SELECT
u.id,
u.name,
o.amount
FROM users u
INNERJOIN orders_demo o
ON u.id = o.user_id;
Result
name
amount
John
100
John
200
Mary
300
The orphan order disappears.
LEFT JOIN
SELECT
u.id,
u.name,
o.amount
FROM users u
LEFTJOIN orders_demo o
ON u.id = o.user_id;
Result
name
amount
John
100
John
200
Mary
300
Bob
NULL
Alice
NULL
Question:
Where is the orphan order?
It is gone!
Why?
Because LEFT JOIN keeps every row from the left table (users). Since there is no user with id = 999, there is nothing on the left to preserve.
FULL OUTER JOIN
SELECT
u.id,
u.name,
o.user_id,
o.amount
FROM users u
FULLOUTERJOIN orders_demo o
ON u.id = o.user_id;
Result
user id
name
order user_id
amount
1
John
1
100
1
John
1
200
2
Mary
2
300
3
Bob
NULL
NULL
4
Alice
NULL
NULL
NULL
NULL
999
400
Now you finally see the difference!
The last row exists only because of FULL OUTER JOIN.
When it comes to building robust and maintainable applications, writing test cases is a crucial practice. In this guide, I will walk you through writing effective test cases for a Ruby on Rails model using a common model name, “Task.” The concepts discussed here are applicable to any model in your Rails application.
Why Write Test Cases?
Writing test cases is essential for several reasons:
Bug Detection: Test cases help uncover and fix bugs before they impact users.
Regression Prevention: Tests ensure that new code changes do not break existing functionality.
Documentation: Well-written test cases serve as documentation for your codebase, making it easier for other developers to understand and modify the code.
Refactoring Confidence: Tests provide the confidence to refactor code knowing that you won’t introduce defects.
Collaboration: Tests facilitate collaboration within development teams by providing a common set of expectations.
Now, let’s dive into creating test cases for a Ruby on Rails model.
Model: Task
We will use a model called “Task” as an example. Tasks might represent items on a to-do list, items in a project management system, or any other entity that requires tracking and management.
Setting Up the Environment
Before writing test cases, ensure that your Ruby on Rails application is set up correctly with the testing framework of your choice. Rails typically uses MiniTest or RSpec for testing. For this guide, we’ll use MiniTest.
# Gemfile
group :test do
gem 'minitest'
# Other testing gems...
end
After updating your Gemfile, run bundle install to install the testing gems. Ensure your test database is set up and up-to-date by running bin/rails db:test:prepare.
Writing Test Cases
Model Validation
The first set of test cases should focus on validating the model’s attributes. For our Task model, we might want to ensure that the title is present and within an acceptable length range.
# test/models/task_test.rb
require 'test_helper'
class TaskTest < ActiveSupport::TestCase
test "should not save task without title" do
task = Task.new
assert_not task.save, "Saved the task without a title"
end
test "should save task with valid title" do
task = Task.new(title: "A valid task title")
assert task.save, "Could not save the task with a valid title"
end
end
Testing Associations
In Rails, models often have associations with other models. For example, a Task might belong to a User. You can write test cases to ensure these associations work correctly.
# test/models/task_test.rb
class TaskTest < ActiveSupport::TestCase
# ...
test "task should belong to a user" do
user = User.create(name: "John")
task = Task.new(title: "Task", user: user)
assert_equal user, task.user, "Task does not belong to the correct user"
end
end
Custom Model Methods
If your model contains custom methods, ensure they behave as expected. For example, if you have a method that returns the completion status of a task, test it.
# test/models/task_test.rb
class TaskTest < ActiveSupport::TestCase
# ...
test "task should return completion status" do
task = Task.new(title: "Task", completed: false)
assert_equal "Incomplete", task.completion_status
task.completed = true
assert_equal "Complete", task.completion_status
end
end
Scopes
Scopes allow you to define common queries for your models. Write test cases to ensure scopes return the expected results.
# test/models/task_test.rb
class TaskTest < ActiveSupport::TestCase
# ...
test "completed scope should return completed tasks" do
Task.create(title: "Completed Task", completed: true)
Task.create(title: "Incomplete Task", completed: false)
completed_tasks = Task.completed
assert_equal 1, completed_tasks.length
assert_equal "Completed Task", completed_tasks.first.title
end
end
Running Tests
You can run your tests with the following command:
bin/rails test
This command will execute all the test cases you’ve written in your test files.
Conclusion
Writing test cases is an essential practice in building reliable and maintainable Ruby on Rails applications. In this guide, we’ve explored how to write effective test cases for a model using a common model name, “Task.” These principles can be applied to test any model in your Rails application.
By writing comprehensive test cases, you ensure that your application functions correctly, maintains quality over time, and makes collaboration within your development team more efficient.
In the world of Ruby programming, we often encounter scenarios where we need to work with dates. Ruby provides us with two methods, Date.current and Date.today, to retrieve the current date. Although they may appear similar at first glance, understanding their differences can help us write more accurate and reliable code. Let’s explore the reasons behind their existence, where we can use them, and the potential pitfalls we might encounter.
Why are there two different methods? Ruby’s Date.current and Date.today methods exist to handle different time zone considerations. When developing applications using the Ruby on Rails framework, it’s crucial to account for the possibility of multiple time zones. Rails provides a simple and consistent way to handle time zone-related operations, and these two methods are part of that feature set.
Where can we use them? a) Date.current: This method is specifically designed for Rails applications. It returns the current date in the time zone specified by the application’s configuration. It ensures that the date obtained is consistent across the entire application, regardless of the server or machine executing the code. Date.current is particularly useful when dealing with user interactions, scheduling, or any scenario where consistent time zone handling is necessary.
b) Date.today: This method retrieves the current date based on the default time zone of the server or machine where the code is running. It is not limited to Rails applications and can be used in any Ruby program. However, when working in a Rails application, it’s generally recommended to use Date.current to maintain consistent time zone handling.
Problems when using each method: Using these methods incorrectly or without understanding their differences can lead to unexpected results: a) Inconsistent time zones: If a Rails application is deployed across multiple servers or machines with different default time zones, using Date.today may produce inconsistent results. It can lead to situations where the same code yields different dates depending on the server’s time zone.
b) Time zone misconfigurations: In Rails applications, failing to properly set the application’s time zone can result in incorrect date calculations. It’s crucial to configure the desired time zone in the application’s configuration file, ensuring that Date.current returns the expected results.
Conclusion:
Understanding the nuances between Date.current and Date.today in Ruby can greatly improve the accuracy and reliability of our code, particularly in Rails applications. By using Date.current, we ensure consistent time zone handling throughout the application, regardless of the server or machine executing the code. Carefully considering the appropriate method to use based on the specific context can prevent common pitfalls related to time zone inconsistencies.