Testing Strategies, Security, and Rails Ecosystem — Ruby Deep Dive[12]
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.