Regular Expressions ๐ŸŽฐ in Ruby: A Step-by-Step Guide

Regular expressions (regex) are powerful tools for pattern matching and text manipulation. In Ruby, they’re implemented through the Regexp class. Let’s start with the basics and gradually build up to more complex patterns.

1. Basic Matching

Literal Characters

The simplest regex matches exact text:

"hello".match(/hello/)  #=> #<MatchData "hello">

Special Characters

Some characters have special meaning and need escaping with \:

# Matching a literal dot
"file.txt".match(/file\.txt/)  #=> #<MatchData "file.txt">

2. Character Classes

Simple Character Sets

Match any one character from a set:

# Match either 'a', 'b', or 'c'
"bat".match(/[abc]/)  #=> #<MatchData "b">

Ranges

Match any character in a range:

# Match any lowercase letter
"hello".match(/[a-z]/)  #=> #<MatchData "h">

# Match any digit
"Room 101".match(/[0-9]/)  #=> #<MatchData "1">

Negated Character Sets

Match any character NOT in the set:

# Match any character that's not a vowel
"hello".match(/[^aeiou]/)  #=> #<MatchData "h">

3. Shorthand Character Classes

Ruby provides shortcuts for common character classes:

\d  # Any digit (0-9)
\D  # Any non-digit
\w  # Word character (letter, digit, underscore)
\W  # Non-word character
\s  # Whitespace (space, tab, newline)
\S  # Non-whitespace

Examples:

"Price: $100".match(/\d+/)  #=> #<MatchData "100">
"hello_world".match(/\w+/)  #=> #<MatchData "hello_world">

4. Quantifiers

Control how many times a pattern should match:

?     # 0 or 1 times
*     # 0 or more times
+     # 1 or more times
{n}   # Exactly n times
{n,}  # n or more times
{n,m} # Between n and m times

Examples:

# Match between 3 and 5 digits
"12345".match(/\d{3,5}/)  #=> #<MatchData "12345">

# Match 'color' or 'colour'
"colour".match(/colou?r/)  #=> #<MatchData "colour">

5. Anchors

Match positions rather than characters:

^  # Start of line
$  # End of line
\A # Start of string
\Z # End of string
\b # Word boundary

Examples:

# Check if string starts with 'Hello'
"Hello world".match(/^Hello/)  #=> #<MatchData "Hello">

# Check if string ends with 'world'
"Hello world".match(/world$/)  #=> #<MatchData "world">

6. Grouping and Capturing

Parentheses create groups and capture matches:

# Capture date components
match = "2023-05-18".match(/(\d{4})-(\d{2})-(\d{2})/)
match[1]  #=> "2023" (year)
match[2]  #=> "05"   (month)
match[3]  #=> "18"   (day)

7. Alternation

The pipe | acts like an OR operator:

# Match 'cat' or 'dog'
"dog".match(/cat|dog/)  #=> #<MatchData "dog">

8. Modifiers

Change how the regex works:

i  # Case insensitive
m  # Multiline mode (dot matches newline)
x  # Ignore whitespace (for readability)

Examples:

# Case insensitive match
"HELLO".match(/hello/i)  #=> #<MatchData "HELLO">

9. Lookarounds

Assert that a pattern is or isn’t ahead/behind:

(?=pattern)  # Positive lookahead
(?!pattern)  # Negative lookahead
(?<=pattern) # Positive lookbehind
(?<!pattern) # Negative lookbehind

Example:

# Match 'q' not followed by 'u'
"qat".match(/q(?!u)/)  #=> #<MatchData "q">

10. Ruby-Specific Features

Named Captures

match = "2023-05-18".match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/)
match[:year]  #=> "2023"

%r Notation

Alternative syntax for regex literals:

%r{http://example\.com}  # Same as /http:\/\/example\.com/

String Methods Using Regex

Ruby strings have many regex methods:

"hello".gsub(/[aeiou]/, '*')  #=> "h*ll*"
"a,b,c".split(/,/)            #=> ["a", "b", "c"]
"hello".scan(/./)             #=> ["h", "e", "l", "l", "o"]

Practical Examples

  1. Email Validation:
email_regex = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
"test@example.com".match?(email_regex)  #=> true
  1. Extracting Phone Numbers:
text = "Call me at 555-1234 or (555) 987-6543"
text.scan(/(\(\d{3}\) \d{3}-\d{4}|\d{3}-\d{4})/)  #=> ["555-1234", "(555) 987-6543"]
  1. HTML Tag Extraction:
html = "<p>Hello</p><div>World</div>"
html.scan(/<(\w+)>(.*?)<\/\1>/)  #=> [["p", "Hello"], ["div", "World"]]

Tips for Effective Regex in Ruby

  1. Use Regexp.escape when matching literal strings:

Returns a new string that escapes any characters that have special meaning in a regular expression:

s = Regexp.escape('\*?{}.')      # => "\\\\\\*\\?\\{\\}\\."
   Regexp.escape("file.txt")  #=> "file\\.txt"
  1. For complex patterns, use the x modifier for readability:
   regex = /
     \A             # Start of string
     [\w+\-.]+      # Local part
     @              # @ symbol
     [a-z\d\-]+     # Domain
     (\.[a-z]+)*    # Subdomains
     \.[a-z]+\z     # TLD
   /xi
  1. Consider using Rubular (https://rubular.com/) for testing your Ruby regular expressions.

Regular expressions can become complex, but starting with these fundamentals will give you a solid foundation for text processing in Ruby.

Happy Ruby Coding! ๐Ÿš€

Useful Rubyย ๐Ÿ’Ž Methods: A Short Guide – Scan, Inject With Performance Analysis

#scan Method

Finds all occurrences of a pattern in a string and returns them as an array.

The scan method in Ruby is a powerful string method that allows you to find all occurrences of a pattern in a string. It returns an array of matches, making it extremely useful for text processing and data extraction tasks.

Basic Syntax

string.scan(pattern) โ†’ array
string.scan(pattern) { |match| block } โ†’ string

Examples

Simple Word matching:

text = "hello world hello ruby"
matches = text.scan(/hello/)
puts matches.inspect
# Output: ["hello", "hello"]

Matching Multiple Patterns

text = "The quick brown fox jumps over the lazy dog"
matches = text.scan(/\b\w{3}\b/)  # Find all 3-letter words
puts matches.inspect
# Output: ["The", "fox", "the", "dog"]

1. Find All Matches

"hello world".scan(/\w+/) # => ["hello", "world"]  

2. Extract Numbers

"Age: 25, Price: $50".scan(/\d+/) # => ["25", "50"]  

3. Matching All Characters

"hello".scan(/./) { |c| puts c }
# Output:
# h
# e
# l
# l
# o

3. Capture Groups (Returns Arrays)

"Name: Alice, Age: 30".scan(/(\w+): (\w+)/)  
# => [["Name", "Alice"], ["Age", "30"]]  

When you use parentheses in your regex, scan returns arrays of captures:

text = "John: 30, Jane: 25, Alex: 40"
matches = text.scan(/(\w+): (\d+)/)
puts matches.inspect
# Output: [["John", "30"], ["Jane", "25"], ["Alex", "40"]]

4. Iterate with a Block

"a1 b2 c3".scan(/(\w)(\d)/) { |letter, num| puts "#{letter} -> #{num}" }  
# Output:  
# a -> 1  
# b -> 2  
# c -> 3  
text = "Prices: $10, $20, $30"
total = 0
text.scan(/\$(\d+)/) { |match| total += match[0].to_i }
puts total
# Output: 60

5. Case-Insensitive Search

"Ruby is COOL!".scan(/cool/i) # => ["COOL"]  

6. Extract Email Addresses

"Email me at test@mail.com".scan(/\S+@\S+/) # => ["test@mail.com"]  
text = "Contact us at support@example.com or sales@company.org"
emails = text.scan(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/)
puts emails.inspect
# Output: ["support@example.com", "sales@company.org"]

Performance Characteristics: Ruby’s #scan Method

The #scan method is generally efficient for most common string processing tasks, but its performance depends on several factors:

  1. String length – Larger strings take longer to process
  2. Pattern complexity – Simple patterns are faster than complex regex
  3. Number of matches – More matches mean more memory allocation

Performance Considerations

1. Time Complexity ๐Ÿงฎ

  • Best case: O(n) where n is string length
  • Worst case: O(n*m) for complex regex patterns (with backtracking)

2. Memory Usage ๐Ÿง 

  • Creates an array with all matches
  • Each match is a new string object (memory intensive for large results)

Benchmark ๐Ÿ“ˆ Examples

require 'benchmark'

large_text = "Lorem ipsum " * 10_000

# Simple word matching
Benchmark.bm do |x|
  x.report("simple scan:") { large_text.scan(/\w+/) }
  x.report("complex scan:") { large_text.scan(/(?:^|\s)(\w+)(?=\s|$)/) }
end

Typical results:

              user     system      total        real
simple scan:  0.020000   0.000000   0.020000 (  0.018123)
complex scan: 0.050000   0.010000   0.060000 (  0.054678)

Optimization Tips ๐Ÿ’ก

  1. Use simpler patterns when possible:
   # Slower
   text.scan(/(?:^|\s)(\w+)(?=\s|$)/)

   # Faster equivalent
   text.scan(/\b\w+\b/)
  1. Avoid capture groups if you don’t need them:
   # Slower (creates match groups)
   text.scan(/(\w+)/)

   # Faster
   text.scan(/\w+/)
  1. Use blocks to avoid large arrays:
   # Stores all matches in memory
   matches = text.scan(pattern)

   # Processes matches without storing
   text.scan(pattern) { |m| process(m) }
  1. Consider alternatives for very large strings:
   # For simple splits, String#split might be faster
   words = text.split

   # For streaming processing, use StringIO

When to Be Cautious โš ๏ธ

  • Processing multi-megabyte strings
  • Using highly complex regular expressions
  • When you only need the first few matches (consider #match instead)

The #scan method is optimized for most common cases, but for performance-critical applications with large inputs, consider benchmarking alternatives.


#injectย Method (aka #reduce)

Enumerable#injectย takes two arguments: a base case and a block.

Each item of theย Enumerableย is passed to the block, and the result of the block is fed into the block again and iterate next item.

In a way the inject function injects the function between the elements of the enumerable. inject is aliased as reduce. You use it when you want to reduce a collection to a single value.

For example:

    product = [ 2, 3, 4 ].inject(1) do |result, next_value|
      result * next_value
    end
    product #=> 24

Purpose

  • Accumulates values by applying an operation to each element in a collection
  • Can produce a single aggregated result or a compound value

Basic Syntax

collection.inject(initial) { |memo, element| block } โ†’ object
collection.inject { |memo, element| block } โ†’ object
collection.inject(symbol) โ†’ object
collection.inject(initial, symbol) โ†’ object

Key Features

  1. Takes an optional initial value
  2. The block receives the memo (accumulator) and current element
  3. Returns the final value of the memo

๐Ÿ‘‰ Examples

1. Summing Numbers

[1, 2, 3].inject(0) { |sum, n| sum + n } # => 6

2. Finding Maximum Value

[4, 2, 7, 1].inject { |max, n| max > n ? max : n } # => 7

3. Building a Hash

[:a, :b, :c].inject({}) { |h, k| h[k] = k.to_s; h }
# => {:a=>"a", :b=>""b"", :c=>"c"}

4. Symbol Shorthand (Ruby 1.9+)

[1, 2, 3].inject(:+) # => 6 (same as sum)
[1, 2, 3].inject(2, :*) # => 12 (2 * 1 * 2 * 3)

5. String Concatenation

%w[r u b y].inject("") { |s, c| s + c.upcase } # => "RUBY"

6. Counting Occurrences

words = %w[apple banana apple cherry apple]
words.inject(Hash.new(0)) { |h, w| h[w] += 1; h }
# => {"apple"=>3, "banana"=>1, "cherry"=>1}

Performance Characteristics: Ruby’s #inject Method

  1. Time Complexity: O(n) – processes each element exactly once
  2. Memory Usage:
  • Generally creates only one accumulator object
  • Avoids intermediate arrays (unlike chained map + reduce)

Benchmark ๐Ÿ“ˆ Examples

require 'benchmark'

large_array = (1..1_000_000).to_a

Benchmark.bm do |x|
  x.report("inject:") { large_array.inject(0, :+) }
  x.report("each + var:") do
    sum = 0
    large_array.each { |n| sum += n }
    sum
  end
end

Typical results show inject is slightly slower than explicit iteration but more concise:

              user     system      total        real
inject:      0.040000   0.000000   0.040000 (  0.042317)
each + var:  0.030000   0.000000   0.030000 (  0.037894)

Optimization Tips ๐Ÿ’ก

  1. Use symbol shorthand when possible (faster than blocks):
   # Faster
   array.inject(:+)

   # Slower
   array.inject { |sum, n| sum + n }
  1. Preallocate mutable objects when building structures:
   # Good for hashes
   items.inject({}) { |h, (k,v)| h[k] = v; h }

   # Better for arrays
   items.inject([]) { |a, e| a << e.transform; a }
  1. Avoid unnecessary object creation in blocks:
   # Bad - creates new string each time
   strings.inject("") { |s, x| s + x.upcase }

   # Good - mutates original string
   strings.inject("") { |s, x| s << x.upcase }
  1. Consider alternatives for simple cases:
   # For simple sums
   array.sum # (Ruby 2.4+) is faster than inject(:+)

   # For concatenation
   array.join is faster than inject(:+)

When to Be Cautious โš ๏ธ

  • With extremely large collections where memory matters
  • When the block operations are very simple (explicit loop may be faster)
  • When building complex nested structures (consider each_with_object)

The inject method provides excellent readability with generally good performance for most use cases.


Ruby MiniTest ๐Ÿ”ฌ- Minimal replacement for test-unit

Minitest provides a complete suite of testing facilities supporting TDD, BDD, mocking, and benchmarking.

minitest/test is a small and incredibly fast unit testing framework. It provides a rich set of assertions to make your tests clean and readable.

minitest/spec is a functionally complete spec engine. It hooks onto minitest/test and seamlessly bridges test assertions over to spec expectations.

minitest/benchmark is an awesome way to assert the performance of your algorithms in a repeatable manner. Now you can assert that your newb co-worker doesn’t replace your linear algorithm with an exponential one!

minitest/mock by Steven Baker, is a beautifully tiny mock (and stub) object framework.

minitest/pride shows pride in testing and adds coloring to your test output

minitest/test_task – a full-featured and clean rake task generator.
– Minitest Github

โ™ฆ๏ธ Incredibly small and fast runner, but no bells and whistles.

Let’s take the given example in the doc, we’d like to test the following class:

class Meme
  def i_can_has_cheezburger?
    "OHAI!"
  end

  def will_it_blend?
    "YES!"
  end
end

๐Ÿงช Unit tests

Define your tests as methods beginning with test_.

require "minitest/autorun"

class TestMeme < Minitest::Test
  def setup
    @meme = Meme.new
  end

  def test_that_kitty_can_eat
    assert_equal "OHAI!", @meme.i_can_has_cheezburger?
  end

  def test_that_it_will_not_blend
    refute_match /^no/i, @meme.will_it_blend?
  end

  def test_that_will_be_skipped
    skip "test this later"
  end
end

setup ()

# File lib/minitest/test.rb, line 153
def setup; end

โ™ฆ๏ธ Runs before every test. Use this to set up before each test run.

The terms “unit test” and “spec” are often used in software testing, and while they can overlap, they have some key differences:

๐Ÿงช Unit Test vs ๐Ÿ“‹ Spec: Key Differences

๐Ÿ”ฌUnit Test

  • Purpose: Tests a single unit of code (typically a method, function, or class) in isolation
  • Scope: Very focused and narrow – tests one specific piece of functionality
  • Style: Usually follows a more traditional testing approach with setup, execution, and assertion
  • Framework examples: Minitest (like in your Ruby file), JUnit, pytest
  • Structure: Often uses test_ prefix or Test classes with assertion methods

๐Ÿ“ Spec (Specification)

  • Purpose: Describes the behavior and requirements of the system in a more readable, documentation-like format
  • Scope: Can cover unit-level, integration, or acceptance testing
  • Style: Uses natural language descriptions that read like specifications
  • Framework examples: RSpec, Jasmine, Mocha, Jest
  • Structure: Uses descriptive blocks like describe, it, should

โš–๏ธ Key Differences

1. โœ๏ธ Writing Style:

  • Unit Test
    def test_array_is_empty with assertions
  • Spec
    describe "when array is empty" do
    it "should return error message"

2. ๐Ÿ‘๏ธ Readability:

  • Unit Test: More code-focused, technical
  • Spec: More human-readable, business-focused

3. ๐ŸŽฏ Philosophy:

  • Unit Test: Test the implementation
  • Spec: Specify the behavior (BDD – Behavior Driven Development)

๐Ÿ“Š Example Comparison

๐Ÿท๏ธ Our current Minitest code:

def array_is_an_empty_array
  assert_equal 'Provide an array with length 2 or more', two_sum([], 9)
end

๐ŸŽจ RSpec equivalent (spec style):

describe "two_sum" do
  context "when array is empty" do
    it "returns an error message" do
      expect(two_sum([], 9)).to eq('Provide an array with length 2 or more')
    end
  end
end

Both test the same functionality, but specs emphasize describing behavior in natural language, making them easier for non-technical stakeholders to understand. ๐ŸŽ‰

๐Ÿ”ฌ Mini-test equivalent:

# frozen_string_literal: true

require 'minitest/spec'
require_relative 'two_sum'

describe "TwoSum" do
  describe "when array is empty" do
    it "returns an error message" do
      _(two_sum([], 9)).must_equal 'Provide an array with length 2 or more'
    end
  end
end

The underscore _() in Minitest spec style is a wrapper method that converts the value into an expectation object.

Here’s why it’s used:

๐Ÿ” Why the Underscore _()?
๐ŸŽฏ Purpose:

The _() method wraps your actual value and returns a special expectation object that has assertion methods like must_equal, must_be, etc.

โš™๏ธ How it works:
# This:
_(two_sum([], 9)).must_equal 'Provide an array with length 2 or more'

# Is equivalent to:
# 1. two_sum([], 9) returns some value
# 2. _() wraps that value into an expectation object
# 3. .must_equal() is called on that expectation object
๐Ÿ”„ Alternative Syntaxes
1. ๐Ÿ“ Using expect() (more readable):
expect(two_sum([], 9)).must_equal 'Provide an array with length 2 or more'
2. ๐Ÿ”ง Using value() (explicit):
value(two_sum([], 9)).must_equal 'Provide an array with length 2 or more'
3. ๐Ÿท๏ธ Traditional unit test style (no wrapper needed):
assert_equal 'Provide an array with length 2 or more', two_sum([], 9)
๐Ÿ’ก Why underscore specifically?
  • Minimal syntax: _() is short and doesn’t clutter the code
  • Ruby convention: Underscore often indicates “throwaway” or “utility” variables
  • Non-conflicting: Unlikely to conflict with your method names
  • Chainable: Creates a fluent interface for assertions
๐ŸŽจ Complete comparison:
# Spec style with underscore
_(result).must_equal expected

# Spec style with expect (more readable)
expect(result).must_equal expected

# Unit test style (no wrapper)
assert_equal expected, result

The expect() version is often preferred because it’s more self-documenting!

๐ŸŽจ Alternative Spec Style with Expectation Syntax:

# frozen_string_literal: true

require 'minitest/spec'
require_relative 'two_sum'

describe "TwoSum" do
  describe "when array is empty" do
    it "returns an error message" do
      expect(two_sum([], 9)).must_equal 'Provide an array with length 2 or more'
    end
  end
end

โš–๏ธ Key Differences in Minitest

๐Ÿ”ง Setup Required:
  • Unit Test: require 'minitest/test'
  • Spec: require 'minitest/spec'
โœ๏ธ Assertion Style:
  • Unit Test: assert_equal expected, actual
  • Spec: _(actual).must_equal expected or expect(actual).must_equal expected
๐Ÿ—๏ธ Structure:
  • Unit Test: Classes with test_ methods
  • Spec: describe blocks with it blocks
๐ŸŽฏ Readability:

Both styles work with Minitest, but the spec style reads more like natural language documentation, making it easier to understand the intended behavior! 

Excellent question! Let me clarify the relationship between TDD/BDD (methodologies) and RSpec/Minitest (testing frameworks):

Happy Testing!

A Complete Guide to Ruby on Rails Security Measures ๐Ÿ›ก๏ธ

Ruby on Rails is known for its developer-friendly conventions, but it’s also built with security in mind. While the framework provides many features to guard against common threats, it’s up to developers to understand and apply them correctly.

In this post, weโ€™ll walk through essential Rails security measures, tackle real-world threats, and share best practices – with examples for both API-only and full-stack Rails applications.

๐Ÿšจ Common Web Threats Rails Helps Mitigate

  1. SQL Injection
  2. Cross-Site Scripting (XSS)
  3. Cross-Site Request Forgery (CSRF)
  4. Mass Assignment
  5. Session Hijacking
  6. Insecure Deserialization
  7. Insecure File Uploads
  8. Authentication & Authorization flaws

Let’s explore how Rails addresses these and what you can do to reinforce your app.


1. ๐Ÿงฑ SQL Injection

๐Ÿ›ก๏ธ Rails Protection:

Threat: Attackers inject malicious SQL through user inputs to read, modify, or delete database records

Rails uses Active Record with prepared statements to prevent SQL injection by default.

Arel: Build complex queries without string interpolation.

# Safe - uses bound parameters
User.where(email: params[:email])

# โŒ Dangerous - interpolates input directly
User.where("email = '#{params[:email]}'")

# Safe: Parameterized query
User.where("role = ? AND created_at > ?", params[:role], 7.days.ago)

# Using Arel for dynamic conditions
users = User.arel_table
def recent_admins
  User.where(users[:role].eq('admin').and(users[:created_at].gt(7.days.ago)))
end

Tip: Never use string interpolation to build SQL queries. Use .where, .find_by, or Arel methods.

Additional Measures

  • Whitelist Columns: Pass only known column names to dynamic ordering or filtering.
  • Gem: activerecord-security to raise errors on unsafe query methods.

2. ๐Ÿงผ Cross-Site Scripting (XSS)

Threat: Injection of malicious JavaScript via user inputs, compromising other usersโ€™ sessions.

๐Ÿ›ก๏ธ Rails Protection

Content Security Policy (CSP): Limit sources of executable scripts.

# config/initializers/content_security_policy.rb
Rails.application.config.content_security_policy do |policy|
  policy.default_src :self
  policy.script_src  :self, :https
  policy.style_src   :self, :https
end

Auto-Escaping: <%= %> escapes HTML; <%== %> and raw do not.

Rails auto-escapes output in views.

<!-- Safe: Escaped -->
<%= user.bio %>

<!-- Unsafe: Unescaped (only use if trusted) -->
<%= raw user.bio %>

In API-only apps: Always sanitize any input returned in JSON if used in web contexts later.

Use gems:

  • sanitize gem to strip malicious HTML
  • loofah for more control (Loofah for robust HTML5 handling and scrubbers.)
# In models or controllers
clean_bio = Loofah.fragment(params[:bio]).scrub!(:prune).to_s

3. ๐Ÿ” Cross-Site Request Forgery (CSRF)

๐Ÿ” How CSRF Works (Example)

1.Victim logs in to bank.example.com, receiving a session cookie.

2. Attacker crafts a hidden form on attacker.com:

<form action="https://bank.example.com/transfer" method="POST">
  <input type="hidden" name="amount" value="1000">
  <input type="hidden" name="to_account" value="attacker_account">
</form>
<script>document.forms[0].submit();</script>

3. Victim visits attacker.com while still logged into the bank.

4. Browser auto-sends the bank session cookie with the forged POSTโ€”and the transfer goes through, because the bank sees a โ€œlegitimateโ€ logged-in request.

๐Ÿ›ก๏ธ Rails’ CSRF Protection

Rails ships with built-in defenses against CSRF by embedding an unguessable token in forms and verifying it on each non-GET request.

1.protect_from_forgery

In ApplicationController, Rails by default includes:

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
end

This causes Rails to raise an exception if the token is missing or invalid.

Refer: https://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection/ClassMethods.html

2. Authenticity Token in Forms

All Rails form-builders (form_with, form_for, form_tag) automatically insert:

<input type="hidden" name="authenticity_token" value="โ€ฆsecure randomโ€ฆ">

3.Meta Tag for AJAX

Rails also inserts in <head>:

<%= csrf_meta_tags %>

Which renders:

<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="โ€ฆsecure randomโ€ฆ" />

Rails UJS or Turbo JS will read these and include the token on XHR/fetch requests.

4.Server-Side Verification

On each POST/PUT/PATCH/DELETE, Rails:

  • Extracts the token (from form param, header X-CSRF-Token, or query string).
  • Compares it to the session-stored token.
  • Rejects the request if they donโ€™t match.

API-only apps:

CSRF is less relevant for stateless APIs using tokens, but if you use session-based authentication, enable CSRF protection:

# application_controller.rb
protect_from_forgery with: :exception

Thought for a couple of seconds

Cross-Site Request Forgery (CSRF) is an attack that tricks a userโ€™s browser into submitting a request (e.g. form submission, link click) to your application without the userโ€™s intention, leveraging the fact that the browser automatically includes credentials (cookies, basic auth headers, etc.) with each request.

๐Ÿ”ง Disabling or Customizing CSRF

โ™ฆ๏ธ Disable for APIs (stateless JSON endpoints):

class Api::BaseController < ActionController::API skip_before_action :verify_authenticity_token end

โ™ฆ๏ธ Use Null Session (allowing some API use without exception):

protect_from_forgery with: :null_session

โœ… Key Takeaways

  • CSRF exploits the browserโ€™s automatic credential sending.
  • Rails guards by inserting and validating an unguessable token.
  • Always keep protect_from_forgery with: :exception in your base controller for full-stack Rails apps.

4. ๐Ÿ›‘ Mass Assignment Vulnerability

Threat: Attackers pass unexpected parameters to update sensitive attributes (e.g., admin=true).

Before Rails 4, mass assignment was a common issue. Now, strong parameters protect against it.

โœ… Use Strong Parameters:

# โœ… Safe
def user_params
  params.require(:user).permit(:name, :email)
end

User.create(user_params)

# โŒ Unsafe
User.create(params[:user])

Pro tip: Don’t over-permit, especially with admin or role-based attributes.

Real-World Gotcha

  • Before permitting arrays or nested attributes, validate length and content.
params.require(:order).permit(:total, items: [:product_id, :quantity])

5. ๐Ÿ”’ Secure Authentication

Built-In: has_secure_password

Provides authenticate method.

Uses BCrypt with configurable cost.

# user.rb
class User < ApplicationRecord
  has_secure_password
  # optional: validates length, complexity
  validates :password, length: { minimum: 12 }, format: { with: /(?=.*\d)(?=.*[A-Z])/ }
end

Make sure you have a password_digest column. This uses bcrypt under the hood.

Using Devise

JWT: integrate with devise-jwt for stateless APIs.

Modules: Database Authenticatable, Confirmable, Lockable, Timeoutable, Trackable.

Devise gives you:

  • Password encryption
  • Lockable accounts
  • Timeoutable sessions
  • Token-based authentication for APIs (with devise-jwt)
# config/initializers/devise.rb
Devise.setup do |config|
  config.jwt do |jwt|
    jwt.secret = Rails.application.credentials.devise_jwt_secret
    jwt.dispatch_requests = [['POST', %r{^/login$}]]
    jwt.revocation_requests = [['DELETE', %r{^/logout$}]]
  end
end

6. ๐Ÿงพ Authorization

Threat: Users access or modify resources beyond their permissions.

Never trust the frontend. Always check permissions server-side.

# โŒ Dangerous
redirect_to dashboard_path if current_user.admin?

# โœ… Use Pundit or CanCanCan
authorize @order

Gems:

  • pundit โ€“ lean policy-based authorization
  • cancancan โ€“ rule-based authorization

Pundit Example

# app/policies/article_policy.rb
class ArticlePolicy
  attr_reader :user, :article

  def initialize(user, article)
    @user = user
    @article = article
  end

  def update?
    user.admin? || article.author_id == user.id
  end
end

# In controller
def update
  @article = Article.find(params[:id])
  authorize @article
  @article.update!(article_params)
end

Use Existing Auditing Libraries

To track user actions including access, use:

For Rails 8 check the post for Rails own Authentication: https://railsdrop.com/2025/05/07/rails-8-implement-users-authentication-orders-order-items/

7. ๐Ÿ—ƒ๏ธ Secure File Uploads

Threat: Attackers upload malicious files (e.g., scripts, executables).

Use Active Storage securely:

<%= image_tag url_for(user.avatar) %>

Active Storage Best Practices

Validation:

class Photo < ApplicationRecord
  has_one_attached :image
  validate :image_type, :image_size

  private
  def image_type
    return unless image.attached?
    acceptable = ['image/jpeg', 'image/png']
    errors.add(:image, 'must be JPEG or PNG') unless acceptable.include?(image.content_type)
  end

  def image_size
    return unless image.attached?
    errors.add(:image, 'is too big') if image.byte_size > 5.megabytes
  end
end
  • Validate content type:
validates :avatar, content_type: ['image/png', 'image/jpg', 'image/jpeg']

  • Restrict file size.
  • Store uploads in private S3 buckets for sensitive data.
  • Private URLs for sensitive documents (e.g., contracts).
  • Virus Scanning: hook into after_upload to scan with ClamAV (or VirusTotal API).

8. ๐Ÿงพ HTTP Headers & SSL

Rails helps with secure headers via secure_headers gem (https://github.com/github/secure_headers).

# config/initializers/secure_headers.rb
SecureHeaders::Configuration.default do |config|
  config.hsts = "max-age=31536000; includeSubDomains"
  config.x_frame_options = "DENY"
  config.x_content_type_options = "nosniff"
  config.x_xss_protection = "1; mode=block"
end

SSL/TLS Force HTTPS:

# config/environments/production.rb
config.force_ssl = true

Ensure HSTS is enabled:
# config/initializers/secure_headers.rb
Rails.application.config.middleware.insert_before 0, SecureHeaders::Middleware
SecureHeaders::Configuration.default do |config|
  config.hsts = "max-age=63072000; includeSubDomains; preload"
end

Key Headers

  • X-Frame-Options: DENY to prevent clickjacking.
  • X-Content-Type-Options: nosniff.
  • Referrer-Policy: strict-origin-when-cross-origin.

9. ๐Ÿงช Security Testing

  • Use brakeman to detect common vulnerabilities.
bundle exec brakeman

  • Add bundler-audit to scan for insecure gems.
bundle exec bundler-audit check --update

Check the post for more details: https://railsdrop.com/2025/05/05/rails-8-setup-simplecov-brakeman-for-test-coverage-security/

  • Fuzz & Pen Testing: Use tools like ZAP Proxy, OWASP ZAP.
  • Use RSpec tests for role restrictions, parameter whitelisting, and CSRF.
describe "Admin access" do
  it "forbids non-admins from deleting users" do
    delete admin_user_path(user)
    expect(response).to redirect_to(root_path)
  end
end

  • Continuous Integration – Integrate scans in CI pipeline (GitHub Actions example):
jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Brakeman
        run: bundle exec brakeman -o brakeman-report.html
      - name: Bundler Audit
        run: bundle exec bundler-audit check --update

Read the post: Setup ๐Ÿ›  Rails 8 App โ€“ Part 15: Set Up CI/CD โš™๏ธ with GitHub Actions for Rails 8

10. ๐Ÿ”‘ API Security (Extra Measures)

  • Use JWT or OAuth2 for stateless token authentication.
  • Set appropriate CORS headers.

Gem: `rack-cors` (https://github.com/cyu/rack-cors)

Add in your Gemfile:

gem 'rack-cors'
# config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins 'your-frontend.com'
    resource '*',
      headers: :any,
      expose: ['Authorization'],
      methods: [:get, :post, :patch, :put, :delete, :options]
  end
end

  • Rate-limit endpoints with Rack::Attack

Include the Gem rack-attack (https://github.com/rack/rack-attack) to your Gemfile.

# In your Gemfile
gem 'rack-attack'
# config/initializers/rack_attack.rb

Rack::Attack.throttle('req/ip', limit: 60, period: 1.minute) do |req|
  req.ip
end

in Rails 8 we can use rate_limit for Controller actions like:

  rate_limit to: 10, within: 1.minutes, only: :create, with: -> { redirect_to new_session_url, alert: "Try again later." }

  • Pagination & Filtering: Prevent large payloads to avoid DoS.

๐Ÿ“ Summary: Best Practices Checklist

โœ… Use Strong Parameters
โœ… Escape output (no raw unless absolutely trusted)
โœ… Sanitize user content
โœ… Use Devise or Sorcery for auth
โœ… Authorize every resource with Pundit or CanCanCan
โœ… Store files safely and validate uploads
โœ… Enforce HTTPS in production
โœ… Regularly run Brakeman and bundler-audit
โœ… Rate-limit APIs with Rack::Attack
โœ… Keep dependencies up to date

๐Ÿ” Final Thought

Rails does a lot to keep you safe โ€” but security is your responsibility. Follow these practices and treat every external input as potentially dangerous. Security is not a one-time setup โ€” it’s an ongoing process.


Happy and secure coding! ๐Ÿš€

Rails 8 App: Adding SimpleCov ๐Ÿงพ & Brakeman ๐Ÿ”ฐ To Our Application For CI/CD Setup

Ensuring code quality and security in a Rails application is critical – especially as your project grows. In this post, weโ€™ll walk through integrating two powerful tools into your Rails 8 app:

  1. SimpleCov: for measuring and enforcing test coverage
  2. Brakeman: for automated static analysis of security vulnerabilities

By the end, youโ€™ll understand why each tool matters, how to configure them, and the advantages they bring to your development workflow.

Why Code Coverage & Security Scanning Matter

  • Maintainability
    Tracking test coverage ensures critical paths are exercised by your test suite. Over time, you can guard against regressions and untested code creeping in.
  • Quality Assurance
    High coverage correlates with fewer bugs: untested code is potential technical debt. SimpleCov gives visibility into whatโ€™s untested.
  • Security
    Rails apps can be vulnerable to injection, XSS, mass assignment, and more. Catching these issues early, before deployment, dramatically reduces risk.
  • Compliance & Best Practices
    Many organizations require minimum coverage thresholds and regular security scans. Integrating these tools automates compliance.

Part 1: Integrating SimpleCov for Test Coverage

1. Add the Gem

In your Gemfile, under the :test group, add:

group :test do
  gem 'simplecov', require: false
end

Then run:

bundle install

2. Configure SimpleCov

Create (or update) test/test_helper.rb (for Minitest) before any application code is loaded:

require 'simplecov'
SimpleCov.start 'rails' do
  coverage_dir 'public/coverage'           # output directory
  minimum_coverage 90               # fail if coverage < 90%
  add_filter '/test/'               # ignore test files themselves
  add_group 'Models', 'app/models'
  add_group 'Controllers', 'app/controllers'
  add_group 'Jobs', 'app/jobs'
  add_group 'Libraries', 'lib'
end

# Then require the rest of your test setup
ENV['RAILS_ENV'] ||= 'test'
require_relative '../config/environment'
require 'rails/test_help'
# ...

Tip: You can customize groups, filters, and thresholds. If coverage dips below the set minimum, your CI build will fail.

Note: coverage_dir should be modified to public/coverage. Else you cannot access the html publically.

3. Run Your Tests & View the Report

โœ— bin/rails test
โ‰ˆ tailwindcss v4.1.3

Done in 46ms
Running 10 tests in a single process (parallelization threshold is 50)
Run options: --seed 63363

# Running:

..........

Finished in 0.563707s, 17.7397 runs/s, 60.3150 assertions/s.
10 runs, 34 assertions, 0 failures, 0 errors, 0 skips
Coverage report generated for Minitest to /Users/abhilash/rails/design_studio/public/coverage.
Line Coverage: 78.57% (88 / 112)
Line coverage (78.57%) is below the expected minimum coverage (90.00%).
SimpleCov failed with exit 2 due to a coverage related error

Once tests complete, open http://localhost:3000/coverage/index.html#_AllFiles in your browser:

  • A color-coded report shows covered (green) vs. missed (red) lines.
  • Drill down by file or group to identify untested code.

We get 78.57% only coverage and our target is 90% coverage. Let’s check where we missed the tests. ProductsController 82%. We missed coverage for #delete_image action. Let’s add it and check again.

Let’s add Product Controller json requests test cases for json error response and add the ApplicationControllerTest for testing root path.

Now we get: 88.3%

Now we have to add some Test cases for Product model.

Now we get: 92.86% โœ…

4. Enforce in CI

In your CI pipeline (e.g. GitHub Actions), ensure:

- name: Run tests with coverage
  run: |
    bundle exec rails test
    # Optionally upload coverage to Coveralls or Codecov

If coverage < threshold, the job will exit non-zero and fail.


Part 2: Incorporating Brakeman for Security Analysis

1. Add Brakeman to Your Development Stack

You can install Brakeman as a gem (development-only) or run it via Docker/CLI. Hereโ€™s the gem approach:

group :development do
  gem 'brakeman', require: false
end

Then:

bundle install

2. Basic Usage

From your project root, simply run:

โœ— bundle exec brakeman

Generating report...

== Brakeman Report ==

Application Path: /Users/abhilash/rails/design_studio
Rails Version: 8.0.2
Brakeman Version: 7.0.2
Scan Date: 2025-05-07 11:06:36 +0530
Duration: 0.35272 seconds
Checks Run: BasicAuth, BasicAuthTimingAttack, CSRFTokenForgeryCVE, ....., YAMLParsing

== Overview ==

Controllers: 2
Models: 3
Templates: 12
Errors: 0
Security Warnings: 0

== Warning Types ==


No warnings found

By default, Brakeman:

  • Scans app/, lib/, config/, etc.
  • Outputs a report in the terminal and writes brakeman-report.html.

3. Customize Your Scan

Create a config/brakeman.yml to fine-tune:

ignored_files:
  - 'app/controllers/legacy_controller.rb'
checks:
  - mass_assignment
  - cross_site_scripting
  - sql_injection
skip_dev: true                 # ignores dev-only gems
quiet: true                     # suppress verbose output

Run with:

bundle exec brakeman -c config/brakeman.yml -o public/security_report.html

Theconfig/brakeman.yml file is not added by default. You can add the file by copying the contents from: https://gist.github.com/abhilashak/038609f1c35942841ff8aa5e4c88b35b

Check: http://localhost:3000/security_report.html

4. CI Integration

In GitHub Actions:

- name: Run Brakeman security scan
  run: |
    bundle exec brakeman -q -o brakeman.json
- name: Upload Brakeman report
  uses: actions/upload-artifact@v3
  with:
    name: security-report
    path: brakeman.json

Optionally, you can fail the build if new warnings are introduced by comparing against a baseline report.


Advantages of Using SimpleCov & Brakeman Together

AspectSimpleCovBrakeman
PurposeTest coverage metricsStatic security analysis
Fail-fastFails when coverage drops below thresholdCan be configured to fail on new warnings
VisibilityColorized HTML coverage reportDetailed HTML/JSON vulnerability report
CI/CD ReadyIntegrates seamlessly with most CI systemsCLI-friendly, outputs machine-readable data
CustomizableGroups, filters, thresholdsChecks selection, ignored files, baseline

Together, they cover two critical quality dimensions:

  1. Quality & Maintainability (via testing)
  2. Security & Compliance (via static analysis)

Automating both checks in your pipeline means faster feedback, fewer production issues, and higher confidence when shipping code.


Best Practices & Tips

  • Threshold for SimpleCov: Start with 80%, then gradually raise to 90โ€“95% over time.
  • Treat Brakeman Warnings Seriously: Not all findings are exploitable, but donโ€™t ignore themโ€”triage and document why youโ€™re suppressing any warning.
  • Baseline Approach: Use a baseline report for Brakeman so your build only fails on newly introduced warnings, not historical ones.
  • Schedule Periodic Full Scans: In addition to per-PR scans, run a weekly scheduled Brakeman job to catch issues from merged code.
  • Combine with Other Tools: Consider adding gem like bundler-audit for known gem vulnerabilities.

Conclusion

By integrating SimpleCov and Brakeman into your Rails 8 app, you establish a robust safety net that:

  • Ensures new features are properly tested
  • Keeps an eye on security vulnerabilities
  • Automates quality gates in your CI/CD pipeline

These tools are straightforward to configure and provide immediate benefits – improved code confidence, faster code reviews, and fewer surprises in production. Start today, and make code quality and security first-class citizens in your Rails workflow!

Happy Rails CI/CD Integration .. ๐Ÿš€

Setup ๐Ÿ› ย Rails 8 App โ€“ Part 10: PostgreSQL Into The Action

For a Ruby on Rails 8 application, the choice of database depends on your specific needs, but hereโ€™s a breakdown of the best options and when to use each:

PostgreSQL (Highly Recommended)

Best overall choice for most Rails apps.

Why:

  • First-class support in Rails.
  • Advanced features like full-text search, JSONB support, CTEs, window functions.
  • Strong consistency and reliability.
  • Scales well vertically and horizontally (with tools like Citus).
  • Used by: GitHub, Discourse, Basecamp, Shopify.

Use if:

  • Youโ€™re building a standard Rails web app or API.
  • You need advanced query features or are handling complex data types (e.g., JSON).

SQLite (For development/testing only)

  • Lightweight, file-based.
  • Fast and easy to set up.
  • But not recommended for production.

Use if:

  • Youโ€™re building a quick prototype or local dev/testing app.
  • NOT for multi-user production environments.

MySQL / MariaDB

  • Also supported by Rails.
  • Can work fine for simpler applications.
  • Lacks some advanced features (like robust JSON support or full Postgres-style indexing).
  • Not the default in many modern Rails setups.

Use if:

  • Your team already has MySQL infrastructure or legacy systems.
  • You need horizontal scaling with Galera Cluster or similar setups.

Others (NoSQL like MongoDB, Redis, etc.)

  • Use Redis for caching and background job data (not as primary DB).
  • Use MongoDB or other NoSQL only if your data model really demands it (e.g., unstructured documents, event sourcing).

Recommendation Summary:

Use CaseRecommended DB
Production web/API appPostgreSQL
Dev/prototyping/local testingSQLite
Legacy systems/MySQL infrastructureMySQL/MariaDB
Background jobs/cachingRedis
Special needs (e.g., documents)MongoDB (with caution)

If you’re starting fresh or building something scalable and modern with Rails 8, go with PostgreSQL.

Letโ€™s break that down:

๐Ÿ’ฌ What does “robust JSON support” mean?

PostgreSQL supports a special column type: json and jsonb, which lets you store structured JSON data directly in your database โ€” like hashes or objects.

Why it matters:

  • You can store dynamic data without needing to change your schema.
  • You can query inside the JSON using SQL (->, ->>, @>, etc.).
  • You can index parts of the JSON โ€” for speed.

๐Ÿ”ง Example:

You have a products table with a specs column that holds tech specs in JSON:

specs = {
  "color": "black",
  "brand": "Libas",
  "dimensions": {"chest": "34", "waist": "30", "shoulder": "13.5"}
}

You can query like:

SELECT * FROM products WHERE specs->>'color' = 'black';

Or check if the JSON contains a value:

SELECT * FROM products WHERE specs @> '{"brand": "Libas"}';

You can even index specs->>'color' to make these queries fast.


๐Ÿ’ฌ What does “full Postgres-style indexing” mean?

PostgreSQL supports a wide variety of powerful indexing options, which improve query performance and flexibility.

โš™๏ธ Types of Indexes PostgreSQL supports:

Index TypeUse Case
B-TreeDefault; used for most equality and range searches
GIN (Generalized Inverted Index)Fast indexing for JSON, arrays, full-text search
Partial IndexesIndex only part of the data (e.g., WHERE active = true)
Expression IndexesIndex a function or expression (e.g., LOWER(email))
Covering Indexes (INCLUDE)Fetch data directly from the index, avoiding table reads
  • B-Tree Indexes: B-tree indexes are more suitable for single-value columns.
  • When to Use GIN Indexes: When you frequently search for specific elements within arrays, JSON documents, or other composite data types.
  • Example for GIN Indexes: Imagine you have a table with a JSONB column containing document metadata. A GIN index on this column would allow you to quickly find all documents that have a specific author or belong to a particular category. 

Why does this matter for our shopping app?

  • We can store and filter products with dynamic specs (e.g., kurtas, shorts, pants) without new columns.
  • Full-text search on product names/descriptions.
  • Fast filters: color = 'red' AND brand = 'Libas' even if those are stored in JSON.
  • Index custom expressions like LOWER(email) for case-insensitive login.

๐Ÿ’ฌ What are Common Table Expressions (CTEs)?

CTEs are temporary result sets you can reference within a SQL query โ€” like defining a mini subquery that makes complex SQL easier to read and write.

WITH recent_orders AS (
  SELECT * FROM orders WHERE created_at > NOW() - INTERVAL '7 days'
)
SELECT * FROM recent_orders WHERE total > 100;

  • Breaking complex queries into readable parts.
  • Re-using result sets without repeating subqueries.
In Rails (via with from gems like scenic or with_cte):
Order
  .with(recent_orders: Order.where('created_at > ?', 7.days.ago))
  .from('recent_orders')
  .where('total > ?', 100)

๐Ÿ’ฌ What are Window Functions?

Window functions perform calculations across rows related to the current row โ€” unlike aggregate functions, they donโ€™t group results into one row.

๐Ÿ”ง Example: Rank users by their score within each team:
SELECT
  user_id,
  team_id,
  score,
  RANK() OVER (PARTITION BY team_id ORDER BY score DESC) AS rank
FROM users;
Use cases:
  • Ranking rows (like leaderboards).
  • Running totals or moving averages.
  • Calculating differences between rows (e.g. โ€œHow much did this order increase from the last?โ€).
๐Ÿ›ค In Rails:

Window functions are available through raw SQL or Arel. Here’s a basic example:

User
  .select("user_id, team_id, score, RANK() OVER (PARTITION BY team_id ORDER BY score DESC) AS rank")

CTEs and Window functions are fully supported in PostgreSQL, making it the go-to DB for any Rails 8 app that needs advanced querying.

JSONB Support

JSONB stands for “JSON Binary” and is a binary representation of JSON data that allows for efficient storage and retrieval of complex data structures.

This can be useful when you have data that doesn’t fit neatly into traditional relational database tables, such as nested or variable-length data structures.

Absolutely โ€” storing JSON in a relational database (like PostgreSQL) can be super powerful when used wisely. It gives you schema flexibility without abandoning the structure and power of SQL. Here are real-world use cases for using JSON columns in relational databases:

Here are real-world use cases for using JSON columns in relational databases:

๐Ÿ”ง 1. Flexible Metadata / Extra Attributes

Let users store arbitrary attributes that don’t require schema changes every time.

Use case: Product variants, custom fields

t.jsonb :metadata

{
  "color": "red",
  "size": "XL",
  "material": "cotton"
}

=> Good when:

  • You can’t predict all the attributes users will need.
  • You donโ€™t want to create dozens of nullable columns.

๐ŸŽ›๏ธ 2. Storing Settings or Preferences

User or app settings that vary a lot.

Use case: Notification preferences, UI layout, feature toggles

{
  "email": true,
  "sms": false,
  "theme": "dark"
}

=> Easy to store and retrieve as a blob without complex joins.

๐ŸŒ 3. API Response Caching

Store external API responses for caching or auditing.

Use case: Storing Stripe, GitHub, or weather API responses.

t.jsonb :api_response

=> Avoids having to map every response field into a column.

๐Ÿ“ฆ 4. Storing Logs or Events

Use case: Audit trails, system logs, user events

{
  "action": "login",
  "timestamp": "2025-04-18T10:15:00Z",
  "ip": "123.45.67.89"
}

=> Great for capturing varied data over time without a rigid schema.

๐Ÿ“Š 6. Embedded Mini-Structures

Use case: A form builder app storing user-created forms and fields.

{
  "fields": [
    { "type": "text", "label": "Name", "required": true },
    { "type": "email", "label": "Email", "required": false }
  ]
}

=> When each row can have nested, structured data โ€” almost like a mini-document.

๐Ÿ•น๏ธ 7. Device or Browser Info (User Agents)

Use case: Analytics, device fingerprinting

{
  "browser": "Safari",
  "os": "macOS",
  "version": "17.3"
}

=> You donโ€™t need to normalize or query this often โ€” perfect for JSON.


JSON vs JSONB in PostgreSQL

Use jsonb over json unless you need to preserve order or whitespace.

  • jsonb is binary format โ†’ faster and indexable
  • You can do fancy stuff like:
SELECT * FROM users WHERE preferences ->> 'theme' = 'dark';

Or in Rails:

User.where("preferences ->> 'theme' = ?", 'dark')

store and store_accessor

They let you treat JSON or text-based hash columns like structured data, so you can access fields as if they were real database columns.

๐Ÿ”น store

  • Used to declare a serialized store (usually a jsonb, json, or text column) on your model.
  • Works best with key/value stores.

๐Ÿ‘‰ Example:

Letโ€™s say your users table has a settings column of type jsonb:

# migration
add_column :users, :settings, :jsonb, default: {}

Now in your model:

class User < ApplicationRecord
  store :settings, accessors: [:theme, :notifications], coder: JSON
end

You can now do this:

user.theme = "dark"
user.notifications = true
user.save

user.settings
# => { "theme" => "dark", "notifications" => true }

๐Ÿ”น store_accessor

A lightweight version that only declares attribute accessors for keys inside a JSON column. Doesnโ€™t include serialization logic โ€” so you usually use it with a json/jsonb/text column that already works as a Hash.

๐Ÿ‘‰ Example:

class User < ApplicationRecord
  store_accessor :settings, :theme, :notifications
end

This gives you:

  • user.theme, user.theme=
  • user.notifications, user.notifications=
๐Ÿค” When to Use Each?
FeatureWhen to Use
storeWhen you need both serialization and accessors
store_accessorWhen your column is already serialized (jsonb, etc.)

If you’re using PostgreSQL with jsonb columns โ€” it’s more common to just use store_accessor.

Querying JSON Fields
User.where("settings ->> 'theme' = ?", "dark")

Or if you’re using store_accessor:

User.where(theme: "dark")

๐Ÿ’ก But remember: youโ€™ll only be able to query these fields efficiently if youโ€™re using jsonb + proper indexes.


๐Ÿ”ฅ Conclusion:

  • PostgreSQL can store, search, and index inside JSON fields natively.
  • This lets you keep your schema flexible and your queries fast.
  • Combined with its advanced indexing, itโ€™s ideal for a modern e-commerce app with dynamic product attributes, filtering, and searching.

To install and set up PostgreSQL on macOS, you have a few options. The most common and cleanest method is using Homebrew. Hereโ€™s a step-by-step guide:

Setup ๐Ÿ› ย Rails 8 App โ€“ Part 9: Setup โš™๏ธ CI/CD with GitHub Actions | Run Test Cases via VS Code Co-pilot

Switching to a feature-branch workflow with pull requests is a great move for team collaboration, code review, and better CI/CD practices. Here’s how you can transition our Rails 8 app to a proper CI/CD pipeline using GitHub and GitHub Actions.

๐Ÿ”„ Workflow Change: Feature Branch + Pull Request

1. Create a new branch for each feature/task:
git checkout -b feature/feature-name
2. Push it to GitHub:
git push origin feature/feature-name
3. Open a Pull Request on GitHub from feature/feature-name to main.
4. Enable branch protection (optional but recommended):

Note: You can set up branch protection rules in GitHub for free only on public repositories.

About protected branches

You can protect important branches by setting branch protection rules, which define whether collaborators can delete or force push to the branch and set requirements for any pushes to the branch, such as passing status checks or a linear commit history.

You can create a branch protection rule in a repository for a specific branch, all branches, or any branch that matches a name pattern you specify with fnmatch syntax. For example, to protect any branches containing the word release, you can create a branch rule for *release*

  • Go to your repo โ†’ Settings โ†’ Branches โ†’ Protect main.
  • Require pull request reviews before merging.
  • Require status checks to pass before merging (CI tests).

Check the link in your github account: https://github.com/<user-name>/<repo-name>/settings/branch_protection_rules/new

For creating the branch protection rules, you need to take the github business account OR Move your work into an organization (https://github.com/account/organizations/new).

GitHub Actions

Basically github actions allow us to run some actions (ex: testing the code) if an event occurs during the code changes/commit/push (it mostly related to a branch).

Our Goal: When we push to a feature branch test the code before merging it to the main branch so that we can ensure nothing is broken before going the code into live.

You can try the VS Code plugin for helping the Github Actions workflow (best for auto-complete the data we needed and auto-populate the env variables etc from our github account):

Sign in using your github account and grant access to the public repositories.

If you try to push to main branch, you will find the following error:

remote: error: GH006: Protected branch update failed for refs/heads/main.
remote:
remote: - Changes must be made through a pull request.
remote:
remote: - Cannot change this locked branch
To github.com:<username>/<project>.git
 ! [remote rejected] main -> main (protected branch hook declined)

We will be finishing Database and all other setup for our Web Application before starting CI/CD setup.

For the next part of CI/CD configuration check the post: https://railsdrop.com/2025/05/06/rails-8-ci-cd-setup-with-github-actions/

Let’s Start to Use VS Code Co-pilot For Test Creation/Execution

Test cases are Important for CI/CD setup. Our main focus will be running Rails test cases when integrating CI.

  • Generate Test using Co-pilot From Controller
  • Co-pilot Creates Tests
  • Co-pilot run Tests

  • Use Co-pilot to Fix Test Failures
  • Test Results: Pending Migrations
  • Test Success: After Migration
  • VS Code: Check ruby Lint Symbol for details
  • VS Code try to run Tests: Rubocop Path Issue
  • Fixed Rubocop Issue: All Test passes

Profiling ๐Ÿ“Š Ruby on Rails 8 Applications: Essential Tools and Techniques

Introduction

Performance optimization is critical for delivering fast, responsive Rails applications. This comprehensive guide covers the most important profiling tools you should implement in your Rails 8 application, complete with setup instructions and practical examples.

Why Profiling Matters

Before diving into tools, let’s understand why profiling is essential:

  1. Identify bottlenecks: Pinpoint exactly which parts of your application are slowing things down
  2. Optimize resource usage: Reduce memory consumption and CPU usage
  3. Improve user experience: Faster response times lead to happier users
  4. Reduce infrastructure costs: Efficient applications require fewer server resources

Essential Profiling Tools for Rails 8

1. Rack MiniProfiler

What it does: Provides real-time profiling of your application’s performance directly in your browser.

Why it’s important: It’s the quickest way to see performance metrics without leaving your development environment.

Installation:

# Gemfile
gem 'rack-mini-profiler', group: :development

Usage example:
After installation, it automatically appears in your browser’s corner showing:

  • SQL query times
  • Ruby execution time
  • Memory allocation
  • Flamegraphs (with additional setup)

Advantages:

  • No configuration needed for basic setup
  • Shows N+1 query warnings
  • Integrates with Rails out of the box

GitHubhttps://github.com/MiniProfiler/rack-mini-profiler
Documentationhttps://miniprofiler.com/

2. Bullet

What it does: Detects N+1 queries, unused eager loading, and missing counter caches.

Why it’s important: N+1 queries are among the most common performance issues in Rails applications.

Installation:

# Gemfile
gem 'bullet', group: :development

Configuration:

# config/environments/development.rb
config.after_initialize do
  Bullet.enable = true
  Bullet.alert = true
  Bullet.bullet_logger = true
  Bullet.console = true
  Bullet.rails_logger = true
end

Example output:

GET /posts
USE eager loading detected
  Post => [:comments]
  Add to your query: Post.includes([:comments])

Advantages:

  • Catches common ORM performance issues early
  • Provides specific recommendations for fixes
  • Works across all environments

GitHubhttps://github.com/flyerhzm/bullet
Documentationhttps://github.com/flyerhzm/bullet/blob/master/README.md

3. Ruby Prof (and StackProf)

What it does: Low-level Ruby code profiler that shows exactly where time is being spent.

Why it’s important: When you need deep insight into method-level performance characteristics.

Installation:

# Gemfile
gem 'ruby-prof', group: :development
gem 'stackprof', group: :development

Usage example:

# In your controller or service object
result = RubyProf.profile do
  # Code you want to profile
end

printer = RubyProf::GraphPrinter.new(result)
printer.print(STDOUT, {})

For StackProf:

StackProf.run(mode: :cpu, out: 'tmp/stackprof.dump') do
  # Code to profile
end

Advantages:

  • Method-level granularity
  • Multiple output formats (call graphs, flamegraphs)
  • StackProf is sampling-based so has lower overhead

GitHubhttps://github.com/ruby-prof/ruby-prof
Documentationhttps://github.com/ruby-prof/ruby-prof/blob/master/README.md

StackProf Alternative:
GitHubhttps://github.com/tmm1/stackprof
Documentationhttps://github.com/tmm1/stackprof/blob/master/README.md

4. Memory Profiler

What it does: Tracks memory allocations and helps identify memory bloat.

Why it’s important: Memory issues can lead to slow performance and even crashes.

Installation:

# Gemfile
gem 'memory_profiler', group: :development

Usage example:

report = MemoryProfiler.report do
  # Code to profile
end

report.pretty_print(to_file: 'memory_report.txt')

Advantages:

  • Shows allocated objects by class and location
  • Tracks retained memory after GC
  • Helps find memory leaks

GitHubhttps://github.com/SamSaffron/memory_profiler
Documentationhttps://github.com/SamSaffron/memory_profiler/blob/master/README.md

5. Skylight

What it does: Production-grade application performance monitoring (APM).

Why it’s important: Understanding real-world performance characteristics is different from development profiling.

Installation:

# Gemfile
gem 'skylight'

Configuration:

# config/skylight.yml
production:
  authentication: [YOUR_AUTH_TOKEN]

Advantages:

  • Low-overhead production profiling
  • Endpoint-level performance breakdowns
  • Database query analysis
  • Exception tracking

Websitehttps://www.skylight.io
Documentationhttps://docs.skylight.io
GitHubhttps://github.com/skylightio/skylight-ruby

6. AppSignal

What it does: Full-stack performance monitoring and error tracking.

Why it’s important: Provides comprehensive insights across your entire application stack.

Installation:

# Gemfile
gem 'appsignal'

Then run:

bundle exec appsignal install YOUR_PUSH_API_KEY

Advantages:

  • Error tracking alongside performance
  • Host metrics integration
  • Background job monitoring
  • Magic Dashboard for quick insights

Websitehttps://appsignal.com
Documentationhttps://docs.appsignal.com/ruby
GitHubhttps://github.com/appsignal/appsignal-ruby

7. Derailed Benchmarks

What it does: Suite of benchmarks and performance tests for your application.

Why it’s important: Helps catch performance regressions before they hit production.

Installation:

# Gemfile
group :development, :test do
  gem 'derailed_benchmarks'
end

Usage examples:

# Memory usage at boot
bundle exec derailed bundle:mem

# Performance per route
bundle exec derailed exec perf:routes

Advantages:

  • CI-friendly performance testing
  • Memory usage analysis
  • Route-based performance testing

GitHubhttps://github.com/schneems/derailed_benchmarks
Documentationhttps://github.com/schneems/derailed_benchmarks/blob/master/README.md

8. Flamegraph Generation

What it does: Visual representation of where time is being spent in your application.

Why it’s important: Provides an intuitive way to understand call stacks and hot paths.

Installation:

# Gemfile
gem 'flamegraph'
gem 'stackprof' # if not already installed

Usage example:

Flamegraph.generate('flamegraph.html') do
  # Code to profile
end

Advantages:

  • Visual representation of performance
  • Easy to spot hot paths
  • Interactive exploration

GitHubhttps://github.com/SamSaffron/flamegraph
Documentationhttp://samsaffron.github.io/flamegraph/rails-startup.html

Additional Helpful Tools ๐Ÿ”ง

9. Benchmark-ips

Benchmark-ips (iterations per second) is a superior benchmarking tool compared to Ruby’s standard Benchmark library. It provides:

  1. Iterations-per-second measurement – More intuitive than raw time measurements
  2. Statistical analysis – Shows standard deviation between runs
  3. Comparison mode – Easily compare different implementations
  4. Warmup phase – Accounts for JIT and cache warming effects

Benchmark-ips solves these problems and is particularly valuable for:

  • Comparing algorithm implementations
  • Testing performance optimizations
  • Benchmarking gem alternatives
  • Validating performance-critical code

GitHubhttps://github.com/evanphx/benchmark-ips
Documentationhttps://github.com/evanphx/benchmark-ips/blob/master/README.md

Installation
# Gemfile
gem 'benchmark-ips', group: :development
Basic Usage:
require 'benchmark/ips'

Benchmark.ips do |x|
  x.report("addition") { 1 + 2 }
  x.report("addition with to_s") { (1 + 2).to_s }
  x.compare!
end
Advanced Features:
Benchmark.ips do |x|
  x.time = 5 # Run each benchmark for 5 seconds
  x.warmup = 2 # Warmup time of 2 seconds
  
  x.report("Array#each") { [1,2,3].each { |i| i * i } }
  x.report("Array#map") { [1,2,3].map { |i| i * i } }
  
  # Add custom statistics
  x.config(stats: :bootstrap, confidence: 95)
  
  x.compare!
end
# Memory measurement
require 'benchmark/memory'

Benchmark.memory do |x|
  x.report("method1") { ... }
  x.report("method2") { ... }
  x.compare!
end

# Disable GC for more consistent results
Benchmark.ips do |x|
  x.config(time: 5, warmup: 2, suite: GCSuite.new)
end
Sample Output:
Warming up --------------------------------------
            addition    281.899k i/100ms
  addition with to_s    261.831k i/100ms
Calculating -------------------------------------
            addition      8.614M (ยฑ 1.2%) i/s -     43.214M in   5.015800s
  addition with to_s      7.017M (ยฑ 1.8%) i/s -     35.347M in   5.038446s

Comparison:
            addition:  8613594.0 i/s
  addition with to_s:  7016953.3 i/s - 1.23x slower

Key Advantages

  1. Accurate comparisons with statistical significance
  2. Warmup phase eliminates JIT/caching distortions
  3. Memory measurements available through extensions
  4. Customizable reporting with various statistics options

10. Rails Performance (Dashboard)

What is Rails Performance?

Rails Performance is a self-hosted alternative to New Relic/Skylight that provides:

  1. Request performance tracking
  2. Background job monitoring
  3. Slowest endpoints identification
  4. Error tracking
  5. Custom event monitoring
Why It’s Important

For teams that:

  • Can’t use commercial SaaS solutions
  • Need to keep performance data in-house
  • Want historical performance tracking
  • Need simple setup without complex infrastructure

GitHubhttps://github.com/igorkasyanchuk/rails_performance
Documentationhttps://github.com/igorkasyanchuk/rails_performance/blob/master/README.md

Installation
# Gemfile
gem 'rails_performance', group: :development

Then run:

rails g rails_performance:install
rake db:migrate
Configuration
# config/initializers/rails_performance.rb
RailsPerformance.setup do |config|
  config.redis = Redis.new # optional, will use Rails.cache otherwise
  config.duration = 4.hours # store requests for 4 hours
  config.enabled = Rails.env.production?
  config.http_basic_authentication_enabled = true
  config.http_basic_authentication_user_name = 'admin'
  config.http_basic_authentication_password = 'password'
end
Accessing the Dashboard:

After installation, access the dashboard at:

http://localhost:3000/rails/performance

Custom Tracking:

# Track custom events
RailsPerformance.trace("custom_event", tags: { type: "import" }) do
  # Your code here
end

# Track background jobs
class MyJob < ApplicationJob
  around_perform do |job, block|
    RailsPerformance.trace(job.class.name, tags: job.arguments) do
      block.call
    end
  end
end
# Add custom fields to requests
RailsPerformance.attach_extra_payload do |payload|
  payload[:user_id] = current_user.id if current_user
end

# Track slow queries
ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
  event = ActiveSupport::Notifications::Event.new(*args)
  if event.duration > 100 # ms
    RailsPerformance.trace("slow_query", payload: {
      sql: event.payload[:sql],
      duration: event.duration
    })
  end
end
Sample Dashboard Views:
  1. Requests Overview:
    • Average response time
    • Requests per minute
    • Slowest actions
  2. Detailed Request View:
    • SQL queries breakdown
    • View rendering time
    • Memory allocation
  3. Background Jobs:
    • Job execution time
    • Failures
    • Queue times
Key Advantages
  1. Self-hosted solution – No data leaves your infrastructure
  2. Simple setup – No complex dependencies
  3. Historical data – Track performance over time
  4. Custom events – Track any application events
  5. Background jobs – Full visibility into async processes

Implementing a Complete Profiling Strategy

For a comprehensive approach, combine these tools at different stages:

  1. Development:
  • Rack MiniProfiler (always on)
  • Bullet (catch N+1s early)
  • RubyProf/StackProf (for deep dives)
  1. CI Pipeline:
  • Derailed Benchmarks
  • Memory tests
  1. Production:
  • Skylight or AppSignal
  • Error tracking with performance context

Sample Rails 8 Configuration

Here’s how to set up a complete profiling environment in a new Rails 8 app:

# Gemfile

# Development profiling
group :development do
  # Basic profiling
  gem 'rack-mini-profiler'
  gem 'bullet'
  
  # Deep profiling
  gem 'ruby-prof'
  gem 'stackprof'
  gem 'memory_profiler'
  gem 'flamegraph'
  
  # Benchmarking
  gem 'derailed_benchmarks', require: false
  gem 'benchmark-ips'
  
  # Dashboard
  gem 'rails_performance'
end

# Production monitoring (choose one)
group :production do
  gem 'skylight'
  # or
  gem 'appsignal'
  # or
  gem 'newrelic_rpm' # Alternative option
end

Then create an initializer for development profiling:

# config/initializers/profiling.rb
if Rails.env.development?
  require 'rack-mini-profiler'
  Rack::MiniProfilerRails.initialize!(Rails.application)

  Rails.application.config.after_initialize do
    Bullet.enable = true
    Bullet.alert = true
    Bullet.bullet_logger = true
    Bullet.rails_logger = true
  end
end

Conclusion

Profiling your Rails 8 application shouldn’t be an afterthought. By implementing these tools throughout your development lifecycle, you’ll catch performance issues early, maintain a fast application, and provide better user experiences.

Remember:

  • Use development tools like MiniProfiler and Bullet daily
  • Run deeper profiles with RubyProf before optimization work
  • Monitor production with Skylight or AppSignal
  • Establish performance benchmarks with Derailed

With this toolkit, you’ll be well-equipped to build and maintain high-performance Rails 8 applications.

Enjoy Rails! ๐Ÿš€

Setup ๐Ÿ› ย Rails 8 App – Part 8: Debugbar – Apply performance ๐Ÿ“ˆ optimization

1. Integrate pagy for pagination

Why it’s the great choice:

  • Super fast and lightweight (~300x faster than Kaminari or WillPaginate).
  • No dependencies on Active Record or view helpers.
  • Very customizable and modular (can do Bootstrap/Tailwind/semantic UI integrations).
  • Supports metadata, responsive pagination, overflow handling, infinite scrolling, and JSON API pagination.
# Gemfile
# The Best Pagination Ruby Gem [https://ddnexus.github.io/pagy/]
gem "pagy", "~> 9.3" # omit patch digit

bundle install
Example Usage in Controller:
include Pagy::Backend

def index
  @pagy, @products = pagy(Product.all)
end

In Product Helper / Application Helper:
include Pagy::Frontend
In the View (ERB or HAML):
<%= pagy_nav(@pagy) %>
Add an initializer file

Download the file from: https://ddnexus.github.io/pagy/quick-start/

https://ddnexus.github.io/pagy/gem/config/pagy.rb

and save it into the config/initializers directory. Uncomment limit and size options.

Tailwind Support:
# In an initializer (e.g., config/initializers/pagy.rb)
Pagy::DEFAULT[:limit]       = 20                    # default
Pagy::DEFAULT[:size]        = 7                     # default
# Better user experience handled automatically
require "pagy/extras/overflow"
Pagy::DEFAULT[:overflow] = :last_page

I am getting a load error when I want tailwind css to apply to my views:

LoadError: cannot load such file -- pagy/extras/tailwind (LoadError)

Ahh it’s not supporting Tailwind CSS, and there is no tailwind file found in the Gem too!

Hmm..๐Ÿ˜Ÿ Check below:

We can try to include the css manually, check: https://ddnexus.github.io/pagy/docs/api/stylesheets/#pagy-tailwind-css

Create a file pagy.tailwind.css and add the following:

.pagy {
    @apply flex space-x-1 font-semibold text-sm text-gray-500;
    a:not(.gap) {
      @apply block rounded-lg px-3 py-1 bg-gray-200;
      &:hover {
        @apply bg-gray-300;
      }
      &:not([href]) { /* disabled links */
        @apply text-gray-300 bg-gray-100 cursor-default;
      }
      &.current {
        @apply text-white bg-gray-400;
      }
    }
    label {
      @apply inline-block whitespace-nowrap bg-gray-200 rounded-lg px-3 py-0.5;
      input {
        @apply bg-gray-100 border-none rounded-md;
      }
    }
  }

Modify app/assets/tailwind/application.css :

@import "tailwindcss";
@import "./pagy.tailwind.css";

Restart your server and you got it!

Testing performance

You can see that in the query Tab in Debugbar, select * from products query has been replaced with limit query. But this is not the case where you go through the entire thousand hundreds of products, for example searching. We can think of view caching and SQL indexing for such a situation.

to be continued..ย ๐Ÿš€

Understanding Confusing ๐Ÿง Ruby Concepts: Procfile, Rake, Rack, and More

Ruby has several terms that sound similar but serve different purposes. If youโ€™ve ever been confused by things like Procfile, Rakefile, Rack, and Rake, this guide will clarify them all. Plus, we’ll cover additional tricky concepts you might have overlooked!

1. Procfile

What is it?

A Procfile is a text file used in deployment environments (like Heroku and Kamal) to specify how your application should be started.

Where is it used?

Platforms like Heroku, Kamal, and Foreman use Procfile to define process types (like web servers and workers).

Example:

web: bundle exec puma -C config/puma.rb
worker: bundle exec sidekiq

  • web: Starts the Puma web server.
  • worker: Runs background jobs using Sidekiq.

Check the post for details (Foreman): https://railsdrop.com/2025/03/26/setup-rails-8-app-part-4-tailwind-css-into-the-action/

2. Rake and Rakefile

What is Rake?

Rake is a task management tool for automating scripts in Ruby applications. It’s like Makefile but written in Ruby.

What is a Rakefile?

A Rakefile is a Ruby file where Rake tasks are defined.

Check Rails::Railtie.rake_tasks for more info.

Where is it used?

  • Rails applications (for tasks like database migrations and data seeding)
  • Standalone Ruby applications (for automating scripts)

Common Rake Commands in Rails:

rake db:migrate    # Run database migrations
rake db:seed       # Seed the database
rake routes        # Show all available routes

Example Custom Rake Task:

Create a file at lib/tasks/custom.rake:

namespace :custom do
  desc "Prints a greeting"
  task hello: :environment do
    puts "Hello from custom Rake task!"
  end
end

Run it with:

rake custom:hello

3. RackWhat is it?

Rack is a lightweight interface between Ruby web applications and web servers. It provides a simple way to handle HTTP requests and responses.

https://github.com/rack/rack

Checkout Rack in more detail: https://railsdrop.com/2025/04/07/inside-rails-the-role-of-rack-and-middleware/

4. Adding Middleware in a Rails 8 App

Checkout the post: https://railsdrop.com/2025/04/07/inside-rails-the-role-of-rack-and-middleware/

5. Other Confusing Ruby Concepts You Should Know

Gemfile vs. Gemspec

  • Gemfile: Defines dependencies for a Rails project (uses Bundler).
  • Gemspec: Defines dependencies and metadata for a Ruby gem.

Lambda vs. Proc

Both are used for defining anonymous functions, but behave differently:

lambda_example = -> { return "Lambda returns from itself" }
proc_example = Proc.new { return "Proc returns from the enclosing method" }

Safe Navigation Operator (&.)

user&.profile&.name  # Avoids NoMethodError if user or profile is nil

Symbol vs. String

:my_symbol  # Immutable, faster lookup
"my_string" # Mutable, slower lookup

&: Shortcut for Blocks

Ruby allows a shorthand syntax for passing methods as blocks using &:.

["hello", "world"].map(&:upcase)  # => ["HELLO", "WORLD"]

Equivalent to:

["hello", "world"].map { |word| word.upcase }

Single Splat (*) Operator

The * operator is used for handling variable-length arguments in methods.

def sum(*numbers)
  numbers.reduce(:+)
end

puts sum(1, 2, 3, 4)  # Output: 10

It can also be used for array expansion (spreads out Arrays):

arr = [1, 2, 3, 4]
> puts *arr
1
2
3
4
=> nil

odds = [3, 5, 7, 9]
puts *odds
>
3
5
7
9
=> nil

first_odd, *rest = odds
> puts rest
5
7
9
=> nil

We can also insert array elements into another Array. In the example below, odds elements are added to the numbers Array, starting from the position where *odds is called.

odds = [3, 5, 7, 9]
numbers = [1, 2, *odds, 10]
puts "numbers: #{numbers}"

# =>
# numbers: [1, 2, 3, 5, 7, 9, 10]

Double Splat (**) in Method Arguments

The ** operator is used to capture keyword arguments.

def greet(name:, **options)
  puts "Hello, #{name}!"
  puts "Options: #{options}"
end

greet(name: "Alice", age: 25, city: "New York")
# Output:
# Hello, Alice!
# Options: {:age=>25, :city=>"New York"}

What Are Keyword Arguments (kwargs) in Ruby?

(name:) in greet is an example of a keyword argument (kwargs).

Keyword arguments allow you to pass arguments to a method using explicit parameter names, making the code more readable and flexible.

Example: Using a Required Keyword Argument
def greet(name:)
  puts "Hello, #{name}!"
end

greet(name: "Alice")  # Output: Hello, Alice!
  • The name: argument must be provided, otherwise, Ruby will raise an error.
Example: Using Optional Keyword Arguments

You can provide default values for keyword arguments:

def greet(name: "Guest")
  puts "Hello, #{name}!"
end

greet        # Output: Hello, Guest!
greet(name: "Bob")  # Output: Hello, Bob!
Example: Combining Required and Optional Keyword Arguments
def greet(name:, age: nil)
  puts "Hello, #{name}!"
  puts "You are #{age} years old." if age
end

greet(name: "Alice", age: 25)
# Output:
# Hello, Alice!
# You are 25 years old.
Example: Capturing Extra Keyword Arguments with **options

The ** operator captures any additional keyword arguments passed to the method into a hash.

def greet(name:, **options)
  puts "Hello, #{name}!"
  puts "Additional Info: #{options}"
end

greet(name: "Alice", age: 25, city: "New York")
# Output:
# Hello, Alice!
# Additional Info: {:age=>25, :city=>"New York"}

**options collects { age: 25, city: "New York" } as a hash.

Check: https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/

Also check the latest Ruby that is released last week:

Final Thoughts

Ruby has many terms that seem similar but have distinct uses. By understanding Procfile, Rake, Rack, and middleware in Rails 8, youโ€™ll have a much clearer picture of how Ruby applications work under the hood. If you’re working on a Rails 8 app, take some time to explore these concepts furtherโ€”they’ll definitely make your life easier!

Happy coding! ๐Ÿš€