Laravel to Rails: are they realy that similar?
A personal opinion from you avarage PHP developer
Why this analysis?
Having used Laravel for a while, I am familiar with people saying that Laravel is 'the PHP version of Rails'—so I tested this out.
First steps (before Rails)
Since I am an impatient person, I tried to jump right into Rails. Big mistake.
Ruby has its similarities with other common programming languages (like PHP, JS, or Python), but some concepts and syntax can be pretty confusing for most PHP programmers at first glance, like Symbols, using @ instead of $this, and a lot of implicit stuff.
These concepts and differences might seem trivial, but they add up quickly within a framework. So, I read a little bit of the official Ruby documentation, wrote some simple algorithms to practice the syntax, and then jumped right in to build a simple Rails project (this blog).
Starting with Rails (finally)
I opened the documentation right into the first steps tutorial.
Downloaded the Rails CLI and started a project with Docker for future deployment, Tailwind for styling and PostgreSQL since this is my default database, the Rails CLI is pretty useful, I generated the template for the project in a single command:
rails new my_app --css=tailwind --database=postgresql --devcontainer
# enter on our project
cd my_app
There is some other config to be done, like I used Docker Compose, k8s and Redis, but there is a lot of official documentation explaining how to config this and this post isn't about DevOps.
Since this project is not about CSS frameworks I let most of the frontend code be built using out dear friend Copilot.
Rails is a MVC (Model, View and Controller) framework and me (and probably you) already know what that is, so let's start where the fun begins: How do I create my first Model, Controller and Views ?
In Rails you can create these using individual commands, but i opted to use a single command:
rails generate scaffold my_model
or
rails generate scaffold MyModel
this single command generates:
- Controller (
my_models_controller.rb) withshow,index,create,store,updateandsavemethods; - Model (
my_model.rb) with the name MyModel; - Database migration (
<timestamp>_create_my_models.rb); - Views
edit.html.erb,index.json.jbuilder,_my_model.json.jbuilder,_form.html.erb,new.html.erb,show.html.erb,index.html.erb,_my_model.html.erbandshow.json.jbuilder - In the routes file (
routes.rb) it addsresources :my_modelswhich creates all the necessary routes for the controller. - Test file for the controller (
my_models_controller_test.rb). - Test file for the model (
my_model_test.rb).
This is pretty cool, it creates a lot of boilerplate code for you, this is the definition of 'convention over configuration'.
Models
In Laravel, we have Eloquent models and Active Record in Rails is pretty similar, just with the Ruby magic and syntax.
Some examples using posts and users, including some of the most common features of Laravel models, like fillable, hidden, rules and casts:
namespace App\Models;
// Our well known Laravel model
class User extends Model
{
protected $fillable = ['name', 'email', 'password'];
protected $hidden = ['password'];
protected $rules = [
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:8|confirmed',
];
protected $casts = [
'password' => 'hashed',
];
public function posts()
{
return $this->hasMany(Post::class);
}
}
namespace App\Models;
// Our well known Laravel model
class Post extends Model
{
protected $rules = [
'title' => 'required|string|max:255',
'content' => 'required|string',
'user_id' => 'required|integer|exists:users,id',
];
protected $fillable = ['title', 'content', 'user_id'];
public function user()
{
return $this->belongsTo(User::class);
}
}
now, let's see how this looks in Rails (read the comments for the explanations):
# Our Rails model, it inherits from ApplicationRecord which is the base class for all models in Rails, it provides a lot of functionality out of the box, like database interactions, validations, relationships, etc.
class User < ApplicationRecord
# validates is a class method (you can call it inside the class but outside of any instance method in Ruby) that takes a symbol (the name of the attribute) and a hash of options (the validation rules)
validates :name, presence: true, length: { maximum: 255 }
# URI::MailTo::EMAIL_REGEXP is a constant, Ruby uses :: to access constants, this would be the equivalent of \Illuminate\Support\Str::emailRegex() in Laravel, it is a regular expression that validates email format
validates :email, presence: true, length: { maximum: 255 }, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP }
validates :password, presence: true, length: { minimum: 8 }, confirmation: true
# has_many is also a class method that defines a relationship between the User model and the Post model, it takes a symbol (the name of the relationship) and it will create methods to access the related posts (like user.posts)
has_many :posts
end
Special comparisions:
- Class Method: In PHP, you cannot call a class method inside the class definition, but in Ruby you can and Rails use this for defining validations and relationships. This is why we can call
validatesinside the class definition in Rails. - Methods In Gereral: With PHP you must call a method so it can be executed, but in Ruby you can call a method without parentheses and it will be executed, this is why we can call
has_many :postswithout parentheses in Rails. - Modules: In Ruby, you can use modules to group related methods and constants, this is similar to traits in PHP, but they are more powerful and flexible, you can include a module in a class to add its methods and constants to the class, this is how Rails implements some of its features like validations and relationships.
Lets see how the Post model looks in Rails:
class Post < ApplicationRecord
validates :title, presence: true, length: { maximum: 255 }
validates :content, presence: true
validates :user_id, presence: true, numericality: { only_integer: true },inclusion: { in: User.pluck(:id) }
belongs_to :user
end
After some explanations, you can see that the Rails models are pretty similar to Laravel's Eloquent models, but with some differences in syntax and conventions.
Controllers
Again, Controllers are pretty similiar between languages and frameworks, just with some Ruby magic, let's see how the PostsController looks in Laravel:
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\Request;
class PostController extends Controller
{
public function index()
{
$posts = Post::all();
return view('posts.index', compact('posts'));
}
public function create()
{
return view('posts.create');
}
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|string|max:255',
'content' => 'required|string',
'user_id' => 'required|integer|exists:users,id',
]);
Post::create($validated);
return redirect()->route('posts.index');
}
public function show(Post $post)
{
return view('posts.show', compact('post'));
}
public function edit(Post $post)
{
return view('posts.edit', compact('post'));
}
public function update(Request $request, Post $post)
{
$validated = $request->validate([
'title' => 'required|string|max:255',
'content' => 'required|string',
'user_id' => 'required|integer|exists:users,id',
]);
$post->update($validated);
return redirect()->route('posts.index');
}
public function destroy(Post $post)
{
$post->delete();
return redirect()->route('posts.index');
}
}
And now let's see how it looks in Rails:
class PostsController < ApplicationController
def index
# @ is the same as $this-> in PHP, it is used to define instance variables that can be accessed in the views, in this case we are defining @posts which will contain all the posts from the database, since rails uses the attribute name on the controller to pass data to the view, we can access @posts in the index.html.erb view
@posts = Post.all
# rails automatically renders the view that corresponds to the action name, in this case it will render index.html.erb, so we don't need to explicitly return a view like in Laravel, but if we want to render a different view we can use the render method, for example: render 'posts/index'
end
def new
# defines a empty post to be used in the form, this is similar to creating a new Post instance in Laravel and passing it to the view, in Rails we can just define @post and it will be available in the new.html.erb view
@post = Post.new
end
def create
# post_params is a private method (you don't need to use '()' for calling) that we will define later, it is used to whitelist the parameters that we want to allow for mass assignment, this is similar to the $fillable property, validation or a custom Request in Laravel, we are creating a new Post instance with the post_params and assigning it to @post
@post = Post.new(post_params)
# saving in the database, exactly like in Laravel
if @post.save
redirect_to posts_path
else
render :new
end
end
def show
# Again, exactly like in Laravel, finding by id
@post = Post.find(params[:id])
end
def edit
@post = Post.find(params[:id])
end
def update
@post = Post.find(params[:id])
if @post.update(post_params)
redirect_to posts_path
else
render :edit
end
end
def destroy
@post = Post.find(params[:id])
@post.destroy
redirect_to posts_path
end
private
def post_params
params.require(:post).permit(:title, :content, :user_id)
end
end
The controllers are also pretty similar, but with some differences in syntax, conventions, amount of code written (Rails is more concise) and the Rails way of dealing with validations, rendering views and handling parameters.
Views
A really simple view in Laravel would look like this:
<!-- resources/views/posts/index.blade.php -->
@extends('layouts.app')
@section('content')
<h1>Posts</h1>
<ul>
@foreach ($posts as $post)
<li><a href="{{ route('posts.show', $post) }}">{{ $post->title }}</a></li>
@endforeach
</ul>
@endsection
And the same view in Rails would look like this:
<!-- app/views/posts/index.html.erb -->
<h1>Posts</h1>
<ul>
<% @posts.each do |post| %>
<li><a href="<%= post_path(post) %>"><%= post.title %></a></li>
<% end %>
</ul>
The views are also pretty similar, but with some differences in syntax and conventions, like using <% %> for Ruby code and <%= %> for outputting values, and the fact that Rails uses ERB as its default templating engine while Laravel uses Blade.
Routes are called using some Rails 'Magic' like you can se in post_path(post), which we never defined, but Rails automatically generates it based on the controller and action names, this is part of the 'convention over configuration' philosophy of Rails.
Conclusion
Overall, Laravel and Rails are both powerful web frameworks that follow the MVC architecture and have a lot of similarities in terms of features and functionality. However, they have some differences in syntax, conventions, and the way they handle certain aspects of web development. Laravel is more verbose and explicit, while Rails is more concise and relies on conventions and 'magic' to reduce the amount of code you need to write.
I particularly enjoyed the Rails way of doing things, it is more elegant and less verbose than Laravel, but it can be a bit confusing (and i imagine it being really confusing with bad code and large projects) if you are not familiar with the conventions and the 'magic' of Rails. Laravel is more straightforward and easier to understand for beginners, but it can be a bit more verbose and less elegant than Rails.
If you want to see the full code of the project, you can check it here on my github.