Mastering ๐–กŽ Ruby: Exploring Syntax Sugar, Operators, and Useful Methods

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


Ruby Syntax Sugar

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

Shorthand for Array and Symbol Arrays

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

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

These notations also support interpolation:

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

Shorthand for Mapping and Selecting Elements

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

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

This is equivalent to:

strings.map { |str| str.upcase }


Operator Method Calls

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

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

Array Indexing and Append Operations

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

Avoiding Dot Notation with Operators

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

# Good practice
puts num + 5  # Preferred

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


none? Method in Ruby Arrays

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

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

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


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

Exponentiation

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

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

XOR Operator

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

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

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


Safe Navigation Operator (&.)

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

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

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


Useful String Methods: chop and find_all

chop: Removing the Last Character

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

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

find_all: Filtering Collections

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

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

This is particularly useful for data processing tasks.


=~ Operator in Ruby

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

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

This operator is useful for quick pattern matching in strings.


.squish Method in Ruby

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

require 'active_support/all'

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

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


Conclusion

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

Enjoy Ruby! ๐Ÿš€

Rails ๐Ÿ›ค DSLs Explained: How Ruby Makes Configuration Elegant

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

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


What is a DSL?

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

Two types of DSLs exist:

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

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


1. Routes in Rails: A Classic Example of DSL

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

Example:

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

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

How Does This Work?

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

Why Use a DSL for Routes?

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

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


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

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

Example:

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

What is config Here?

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

Why a DSL for Configuration?

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

3. RSpecโ€™s describe Method: A DSL for Testing

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

Example:

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

How Does This Work?

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

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

Why Use a DSL for Testing?

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

4. Defining Methods Dynamically: ActiveSupport::Concern

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

Example:

module Trackable
  extend ActiveSupport::Concern

  included do
    before_save :track_changes
  end

  private
  def track_changes
    puts "Tracking changes!"
  end
end

class User < ApplicationRecord
  include Trackable
end

How This Works:

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

Why a DSL for Mixins?

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

Conclusion: Why Rails Embraces DSLs

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

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

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

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

Enjoy Ruby ๐Ÿš€

Exploring Rails 8: Powerful ๐Ÿ’ช Features, Deployment & Real-Time Updates

Introduction

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

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


Rails 8 Features & Enhancements

1. Modern JavaScript with Importmaps & Hotwire

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

Key Benefits:

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

Example: Pinning a Package

bin/importmap pin local-time

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

Hotwire Integration

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

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

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


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

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

Setting Up Turbo Streams in Views:

<%= turbo_stream_from @object %>

This creates a WebSocket channel tied to the object.

Broadcasting Updates from Models:

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

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

Why This Matters:

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

3. Rich Text with ActionText

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

Model Level Implementation:

has_rich_text :body

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

View Implementation:

<%= form.rich_text_area :body %>

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

Displaying Updated Timestamps:

<%= time_tag post.updated_at %>

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


4. Deployment with Kamal โ€“ Simpler & Faster

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

Deployment Steps:

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

File Uploads with Active Storage

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


5. Built-in Authentication โ€“ No Devise Needed

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

Generating Authentication:

rails g authentication
rails db:migrate

Creating a User for Testing:

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

Managing Authentication:

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

6. Turning a Rails App into a PWA

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

Steps to Enable PWA:

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

Final Thoughts

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

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

Installing โš™๏ธ and Setting Up ๐Ÿ”ง Ruby 3.4, Rails 8.0 and IDE on macOS in 2025

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):

brew install openssl@3 libyaml gmp rust

โ€ฆ..
==> Installing rust dependency: libssh2, readline, sqlite, python@3.13, pkgconf
==> Installing rust

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

Install Mise Version Manager

curl https://mise.run | sh
echo 'eval "$(~/.local/bin/mise activate zsh)"' >> ~/.zshrc
source ~/.zshrc

Install Ruby and Rails

mise use -g ruby@3
mise ruby@3.4.1 โœ“ installed
mise ~/.config/mise/config.toml tools: ruby@3.4.1

ruby --version   # output Ruby 3.4.1

gem install rails

# reload terminal and check
rails --version  # output Rails 8.0.1

For additional guidance, refer to these resources:


2. Installing an IDE for Ruby on Rails Development

Choosing the right Integrated Development Environment (IDE) is crucial for productivity. Here are some popular options:

RubyMine

  • Feature-rich and specifically designed for Ruby on Rails.
  • Includes debugging tools, database integration, and smart code assistance.
  • Paid software that can be resource-intensive.

Sublime Text

  • Lightweight and highly customizable.
  • Requires plugins for additional functionality.

Visual Studio Code (VS Code) (Recommended)

  • Free and open-source.
  • Excellent plugin support.

Install VS Code

Follow the official installation guide.

Enable GitHub Copilot for AI-assisted coding:

  1. Open VS Code.
  2. Sign in with your GitHub account.
  3. Enable Copilot from the extensions panel.

To use VS Code from the terminal, ensure code is added to your $PATH:

  1. Open Command Palette (Cmd+Shift+P).
  2. Search for Shell Command: Install 'code' command in PATH.
  3. Restart your terminal and try: code .

3. Your 15 Essential VS Code Extensions for Ruby on Rails

To enhance your development workflow, install the following VS Code extensions:

  1. GitHub Copilot – AI-assisted coding (already installed).
  2. vscode-icons – Better file and folder icons.
  3. Tabnine AI – AI autocompletion for JavaScript and other languages.
  4. Ruby & Ruby LSP – Language support and linting.
  5. ERB Formatter/Beautify – Formats .erb files (requires htmlbeautifier gem): gem install htmlbeautifier
  6. ERB Helper Tags – Autocomplete for ERB tags.
  7. GitLens – Advanced Git integration.
  8. Ruby Solargraph – Provides code completion and inline documentation (requires solargraph gem): gem install solargraph
  9. Rails DB Schema – Auto-completion for Rails database schema.
  10. ruby-rubocop – Ruby linting and auto-formatting (requires rubocop gem): gem install rubocop
  11. endwise – Auto-adds end keyword in Ruby.
  12. Output Colorizer – Enhances syntax highlighting in log files.
  13. Auto Rename Tag – Automatically renames paired HTML/Ruby tags.
  14. Highlight Matching Tag – Highlights matching tags for better visibility.
  15. Bracket Pair Colorizer 2 – Improved bracket highlighting.

Conclusion

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.

Part 2: https://railsdrop.com/2025/03/22/setup-rails-8-app-rubocop-actiontext-image-processing-part-2

Happy Rails setup! ๐Ÿš€

The Evolution of Asset ๐Ÿ“‘ Management in Web and Ruby on Rails

Understanding Middleware in Rails

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.

ActionDispatch::Static Documentation

“This middleware serves static files from disk, if available. If no file is found, it hands off to the main app.”

Where Are Static Files Stored?

Rails stores static assets in the public/ directory, and ActionDispatch::Static ensures these are served efficiently without hitting the Rails stack.

Core Components of Ruby on Rails – A reminder

To understand asset management evolution, let’s quickly revisit Rails’ core components:

  • 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:

  1. ES6 Native Support in All Major Browsers
    • No need for transpilation of modern JavaScript.
  2. CSS Advancements
    • Features like variables and nesting eliminate the need for preprocessors like SASS.
  3. 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!

Happy Rails Coding! ๐Ÿš€

Writing Effective Test Cases ๐Ÿšง for Your Ruby on Rails Model: A Guide

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:

  1. Bug Detection: Test cases help uncover and fix bugs before they impact users.
  2. Regression Prevention: Tests ensure that new code changes do not break existing functionality.
  3. Documentation: Well-written test cases serve as documentation for your codebase, making it easier for other developers to understand and modify the code.
  4. Refactoring Confidence: Tests provide the confidence to refactor code knowing that you won’t introduce defects.
  5. 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.

Happy testing!

Rails 6.1 introduce ‘compact_blank’

Before Rails 6 we used to remove the blank values from Array and Hash by using other available methods.

Before:

  [...].delete_if(&:blank?)
  {....}.delete_if { |_k, v| v.blank? }
OR
  [...].reject(&:blank?)
  ...

From now, Rails 6.1.3.1 onwards you can use the module Enumerable’s compact_blank and compact_blank! methods.

Now we can use:

[1, "", nil, 2, " ", [], {}, false, true].compact_blank
=> [1, 2, true]

['', nil, 8, [], {}].compact_blank
=> [8]

{ a: "", b: 1, c: nil, d: [], e: false, f: true }.compact_blank
=> {:b=>1, :f=>true}

The method compact_blank! is a destructive method (handle with care) for compact_blank.

As a Rails developer, I am grateful for this method because there are many scenarios where we find ourselves replicating this code.

Basic Software installation| Moving micro-services into AWS EC2 instance – Part 1

As I mentioned in the previous post, I have decided to move away from micro-services. To achieve this, I am taking an AWS EC2 instance and configuring each micro-service on this instance. For this setup, I am using an Ubuntu 16.04 machine because my application setup is a bit old. However, if you have newer versions of Rails, Ruby, etc., you may want to choose Ubuntu 20.04.

Our setup includes Ruby on Rails (5.2.1) micro-services (5-10 in number), a NodeJS application, a Sinatra Application, and an Angular 9.1 Front-End Application.

To begin, go to the AWS EC2 home page and select an Ubuntu 16.04 machine with default configurations and SSH enabled.

https://ap-south-1.console.aws.amazon.com/ec2/v2/home

Now login to this new instance and install all the packages we needed for our setup.

Software Installation

Update the package list.

sudo apt-get update

Install Ruby dependencies.

sudo apt-get install ruby-dev
sudo apt-get install libxml2-dev
sudo apt-get install libxslt-dev
sudo apt-get install graphviz

Install NodeJS

curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash -
sudo apt-get install -y nodejs
node -v

Install yarn and other dependencies.

curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt-get update
sudo apt-get install git-core zlib1g-dev build-essential libssl-dev libreadline-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt1-dev libcurl4-openssl-dev software-properties-common libffi-dev nodejs yarn

Install Mysql 5.7 (Remember this is for Ubuntu 16.04, 18.04 versions)

sudo apt-get install mysql-server-5.7 mysql-client-core-5.7 libmysqlclient-dev
sudo service mysql status # or
systemctl status mysql
username: <your-username>, password: <your-password>

You can also try
mysql_secure_installation, if you use other mysql version.

Note that if you are setting up Ubuntu 20.04, there is a significant change in MySQL, as the version of MySQL is now 8.0 instead of 5.7. If you have applications running in MySQL 5.7, it is recommended that you set up and use Ubuntu 16.04 or 18.04.

We will continue the installation process in our next post.

Our Challenges with Microservices on AWS ECS

As part of our startup, our predecessors chose to use micro-services for our new website as it is a trending technology.

This decision has many benefits, such as:

  • Scaling a website becomes much easier when using micro-services, as each service can be scaled independently based on its individual needs.
  • The loosely coupled nature of micro-services also allows for easier development and maintenance, as changes to one service do not affect the functionality of other services.
  • Additionally, deployment can be focused on each individual service, making the overall process more efficient.
  • Micro-services also allow for the use of different technologies for each service, providing greater flexibility and the ability to choose the best tools for each task.
  • Finally, testing can be concentrated on one service at a time, allowing for more thorough and effective testing, which can result in higher quality code and a better user experience.

In developing our application with micro-services, we considered the potential problems that we may face in the future. However, it is important to note that we also need to consider whether these problems will have a significant impact compared to the potential disadvantages of using micro-services.

One factor to keep in mind is that our website is currently experiencing low traffic and we are acquiring clients gradually. As such, we need to consider whether the benefits of micro-services outweigh any potential drawbacks for our particular situation.

Regardless, some potential issues with micro-services include increased complexity and overhead in development, as well as potential performance issues when integrating multiple services. Additionally, managing multiple services and ensuring they communicate effectively can also be a challenge.

Despite the benefits of micro-services, we have faced some issues in implementing them. One significant challenge is the increased complexity of deployment and maintenance that comes with having multiple services. This can require more time and resources to manage and can potentially increase the likelihood of errors.

Additionally, the cost of using AWS ECS for hosting all of the micro-services can be higher than using other hosting solutions for a less traffic website. This is something to consider when weighing the benefits and drawbacks of using micro-services for our specific needs.

Another challenge we have faced is managing dependencies between services, which can be difficult to avoid. When one service goes offline, it can cause issues with other services, leading to a “No Service” issue on the website.

Finally, it can be very difficult to go back to a monolithic application even if we combine 3-4 services together, as they may use different software or software versions. This can make it challenging to make changes or updates to the application as a whole.

It is important to carefully consider whether micro-service architecture is the best fit for your business and current situation. If you have a less used website or are just starting your business, it may not be necessary or cost-effective to implement micro-services.

It is important to take the time to evaluate the benefits and drawbacks of using micro-services for your specific needs and budget. Keep in mind that hosting multiple micro-services can come with additional costs, so be prepared to pay a minimum amount for hosting if you decide to go this route.

Ultimately, the decision to use micro-services should be based on a thorough assessment of your business needs and available resources, rather than simply following a trend or industry hype.

Set up:

  • Used AWS ECS (ec2 launch type) with services and task definitions defined
  • 11 Micro-services, 11 containers are spinning
  • Cost: Rs.12k ($160) per month

Workaround:

  • Consider using AWS Fargate type but not sure these issues get resolved
  • Deploy all the services in one EC2 Instance without using ECS

Setup Rspec, factory bot and database cleaner for Rails 5.2.6

To configure the best test suite in Rails using the RSpec framework and other supporting libraries, such as Factory Bot and Database Cleaner, we’ll remove the Rails native test folder and related configurations.

To begin, we’ll add the necessary gems to our Gemfile:

group :development, :test do
  # Rspec testing module and needed libs
  gem 'factory_bot_rails', '5.2.0'
  gem 'rspec-rails', '~> 4.0.0'
end

group :test do
  # db cleaner for test suite 
  gem 'database_cleaner-active_record', '~> 2.0.1'
end

Now do

bunde install # this installs all the above gems

If your Rails application already includes the built-in Rails test suite, you’ll need to remove it in order to use the RSpec module instead.

I recommend using RSpec over the Rails native test module, as RSpec provides more robust helpers and mechanisms for testing.

To disable the Rails test suite, navigate to the application.rb file and comment out the following line:

# require 'rails/test_unit/railtie'

inside the class Application add this line:

# Don't generate system test files.
config.generators.system_tests = nil

Remove the native rails test folder:

rm -r test/

We use factories over fixtures. Remove this line from rails_helper.rb

config.fixture_path = "#{::Rails.root}/spec/fixtures"

and modify this line to:

config.use_transactional_fixtures = false # instead of true

This is for preventing rails to generate the native test files when we run rails generators.

Database Cleaner

Now we configure the database cleaner that is used for managing data in our test cycles.

Open rails_helper.rb file and require that module

require 'rspec/rails'
require 'database_cleaner'  # <= add here

Note: Use only if you run integration tests with capybara or dealing with javascript codes in the test suite.

“Capybara spins up an instance of our Rails app that canโ€™t see our test data transaction so even tho weโ€™ve created a user in our tests, signing in will fail because to the Capybara run instance of our app, there are no users.”

I experienced database credentials issues:

โžœ rspec
An error occurred while loading ./spec/models/user_spec.rb.
Failure/Error: ActiveRecord::Migration.maintain_test_schema!

Mysql2::Error::ConnectionError:
  Access denied for user 'username'@'localhost' (using password: NO)

Initially, I planned to use Database Cleaner, but later I realized that an error I was experiencing was actually due to a corrupted credentials.yml.enc file. I’m not sure how it happened.

To check if your credentials are still intact, try editing the file and verifying that the necessary information is still present.

EDITOR="code --wait" bin/rails credentials:edit

Now in the Rspec configuration block we do the Database Cleaner configuration.

Add the following file:

spec/support/database_cleaner.rb

Inside, add the following:

# DB cleaner using database cleaner library
RSpec.configure do |config|
  # This says that before the entire test suite runs, clear 
  # the test database out completely
  config.before(:suite) do
    DatabaseCleaner.strategy = :transaction
    DatabaseCleaner.clean_with(:truncation)
  end

  # This sets the default database cleaning strategy to 
  # be transactions
  config.before(:each) do
    DatabaseCleaner.strategy = :transaction
  end

  # include this if you uses capybara integration tests
  config.before(:each, :js => true) do
    DatabaseCleaner.strategy = :truncation
  end

  # These lines hook up database_cleaner around the beginning 
  # and end of each test, telling it to execute whatever 
  # cleanup strategy we selected
  config.before(:each) do
    DatabaseCleaner.start
  end

  config.after(:each) do
    DatabaseCleaner.clean
  end
end

and be sure to require this file in rails_helper.rb

require 'rspec/rails'
require 'database_cleaner'
require_relative 'support/database_cleaner'  # <= here

Configure Factories

Note: We use factories over fixtures because factories provide better features that make writing test cases an easy task.

Create a folder to generate the factories:

mkdir spec/factories

Rails generators will automatically generate factory files for models inside this folder.

A generator for model automatically creating the following files:

spec/models/model_spec.rb
spec/factories/model.rb

Now lets load Factory bot configuration to rails test suite.

Add the following file:

spec/support/factory_bot.rb

and be sure to require this file in rails_helper.rb

require 'rspec/rails'
require 'database_cleaner'
require_relative 'support/database_cleaner'
require_relative 'support/factory_bot'  # <= here

You can see the following line commented

# Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }

You can uncomment the line to make all factories available in your test suite, but I don’t recommend this approach as it can slow down test execution. Instead, it’s better to load each factory as needed.

Here’s the final version of the rails_helper.rb file. Note that we won’t be using Capybara for integration tests, so we’re not including the database_cleaner configuration:

# This file is copied to spec/ when you run 'rails generate rspec:install'
require 'spec_helper'
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../config/environment', __dir__)
# Prevent database truncation if the environment is production
abort('The Rails environment is running in production mode!') if Rails.env.production?
require 'rspec/rails'
require_relative 'support/factory_bot'

# Checks for pending migrations and applies them before tests are run.
# If you are not using ActiveRecord, you can remove these lines.
begin
  ActiveRecord::Migration.maintain_test_schema!
rescue ActiveRecord::PendingMigrationError => e
  puts e.to_s.strip
  exit 1
end
RSpec.configure do |config|
  # If you're not using ActiveRecord, or you'd prefer not to run each of your
  # examples within a transaction, remove the following line or assign false
  # instead of true.
  config.use_transactional_fixtures = false

  config.infer_spec_type_from_file_location!

  # Filter lines from Rails gems in backtraces.
  config.filter_rails_from_backtrace!
  # arbitrary gems may also be filtered via:
  # config.filter_gems_from_backtrace("gem name")
end

A spec directory look something like this:

spec/
  controllers/
    user_controller_spec.rb
    product_controller_spec.rb
  factories/
    user.rb
    product.rb
  models/
    user_spec.rb
    product_spec.rb
  mailers/
    mailer_spec.rb
  services/
    service_spec.rb  
  rails_helper.rb
  spec_helper.rb

References:

https://github.com/rspec/rspec-rails
https://relishapp.com/rspec/rspec-rails/docs
https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md#configure-your-test-suite
https://github.com/DatabaseCleaner/database_cleaner

Model Specs

Lets generate a model spec. A model spec is used to test smaller parts of the system, such as classes or methods.

# RSpec also provides its own spec file generators
โžœ rails generate rspec:model user
      create  spec/models/user_spec.rb
      invoke  factory_bot
      create    spec/factories/users.rb

Now run the rpsec command. That’s it. You can see the output from rspec.

โžœ rspec
*

Pending: (Failures listed here are expected and do not affect your suite's status)

  1) Item add some examples to (or delete) /home/.../spec/models/user_spec.rb
     # Not yet implemented
     # ./spec/models/user_spec.rb:4

Finished in 0.00455 seconds (files took 1.06 seconds to load)
1 example, 0 failures, 1 pending

Lets discuss how to write a perfect model spec in the next lesson.