Testing Strategies, Security, and Rails Ecosystem — Ruby Deep Dive[12]

Bhavyansh @ DiversePixel
4 min readJul 29, 2024

--

Testing Strategies for Large Rails Applications, and the Ecosystem.

Effective Use of RSpec, FactoryBot, and Capybara

RSpec

RSpec is a popular testing framework in Rails. It allows you to write human-readable test cases.

# spec/models/user_spec.rb
require 'rails_helper'
RSpec.describe User, type: :model do
it 'is valid with valid attributes' do
user = User.new(name: 'John Doe', email: 'john@example.com')
expect(user).to be_valid
end
it 'is not valid without a name' do
user = User.new(email: 'john@example.com')
expect(user).to_not be_valid
end
end

FactoryBot

FactoryBot is a fixtures replacement with a straightforward definition syntax.

# spec/factories/users.rb
FactoryBot.define do
factory :user do
name { 'John Doe' }
email { 'john@example.com' }
end
end
RSpec.describe User, type: :model do
it 'is valid with valid attributes' do
user = User.new(name: 'John Doe', email: 'john@example.com')
expect(user).to be_valid
end
it 'is not valid without a name' do
user = User.new(email: 'john@example.com')
expect(user).to_not be_valid
end
end

Capybara

Capybara helps you test web applications by simulating how a real user would interact with your app.

# spec/features/user_signs_in_spec.rb
require 'rails_helper'
RSpec.feature 'UserSignIn', type: :feature do
scenario 'User signs in with valid credentials' do
user = FactoryBot.create(:user, email: 'john@example.com', password: 'password')
visit new_user_session_path
fill_in 'Email', with: 'john@example.com'
fill_in 'Password', with: 'password'
click_button 'Log in'
expect(page).to have_text('Signed in successfully')
end
end

Contract Testing for Microservices

Contract testing ensures that services can communicate with each other as expected. Pacts is a common tool for this purpose.

# Gemfile
gem 'pact'
# spec/pacts/provider_pact_spec.rb
require 'pact/provider/rspec'
Pact.service_provider 'My Service' do
honours_pact_with 'Consumer Service' do
pact_uri '../consumer/pacts/consumer-service-my-service.json'
end
end

Performance Testing and Profiling

Using the benchmark gem

require 'benchmark'
Benchmark.bm do |x|
x.report("task 1:") { task1 }
x.report("task 2:") { task2 }
end

Profiling with rack-mini-profiler

# Gemfile
gem 'rack-mini-profiler'
# config/initializers/mini_profiler.rb
Rack::MiniProfiler.config.auto_inject = false
# ApplicationController
before_action { Rack::MiniProfiler.authorize_request }

Testing Background Jobs and Scheduled Tasks

Sidekiq Testing

# Gemfile
gem 'rspec-sidekiq'
# spec/workers/my_worker_spec.rb
require 'rails_helper'
RSpec.describe MyWorker, type: :worker do
it 'enqueues a job' do
expect {
MyWorker.perform_async('my_arg')
}.to change(MyWorker.jobs, :size).by(1)
end
end

Testing Cron Jobs with whenever

# schedule.rb
every 1.day, at: '4:30 am' do
runner "MyModel.daily_task"
end
# spec/models/my_model_spec.rb
describe '.daily_task' do
it 'performs the daily task' do
expect(MyModel).to receive(:daily_task)
MyModel.daily_task
end
end

Rails Security Deep Dive

Securing APIs (OAuth, JWT)

OAuth with omniauth

# Gemfile
gem 'omniauth'
gem 'omniauth-google-oauth2'
# config/initializers/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
provider :google_oauth2, ENV['GOOGLE_CLIENT_ID'], ENV['GOOGLE_CLIENT_SECRET']
end

JWT Authentication

# Gemfile
gem 'jwt'
# app/controllers/application_controller.rb
class ApplicationController < ActionController::API
def authenticate_request!
token = request.headers['Authorization']
decoded_token = JWT.decode(token, 'secret', true, algorithm: 'HS256')
@current_user = User.find(decoded_token[0]['user_id'])
rescue JWT::DecodeError
render json: { errors: 'Invalid token' }, status: :unauthorized
end
end

Handling Sensitive Data (Encryption, Key Management)

Encrypting Attributes

# Gemfile
gem 'attr_encrypted'
# app/models/user.rb
class User < ApplicationRecord
attr_encrypted :email, key: 'a secret key'
end

Preventing Common Vulnerabilities (CSRF, XSS, SSRF)

CSRF Protection

# ApplicationController
protect_from_forgery with: :exception

XSS Protection

<%= raw sanitize(user_input) %>

SSRF Protection

# Validate URLs before fetching them
require 'addressable/uri'
def safe_fetch(url)
uri = Addressable::URI.parse(url)
if uri.host.nil? || !uri.host.end_with?('trusted.com')
raise 'Untrusted URL'
end
# Fetch the URL
end

Security Headers and Content Security Policy

Security 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

Content Security Policy

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

Rails Ecosystem and Gem Deep Dives

Devise

Devise provides flexible authentication solutions for Rails.

# Gemfile
gem 'devise'
# Generate Devise setup
rails generate devise:install
rails generate devise User

Pundit

Pundit helps with authorization by providing a simple API.

# Gemfile
gem 'pundit'
# app/policies/post_policy.rb
class PostPolicy < ApplicationPolicy
def update?
user.admin? || record.user == user
end
end
# Using Pundit in controllers
class PostsController < ApplicationController
include Pundit
def update
@post = Post.find(params[:id])
authorize @post
# Update logic
end
end

ActiveStorage

ActiveStorage handles file uploads.

# Gemfile
gem 'aws-sdk-s3'
# config/storage.yml
amazon:
service: S3
access_key_id: <%= ENV['AWS_ACCESS_KEY_ID'] %>
secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %>
region: <%= ENV['AWS_REGION'] %>
bucket: <%= ENV['AWS_BUCKET'] %>
# Using ActiveStorage
class User < ApplicationRecord
has_one_attached :avatar
end
# Uploading an avatar
@user.avatar.attach(params[:avatar])

Building and Maintaining Your Own Gems

Creating a Gem

bundle gem my_gem

Gem Structure

my_gem/
├── lib/
│ ├── my_gem/
│ │ └── version.rb
│ └── my_gem.rb
├── spec/
│ └── my_gem_spec.rb
└── my_gem.gemspec

Publishing a Gem

gem build my_gem.gemspec
gem push my_gem-0.1.0.gem

Contributing to Open-Source Rails Projects

Finding Projects

  • Explore GitHub repositories with the rails topic.
  • Look for projects with a CONTRIBUTING.md file.

Submitting Pull Requests

  • Fork the repository.
  • Create a feature branch.
  • Commit your changes with clear messages.
  • Open a pull request with a detailed description.

Example Contribution

# Fork and clone the repository
git clone git@github.com:your-username/rails.git
# Create a feature branch
git checkout -b my-feature
# Make changes and commit
git commit -m "Add my feature"
# Push to your fork
git push origin my-feature
# Open a pull request on GitHub

This article provides a comprehensive guide to testing strategies, security, and the Rails ecosystem. It covers effective testing practices using popular tools, deep dives into securing Rails applications, and detailed looks at essential gems and contributing to open-source projects. Each section includes practical examples and tips to help you implement these strategies effectively in your Rails projects.

--

--

Bhavyansh @ DiversePixel
Bhavyansh @ DiversePixel

Written by Bhavyansh @ DiversePixel

Hey I write about Tech. Join me as I share my tech learnings and insights. 🚀

No responses yet