# Rails-Specific Features for MVC Pattern
> A comprehensive guide to Rails features that make MVC implementation elegant and secure.
## Table of Contents
1. [Controllers](#controllers)
- [Action Controller Parameters](#action-controller-parameters)
- [Filters and Callbacks](#filters-and-callbacks)
2. [Models](#models)
- [Active Record Basics](#active-record-basics)
- [Validations](#validations)
- [Associations](#associations)
3. [Views](#views)
- [ERB Templates](#erb-templates)
- [Helpers](#helpers)
- [Layouts and Partials](#layouts-and-partials)
4. [Routing](#routing)
- [RESTful Routes](#restful-routes)
- [Custom Routes](#custom-routes)
5. [Additional Features](#additional-features)
- [Concerns](#concerns)
- [Mailers](#mailers)
- [Active Storage](#active-storage)
## Controllers
### Action Controller Parameters
One of Rails' most important security features is Strong Parameters (also known as Action Controller Parameters). This feature helps prevent mass assignment vulnerabilities by requiring you to explicitly specify which parameters are allowed in your controller actions.
> 💡 **Security Note**: Strong Parameters were introduced to prevent mass assignment vulnerabilities, where malicious users could potentially set any attribute by manipulating the request parameters.
#### Basic Usage
```ruby
# In a Rails controller
class PersonController < ApplicationController
def create
# Only permit :name and :age parameters from the :person hash
person_params = params.require(:person).permit(:name, :age)
# Safe to use for mass assignment
@person = Person.create(person_params)
end
end
```
#### Key Methods
1. `require`: Ensures a parameter is present and returns the parameter
```ruby
# params = { person: { name: "John", age: 30 } }
params.require(:person) # Returns the person hash
params.require(:admin) # Raises ActionController::ParameterMissing
```
2. `permit`: Specifies which parameters are allowed
```ruby
# Allowing specific parameters
params.permit(:name, :age)
# Allowing nested parameters
params.permit(user: [:name, :age, address: [:street, :city]])
```
3. `expect`: Combines require and permit in one step
```ruby
# Instead of:
params.require(:person).permit(:name, :age)
# You can write:
params.expect(person: [:name, :age])
```
#### Example with Nested Parameters
```ruby
# Complex parameter structure
params = ActionController::Parameters.new({
person: {
name: "John",
age: 30,
address: {
street: "123 Main St",
city: "Boston"
},
hobbies: ["reading", "coding"]
}
})
# Permitting nested parameters
permitted = params.permit(
person: [
:name,
:age,
{ address: [:street, :city] },
{ hobbies: [] } # Empty array means permit any array values
]
)
```
#### Configuration Options
```ruby
# In config/application.rb or an initializer
# Option 1: Permit all parameters (not recommended for production)
ActionController::Parameters.permit_all_parameters = true
# Option 2: Control behavior for unpermitted parameters
ActionController::Parameters.action_on_unpermitted_parameters = :log # Default in development
ActionController::Parameters.action_on_unpermitted_parameters = :raise # Raises exception
ActionController::Parameters.action_on_unpermitted_parameters = false # No action
```
> 💡 **Best Practice**: Always explicitly permit parameters in your controllers. Never use `permit_all_parameters = true` in production, as it defeats the purpose of Strong Parameters.
#### Common Patterns
1. **Controller-level parameter method:**
```ruby
class PersonController < ApplicationController
def create
@person = Person.create(person_params)
end
private
def person_params
params.require(:person).permit(:name, :age, :email)
end
end
```
2. **Handling arrays of objects:**
```ruby
# For nested forms with multiple records
params.permit(
person: [
:name,
{ phones: [:number, :type] } # Allows array of phone objects
]
)
```
3. **Dynamic attributes:**
```ruby
# When attributes are dynamic
def permitted_params
params.permit(:name, *custom_fields)
end
def custom_fields
# Generate list of allowed fields dynamically
current_user.allowed_fields
end
```
### Filters and Callbacks
Controllers in Rails provide powerful filter mechanisms to run code before, after, or around controller actions.
```ruby
class ApplicationController < ActionController::Base
before_action :require_login
after_action :log_action
around_action :wrap_in_transaction
private
def require_login
unless current_user
flash[:error] = "You must be logged in"
redirect_to login_path
end
end
def log_action
Rails.logger.info "Action completed by #{current_user&.email}"
end
def wrap_in_transaction
ActiveRecord::Base.transaction do
yield # This yields to the actual controller action
end
end
end
```
> 💡 **Tip**: Use `skip_before_action` to skip filters for specific actions:
>
> ```ruby
> skip_before_action :require_login, only: [:index, :show]
> ```
## Models
### Active Record Basics
Rails models use Active Record to map database tables to Ruby objects.
```ruby
class User < ApplicationRecord
# Basic CRUD operations
user = User.new(name: "John")
user.save # INSERT
user.name = "Jane"
user.save # UPDATE
user.destroy # DELETE
# Querying
User.find(1) # Find by primary key
User.find_by(email: "example@email.com")
User.where(active: true)
User.order(created_at: :desc)
# Scopes - reusable queries
scope :active, -> { where(active: true) }
scope :recent, -> { order(created_at: :desc) }
end
```
### Validations
Rails provides rich validation capabilities for models:
```ruby
class Product < ApplicationRecord
# Presence validations
validates :name, presence: true
validates :price, presence: true, numericality: { greater_than: 0 }
# Format validations
validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }
# Custom validations
validate :price_must_be_reasonable
private
def price_must_be_reasonable
if price.present? && price > 1000000
errors.add(:price, "is too high")
end
end
end
# Usage
product = Product.new
product.valid? # => false
product.errors.full_messages # => ["Name can't be blank", "Price can't be blank"]
```
### Associations
Rails makes it easy to define relationships between models:
```ruby
class User < ApplicationRecord
# One-to-many relationship
has_many :posts, dependent: :destroy
has_many :comments
# Many-to-many relationship
has_many :followed_users, through: :relationships, source: :followed
has_many :followers, through: :reverse_relationships
# One-to-one relationship
has_one :profile
# Polymorphic association
has_many :pictures, as: :imageable
end
class Post < ApplicationRecord
belongs_to :user
has_many :comments
# Rich join tables
has_many :post_categories
has_many :categories, through: :post_categories
end
```
## Views
### ERB Templates
Rails uses ERB (Embedded Ruby) for view templates:
```erb
<!-- app/views/users/show.html.erb -->
<div class="user-profile">
<% # Ruby code that doesn't output anything %>
<% if @user.profile.present? %>
<%# Ruby code that outputs something %>
<h1><%= @user.name %></h1>
<%# Safe HTML output %>
<%= render 'shared/user_info', user: @user %>
<%# Raw HTML output (be careful!) %>
<%== @user.profile.html_bio %>
<% end %>
</div>
```
### Helpers
View helpers keep your templates clean and DRY:
```ruby
# app/helpers/application_helper.rb
module ApplicationHelper
def format_date(date)
date.strftime("%B %d, %Y")
end
def avatar_for(user, size: 80)
if user.avatar.attached?
image_tag user.avatar.variant(resize: "#{size}x#{size}")
else
image_tag "default_avatar.png", size: "#{size}x#{size}"
end
end
end
# Usage in view
<%= format_date(@user.created_at) %>
<%= avatar_for(@user, size: 100) %>
```
### Layouts and Partials
Rails encourages DRY views through layouts and partials:
```erb
<!-- app/views/layouts/application.html.erb -->
<!DOCTYPE html>
<html>
<head>
<title><%= yield :title %> | MyApp</title>
<%= csrf_meta_tags %>
<%= stylesheet_link_tag 'application' %>
</head>
<body>
<%= render 'shared/header' %>
<div class="container">
<%= yield %>
</div>
<%= render 'shared/footer' %>
</body>
</html>
<!-- app/views/shared/_header.html.erb -->
<header>
<nav>
<%= link_to 'Home', root_path %>
<%= render 'shared/user_menu' if current_user %>
</nav>
</header>
```
## Routing
### RESTful Routes
Rails promotes RESTful routing by default:
```ruby
# config/routes.rb
Rails.application.routes.draw do
# Basic RESTful routes
resources :posts do
resources :comments # Nested resources
end
# Customizing routes
resources :users do
member do
get 'profile'
patch 'update_profile'
end
collection do
get 'search'
end
end
# Namespace for API
namespace :api do
namespace :v1 do
resources :posts, only: [:index, :show]
end
end
end
```
### Custom Routes
Sometimes you need custom routes beyond REST:
```ruby
Rails.application.routes.draw do
# Custom member routes
resources :posts do
member do
post 'publish'
delete 'unpublish'
end
end
# Custom collection routes
resources :users do
collection do
get 'search'
get 'export'
end
end
# Singular resource
resource :profile
# Custom routes with constraints
get 'posts/:year/:month/:day', to: 'posts#by_date',
constraints: {
year: /\d{4}/,
month: /\d{1,2}/,
day: /\d{1,2}/
}
end
```
## Additional Features
### Concerns
Concerns allow you to share common code between models and controllers:
```ruby
# app/models/concerns/searchable.rb
module Searchable
extend ActiveSupport::Concern
included do
scope :search, ->(query) {
where("name LIKE ?", "%#{query}%")
}
end
def search_rank
# Custom search ranking logic
end
end
# Usage in model
class Product < ApplicationRecord
include Searchable
end
```
### Mailers
Rails provides a powerful mailer system:
```ruby
# app/mailers/user_mailer.rb
class UserMailer < ApplicationMailer
def welcome_email
@user = params[:user]
@url = new_user_session_url
attachments['welcome.pdf'] = WelcomePdf.new(@user).render
mail(
to: @user.email,
subject: "Welcome to Our App!"
)
end
end
# app/views/user_mailer/welcome_email.html.erb
<h1>Welcome, <%= @user.name %>!</h1>
<p>You can login at: <%= @url %></p>
# Sending mail
UserMailer.with(user: @user).welcome_email.deliver_later
```
### Active Storage
Rails makes file handling easy with Active Storage:
```ruby
class User < ApplicationRecord
# Single file attachment
has_one_attached :avatar
# Multiple file attachments
has_many_attached :documents
# Validations for attachments
validates :avatar, content_type: ['image/png', 'image/jpeg'],
size: { less_than: 5.megabytes }
end
# Usage in controller
def update
if @user.update(user_params)
redirect_to @user, notice: 'Profile updated!'
else
render :edit
end
end
private
def user_params
params.require(:user).permit(:name, :email, :avatar)
end
# Usage in view
<%= form_with(model: @user) do |form| %>
<%= form.file_field :avatar %>
<%= image_tag @user.avatar if @user.avatar.attached? %>
<% end %>
```
> 💡 **Best Practice**: Always validate file types and sizes to prevent security issues and ensure good performance.
---
🎉 This guide covers the essential Rails features that make MVC implementation powerful and elegant. Remember that Rails follows convention over configuration, so following these patterns will make your development experience smoother and more productive!
Rails-Specific Features for MVC Pattern
A comprehensive guide to Rails features that make MVC implementation elegant and secure.
One of Rails’ most important security features is Strong Parameters (also known as Action Controller Parameters). This feature helps prevent mass assignment vulnerabilities by requiring you to explicitly specify which parameters are allowed in your controller actions.
💡 Security Note: Strong Parameters were introduced to prevent mass assignment vulnerabilities, where malicious users could potentially set any attribute by manipulating the request parameters.
Basic Usage
1
2
3
4
5
6
7
8
9
10
# In a Rails controllerclassPersonController<ApplicationControllerdefcreate# Only permit :name and :age parameters from the :person hashperson_params=params.require(:person).permit(:name,:age)# Safe to use for mass assignment@person=Person.create(person_params)endend
Key Methods
require: Ensures a parameter is present and returns the parameter
1
2
3
# params = { person: { name: "John", age: 30 } }params.require(:person)# Returns the person hashparams.require(:admin)# Raises ActionController::ParameterMissing
permit: Specifies which parameters are allowed
1
2
3
4
5
# Allowing specific parametersparams.permit(:name,:age)# Allowing nested parametersparams.permit(user:[:name,:age,address:[:street,:city]])
expect: Combines require and permit in one step
1
2
3
4
5
# Instead of:params.require(:person).permit(:name,:age)# You can write:params.expect(person:[:name,:age])
# Complex parameter structureparams=ActionController::Parameters.new({person:{name:"John",age:30,address:{street:"123 Main St",city:"Boston"},hobbies:["reading","coding"]}})# Permitting nested parameterspermitted=params.permit(person:[:name,:age,{address:[:street,:city]},{hobbies:[]}# Empty array means permit any array values])
Configuration Options
1
2
3
4
5
6
7
8
9
# In config/application.rb or an initializer# Option 1: Permit all parameters (not recommended for production)ActionController::Parameters.permit_all_parameters=true# Option 2: Control behavior for unpermitted parametersActionController::Parameters.action_on_unpermitted_parameters=:log# Default in developmentActionController::Parameters.action_on_unpermitted_parameters=:raise# Raises exceptionActionController::Parameters.action_on_unpermitted_parameters=false# No action
💡 Best Practice: Always explicitly permit parameters in your controllers. Never use permit_all_parameters = true in production, as it defeats the purpose of Strong Parameters.
# For nested forms with multiple recordsparams.permit(person:[:name,{phones:[:number,:type]}# Allows array of phone objects])
Dynamic attributes:
1
2
3
4
5
6
7
8
9
# When attributes are dynamicdefpermitted_paramsparams.permit(:name,*custom_fields)enddefcustom_fields# Generate list of allowed fields dynamicallycurrent_user.allowed_fieldsend
Filters and Callbacks
Controllers in Rails provide powerful filter mechanisms to run code before, after, or around controller actions.
classApplicationController<ActionController::Basebefore_action:require_loginafter_action:log_actionaround_action:wrap_in_transactionprivatedefrequire_loginunlesscurrent_userflash[:error]="You must be logged in"redirect_tologin_pathendenddeflog_actionRails.logger.info"Action completed by #{current_user&.email}"enddefwrap_in_transactionActiveRecord::Base.transactiondoyield# This yields to the actual controller actionendendend
💡 Tip: Use skip_before_action to skip filters for specific actions:
classUser<ApplicationRecord# Single file attachmenthas_one_attached:avatar# Multiple file attachmentshas_many_attached:documents# Validations for attachmentsvalidates:avatar,content_type:['image/png','image/jpeg'],size:{less_than:5.megabytes}end# Usage in controllerdefupdateif@user.update(user_params)redirect_to@user,notice:'Profile updated!'elserender:editendendprivatedefuser_paramsparams.require(:user).permit(:name,:email,:avatar)end# Usage in view<%= form_with(model: @user) do |form| %>
<%=form.file_field:avatar%>
<%= image_tag @user.avatar if @user.avatar.attached? %><% end %>
💡 Best Practice: Always validate file types and sizes to prevent security issues and ensure good performance.
🎉 This guide covers the essential Rails features that make MVC implementation powerful and elegant. Remember that Rails follows convention over configuration, so following these patterns will make your development experience smoother and more productive!