01: Mapping Concepts: Rails vs Laravel

01: Mapping Concepts: Rails vs Laravel
Section titled “01: Mapping Concepts: Rails vs Laravel”Overview
Section titled “Overview”If you’ve worked with Ruby on Rails, you already understand web frameworks—you just need to see how those concepts translate to Laravel. Laravel’s creator, Taylor Otwell, openly acknowledges Rails as a major inspiration. The result? Two frameworks that share remarkably similar philosophies and patterns.
This chapter shows you Rails code you know, then demonstrates the Laravel equivalent. You’ll discover that Laravel isn’t fundamentally different from Rails—it’s the same concepts with different syntax and conventions.
By the end of this chapter, you’ll have a comprehensive mental map that lets you confidently translate your Rails knowledge into Laravel development.
Prerequisites
Section titled “Prerequisites”Before starting this chapter, you should have:
- Completion of Chapter 00: Introduction or equivalent understanding
- Basic familiarity with Ruby on Rails (routing, models, controllers, migrations)
- PHP 8.4+ installed (for running Laravel examples)
- Estimated Time: ~45-60 minutes
Verify your setup:
php --version # Should show PHP 8.4 or higherWhat You’ll Build
Section titled “What You’ll Build”By the end of this chapter, you will have:
- A comprehensive mental map of Rails-to-Laravel concept translations
- Understanding of how routing, models, controllers, and views compare
- Knowledge of migration syntax differences
- Familiarity with Laravel’s command-line tools (artisan)
- A reference guide for common Rails patterns in Laravel
Objectives
Section titled “Objectives”- Map Rails routing patterns to Laravel routing syntax
- Compare ActiveRecord with Eloquent ORM
- Understand controller differences between Rails and Laravel
- Translate ERB templates to Blade syntax
- Learn migration command and syntax differences
- Compare command-line tools: rails vs artisan
- Understand validation and authentication approaches
Quick Start
Section titled “Quick Start”If you want to see the big picture first, here’s a side-by-side comparison of a simple Rails and Laravel feature:
Rails Route + Controller:
resources :posts
# app/controllers/posts_controller.rbdef index @posts = Post.where(published: true).order(created_at: :desc)endLaravel Route + Controller:
Route::resource('posts', PostController::class);
# app/Http/Controllers/PostController.phppublic function index(){ $posts = Post::where('published', true)->orderBy('created_at', 'desc')->get(); return view('posts.index', compact('posts'));}Notice the similarities? The patterns are nearly identical—just different syntax. Let’s dive into the details.
The Shared Philosophy
Section titled “The Shared Philosophy”Both Rails and Laravel embrace:
- Convention over configuration - Sensible defaults that “just work”
- MVC architecture - Separation of concerns
- Active Record pattern - Models that map to database tables
- RESTful routing - Resource-oriented URLs
- Developer happiness - Focus on productivity and joy
- Batteries included - Full-stack frameworks with everything you need
::: tip As a Rails developer, you’re already 70% of the way to understanding Laravel. The remaining 30% is mostly syntax and PHP-specific patterns. :::
Quick Reference: Rails to Laravel
Section titled “Quick Reference: Rails to Laravel”Here’s a high-level comparison to orient you:
| Rails Concept | Laravel Equivalent | Notes |
|---|---|---|
rails new app | laravel new app | Create new application |
rails server | php artisan serve | Start development server |
rails console | php artisan tinker | Interactive REPL |
rails generate | php artisan make: | Code generation |
| ActiveRecord | Eloquent ORM | Nearly identical Active Record pattern |
routes.rb | routes/web.php | Route definitions |
| Controllers | Controllers | Same concept, different namespace |
| ERB templates | Blade templates | Similar syntax, different directives |
db/migrate/ | database/migrations/ | Migration files |
rake db:migrate | artisan migrate | Run migrations |
has_many | hasMany() | Relationships (camelCase in Laravel) |
validates | Validation rules | Different syntax, same concept |
| Devise | Breeze/Jetstream | Authentication scaffolding |
| RSpec/Minitest | PHPUnit/Pest | Testing frameworks |
Bundler (Gemfile) | Composer | Dependency management |
Step 1: Understanding Routing (~10 min)
Section titled “Step 1: Understanding Routing (~10 min)”Learn how Rails routing patterns translate directly to Laravel’s routing syntax.
Actions
Section titled “Actions”- Compare basic routing syntax
In Rails, you define routes in config/routes.rb:
Rails.application.routes.draw do # Simple route get 'welcome', to: 'pages#welcome'
# RESTful resource routes resources :posts
# Nested resources resources :posts do resources :comments end
# Root route root 'pages#home'endIn Laravel, routes are defined in routes/web.php:
<?php
use App\Http\Controllers\PageController;use App\Http\Controllers\PostController;use App\Http\Controllers\CommentController;
// Simple routeRoute::get('/welcome', [PageController::class, 'welcome']);
// RESTful resource routesRoute::resource('posts', PostController::class);
// Nested resourcesRoute::resource('posts.comments', CommentController::class);
// Root routeRoute::get('/', [PageController::class, 'home']);- Understand route helpers
Rails:
post_path(@post) # => /posts/1posts_path # => /postsedit_post_path(@post) # => /posts/1/editLaravel:
<?php
declare(strict_types=1);
// Generate URL from named routeroute('posts.show', $post); // => /posts/1route('posts.index'); // => /postsroute('posts.edit', $post); // => /posts/1/editExpected Result
Section titled “Expected Result”You should understand that:
- Rails uses symbol-based routing (
'controller#action') - Laravel uses class-based routing (
[Controller::class, 'method']) - Both support RESTful resource routing
- Route helpers work similarly but use different syntax
Why It Works
Section titled “Why It Works”Both frameworks follow RESTful conventions, automatically generating standard CRUD routes. Laravel’s Route::resource() creates the same seven routes as Rails’ resources, following identical HTTP verb and URL patterns.
::: tip Laravel Route Naming
Laravel automatically names resource routes using the pattern resource.action. For example, Route::resource('posts', PostController::class) creates named routes like posts.index, posts.show, posts.store, etc.
:::
Troubleshooting
Section titled “Troubleshooting”-
Error: “Target class does not exist” — Ensure you’ve imported the controller class with
use App\Http\Controllers\ControllerName;at the top of your routes file. -
Routes not working — Run
php artisan route:listto see all registered routes (equivalent torails routesin Rails).
Step 2: Comparing Models and ORM (~15 min)
Section titled “Step 2: Comparing Models and ORM (~15 min)”Understand how ActiveRecord and Eloquent provide nearly identical functionality with different syntax.
Actions
Section titled “Actions”- Compare model relationships
Rails Model:
class Post < ApplicationRecord # Relationships belongs_to :user has_many :comments, dependent: :destroy has_many :tags, through: :post_tags
# Scopes scope :published, -> { where(published: true) } scope :recent, -> { order(created_at: :desc).limit(10) }
# Callbacks before_save :generate_slug
private
def generate_slug self.slug = title.parameterize endendLaravel Model:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Post extends Model{ protected $fillable = ['title', 'body', 'slug', 'published', 'user_id'];
// Relationships public function user() { return $this->belongsTo(User::class); }
public function comments() { return $this->hasMany(Comment::class); }
public function tags() { return $this->belongsToMany(Tag::class, 'post_tags'); }
// Scopes public function scopePublished($query) { return $query->where('published', true); }
public function scopeRecent($query) { return $query->orderBy('created_at', 'desc')->limit(10); }
// Model events protected static function booted() { static::saving(function ($post) { $post->slug = \Str::slug($post->title); }); }}- Compare query building
Rails:
# Find by IDpost = Post.find(1)
# Where clausesposts = Post.where(published: true) .order('created_at DESC') .limit(10)
# Eager loadingposts = Post.includes(:comments, :user).allLaravel:
<?php
declare(strict_types=1);
use App\Models\Post;
// Find by ID$post = Post::find(1);
// Where clauses$posts = Post::where('published', true) ->orderBy('created_at', 'desc') ->limit(10) ->get();
// Eager loading$posts = Post::with('comments', 'user')->get();Expected Result
Section titled “Expected Result”You should see that:
- Relationship methods use camelCase in Laravel (
hasManyvshas_many) - Scopes are methods prefixed with
scopein Laravel - Query building is nearly identical between frameworks
- Laravel requires explicit
->get()to execute queries
Why It Works
Section titled “Why It Works”Both frameworks implement the Active Record pattern where models directly correspond to database tables. The query builders use method chaining to construct SQL, making the APIs feel nearly identical despite being different languages.
::: tip Mass Assignment
Laravel requires you to define fillable or guarded properties for mass assignment protection, whereas Rails uses strong parameters in controllers with params.require().permit().
:::
Step 3: Translating Controllers (~10 min)
Section titled “Step 3: Translating Controllers (~10 min)”Learn how Rails controller actions map to Laravel controller methods.
Actions
Section titled “Actions”- Compare a typical RESTful controller
Rails:
class PostsController < ApplicationController before_action :authenticate_user!, except: [:index, :show] before_action :set_post, only: [:show, :edit, :update, :destroy]
def index @posts = Post.published.recent.page(params[:page]) end
def show end
def create @post = current_user.posts.build(post_params)
if @post.save redirect_to @post, notice: 'Post created successfully.' else render :new, status: :unprocessable_entity end end
private
def set_post @post = Post.find(params[:id]) end
def post_params params.require(:post).permit(:title, :body, :published) endendLaravel:
<?php
namespace App\Http\Controllers;
use App\Models\Post;use App\Http\Requests\StorePostRequest;
class PostController extends Controller{ public function __construct() { $this->middleware('auth')->except(['index', 'show']); }
public function index() { $posts = Post::published()->recent()->paginate(15); return view('posts.index', compact('posts')); }
public function show(Post $post) { // Route model binding automatically finds the post return view('posts.show', compact('post')); }
public function store(StorePostRequest $request) { $post = $request->user()->posts()->create($request->validated());
return redirect() ->route('posts.show', $post) ->with('success', 'Post created successfully.'); }}Expected Result
Section titled “Expected Result”Key differences you should notice:
- Laravel uses
middleware()instead ofbefore_action - Laravel has route model binding (no need for
set_post) - Validation happens in Form Request classes, not controllers
- Flash messages use
->with()instead ofnotice:
Why It Works
Section titled “Why It Works”Both frameworks follow the MVC pattern with controllers handling HTTP requests. Laravel’s dependency injection automatically resolves method parameters, enabling features like route model binding where Post $post automatically queries the database.
::: tip Route Model Binding
In Laravel, type-hinting a model in your controller method (Post $post) automatically queries the database using the route parameter. This eliminates Rails’ need for before_action :set_post filters. If the record isn’t found, Laravel automatically returns a 404 response.
:::
Troubleshooting
Section titled “Troubleshooting”-
Error: “Too few arguments to function” — Ensure your route parameter name matches the variable name in the controller method. For example, if your route is
posts/{post}, your method parameter should bePost $post, notPost $item. -
Error: “Class ‘App\Http\Controllers\Post’ not found” — You’re missing the
use App\Models\Post;import statement at the top of your controller file. -
Middleware not working — Verify the middleware is registered in
app/Http/Kernel.phpand that you’re using the correct middleware alias in your controller.
Step 4: Converting Templates (~8 min)
Section titled “Step 4: Converting Templates (~8 min)”Translate ERB template syntax to Blade template syntax.
Actions
Section titled “Actions”- Compare template syntax
Rails (ERB):
<h1>All Posts</h1>
<% if @posts.any? %> <ul> <% @posts.each do |post| %> <li> <%= link_to post.title, post_path(post) %> <% if post.published? %> <span class="badge">Published</span> <% end %> </li> <% end %> </ul><% else %> <p>No posts found.</p><% end %>Laravel (Blade):
{{-- resources/views/posts/index.blade.php --}}<h1>All Posts</h1>
@if($posts->count() > 0) <ul> @foreach($posts as $post) <li> <a href="{{ route('posts.show', $post) }}">{{ $post->title }}</a> @if($post->published) <span class="badge">Published</span> @endif </li> @endforeach </ul>@else <p>No posts found.</p>@endif- Compare common directives
| Feature | Rails (ERB) | Laravel (Blade) |
|---|---|---|
| Output (escaped) | Uses equals syntax | Uses double-brace syntax |
| Conditionals | Tag-based if/else/end | @if / @else / @endif |
| Loops | Block iteration with .each | @foreach / @endforeach |
| Comments | Hash-prefixed tags | Double-dash curly syntax |
| Partials | render helper | @include directive |
Key Similarities:
- Both escape output by default for XSS protection
- Both support layouts and content sections
- Both compile templates for performance
- Both provide raw/unescaped output options
Expected Result
Section titled “Expected Result”You should understand that:
- Blade uses at-sign directives (like
@if,@foreach) instead of ERB tag syntax - Both frameworks escape output by default for security
- Blade syntax is cleaner and more readable
- Variable access uses dollar-sign prefix in Blade
Why It Works
Section titled “Why It Works”Both templating engines compile to native code (Ruby/PHP) for performance. They provide the same security features (auto-escaping), layout inheritance, and partials—just with different syntax.
Step 5: Understanding Migrations (~7 min)
Section titled “Step 5: Understanding Migrations (~7 min)”Learn how Rails migrations translate to Laravel migrations.
Actions
Section titled “Actions”- Compare migration syntax
Rails:
class CreatePosts < ActiveRecord::Migration[7.0] def change create_table :posts do |t| t.string :title, null: false t.text :body t.string :slug, index: { unique: true } t.boolean :published, default: false t.references :user, null: false, foreign_key: true
t.timestamps end endendLaravel:
<?php
use Illuminate\Database\Migrations\Migration;use Illuminate\Database\Schema\Blueprint;use Illuminate\Support\Facades\Schema;
return new class extends Migration{ public function up() { Schema::create('posts', function (Blueprint $table) { $table->id(); $table->string('title'); $table->text('body')->nullable(); $table->string('slug')->unique(); $table->boolean('published')->default(false); $table->foreignId('user_id')->constrained()->cascadeOnDelete(); $table->timestamps(); }); }
public function down() { Schema::dropIfExists('posts'); }};- Compare migration commands
| Task | Rails | Laravel |
|---|---|---|
| Create migration | rails g migration AddXToY | artisan make:migration add_x_to_y |
| Run migrations | rake db:migrate | artisan migrate |
| Rollback | rake db:rollback | artisan migrate:rollback |
| Reset database | rake db:reset | artisan migrate:fresh |
Expected Result
Section titled “Expected Result”You should see that:
- Laravel uses
up()anddown()methods explicitly - Column modifiers chain in Laravel (
->nullable()->default()) - Foreign keys are more explicit in Laravel
- Commands are nearly identical
Why It Works
Section titled “Why It Works”Both frameworks provide a DSL for database schema management, allowing version control of database structure. The migration files track changes over time and can be rolled back, making database evolution safe and repeatable.
Step 6: Validation Approaches (~5 min)
Section titled “Step 6: Validation Approaches (~5 min)”Understand the key difference between Rails’ model-level validation and Laravel’s request-level validation.
Actions
Section titled “Actions”- Compare validation philosophies
Rails (Model Validation):
class Post < ApplicationRecord validates :title, presence: true, length: { minimum: 5, maximum: 200 } validates :email, format: { with: URI::MailTo::EMAIL_REGEXP } validates :slug, uniqueness: true validates :category, inclusion: { in: %w[tech business lifestyle] }endLaravel (Request Validation):
<?php
declare(strict_types=1);
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StorePostRequest extends FormRequest{ public function authorize(): bool { return true; // or check user permissions }
public function rules(): array { return [ 'title' => 'required|min:5|max:200', 'email' => 'required|email', 'slug' => 'required|unique:posts,slug', 'category' => 'in:tech,business,lifestyle', ]; }
public function messages(): array { return [ 'title.required' => 'Please provide a post title', 'title.min' => 'Title must be at least 5 characters', ]; }}Expected Result
Section titled “Expected Result”You should understand that:
- Rails validates at the model level before saving to database
- Laravel validates at the HTTP request level before reaching controllers
- Laravel’s approach separates validation logic from models
- Both provide similar validation rules with different syntax
Why It Works
Section titled “Why It Works”Laravel’s request validation approach decouples validation from models, allowing different validation rules for the same model depending on the context (create vs update, public API vs admin panel). Rails keeps validation closer to the model for consistency.
::: tip Validation Benefits Laravel Form Requests also handle authorization logic, allowing you to check if the user has permission to perform the action in the same class that validates the input. :::
Step 7: Command-Line Tools Comparison (~5 min)
Section titled “Step 7: Command-Line Tools Comparison (~5 min)”Map Rails CLI commands to Laravel Artisan equivalents.
Actions
Section titled “Actions”Rails CLI:
# Generate scaffoldingrails generate scaffold Post title:string body:textrails generate model Post title:stringrails generate controller Posts index show
# Database operationsrails db:migraterails db:seedrails db:rollback
# Console and serverrails consolerails serverLaravel Artisan:
# Generate scaffoldingphp artisan make:model Post -mcr # Model + Migration + Controller + Resourcephp artisan make:controller PostController --resourcephp artisan make:migration create_posts_table
# Database operationsphp artisan migratephp artisan db:seedphp artisan migrate:rollback
# Console and serverphp artisan tinkerphp artisan serveExpected Result
Section titled “Expected Result”You should see that Artisan provides the same code generation and database management capabilities as Rails, just with php artisan instead of rails.
Why It Works
Section titled “Why It Works”Both frameworks embrace code generation to boost productivity. Artisan’s make: commands parallel Rails’ generate commands, creating boilerplate files that follow framework conventions.
Wrap-up
Section titled “Wrap-up”You’ve completed a comprehensive mapping of Rails to Laravel concepts! Here’s what you’ve accomplished:
✅ Mapped routing patterns - Translated Rails routes to Laravel syntax with route model binding
✅ Compared ORMs - Understood ActiveRecord vs Eloquent similarities and query building
✅ Learned controller differences - Saw how Rails controllers map to Laravel with dependency injection
✅ Translated templates - Converted ERB to Blade syntax with cleaner directives
✅ Compared migrations - Understood database schema differences and chaining modifiers
✅ Understood validation - Compared model-level vs request-level validation approaches
✅ Learned command-line tools - Mapped rails commands to artisan equivalents
As a Rails developer, you now understand that Laravel isn’t fundamentally different—it’s the same concepts with different syntax and some architectural improvements. The main differences are:
- Syntax: PHP vs Ruby, but patterns remain familiar
- Architecture: Laravel’s service container and dependency injection
- Validation: Request-level in Laravel vs model-level in Rails
- Routing: Class-based controllers vs string-based in Rails
- Model Binding: Automatic in Laravel vs explicit
before_actionin Rails
You’re now ready to dive deeper into modern PHP features that make Laravel possible, and see how PHP 8.4 brings many features Rails developers already know and love.
Exercises
Section titled “Exercises”Exercise 1: Translate a Rails Model to Laravel
Section titled “Exercise 1: Translate a Rails Model to Laravel”Goal: Practice translating Rails ActiveRecord patterns to Laravel Eloquent
Create a Laravel model equivalent to this Rails model:
class Post < ApplicationRecord belongs_to :user has_many :comments validates :title, presence: true scope :published, -> { where(published: true) }endYour Laravel model should include:
- The same relationships (belongsTo, hasMany)
- A published scope method
- Proper namespace and type hints
Validation: Test your implementation:
$post = Post::factory()->create(['published' => true]);
// Test relationship$this->assertInstanceOf(User::class, $post->user);
// Test scope$published = Post::published()->get();Expected result: A working Laravel model that mirrors the Rails model’s functionality.
Further Reading
Section titled “Further Reading”- Laravel Routing Documentation - Official routing guide
- Eloquent ORM Documentation - Complete Eloquent reference
- Laravel Migrations - Database migration guide
- Blade Templates - Blade templating documentation
- Ruby on Rails Guides - Rails official documentation for comparison
::: tip Continue Learning Move on to Chapter 02: Modern PHP: What’s Changed to learn about modern PHP 8.4 features. :::