Sobre nós Guias Projetos Contactos
Админка
please wait

O Ruby on Rails continua a ser um dos frameworks web mais produtivos para criar rapidamente aplicações full-stack. A sua filosofia de convention-over-configuration, combinada com um ecossistema rico de gems, torna-o ideal tanto para startups como para empresas já estabelecidas. Este guia aborda a criação de aplicações Rails prontas para produção, na perspetiva de um developer sénior.

Porquê Ruby on Rails

O Rails destaca-se em várias áreas:

  1. Produtividade do developer: Gerar boilerplate com um único comando
  2. Convention over configuration: Valores por defeito sensatos reduzem a fadiga de decisão
  3. Ecossistema maduro: Gems para praticamente todos os casos de uso comuns
  4. Integração full-stack: Backend, frontend e base de dados num único framework
  5. Active Record: ORM intuitivo com uma interface de queries poderosa

Configurar um projeto Rails

Instalar Ruby e Rails:

# A usar rbenv (recomendado)
rbenv install 3.2.0
rbenv global 3.2.0
gem install rails

Criar um novo projeto:

rails new my_app
cd my_app
rails server

Abra http://localhost:3000 para ver a página de boas-vindas.

Estrutura do projeto

O Rails segue uma estrutura MVC clara:

my_app/
├── app/
│ ├── controllers/ # Tratar pedidos
│ ├── models/ # Lógica de negócio e dados
│ ├── views/ # Templates
│ ├── helpers/ # Helpers de view
│ ├── assets/ # CSS, JS, imagens
│ └── jobs/ # Background jobs
├── config/
│ ├── routes.rb # Routing de URL
│ └── database.yml # Configuração da base de dados
├── db/
│ ├── migrate/ # Migrações da base de dados
│ └── schema.rb # Schema atual
├── lib/ # Bibliotecas personalizadas
├── spec/ or test/ # Testes
└── Gemfile # Dependências

Migrações de base de dados

Criar tabelas

Gerar uma migração:

rails generate migration CreatePosts

Editar db/migrate/[timestamp]createposts.rb:

class CreatePosts < ActiveRecord::Migration[7.0]
def change
create_table :posts do |t|
t.string :title, null: false
t.text :body
t.boolean :published, default: false
t.references :user, null: false, foreign_key: true
t.timestamps
end
add_index :posts, :published
end
end

Executar as migrações:

rails db:migrate

Adicionar colunas

class AddViewCountToPosts < ActiveRecord::Migration[7.0]
def change
add_column :posts, :view_count, :integer, default: 0
end
end

Models e Active Record

Definir models

Criar app/models/post.rb:

class Post < ApplicationRecord
# Associações
belongs_to :user
has_many :comments, dependent: :destroy
has_many :post_tags
has_many :tags, through: :post_tags
# Validações
validates :title, presence: true, length: { minimum: 3, maximum: 255 }
validates :body, presence: true
validates_uniqueness_of :slug
# Scopes
scope :published, -> { where(published: true) }
scope :recent, -> { order(created_at: :desc) }
scope :by_author, ->(user) { where(user: user) }
# Callbacks
before_save :generate_slug
private
def generate_slug
self.slug = title.parameterize if slug.blank?
end
end

Associações many-to-many

# app/models/post.rb
class Post < ApplicationRecord
has_many :post_tags
has_many :tags, through: :post_tags
end
# app/models/tag.rb
class Tag < ApplicationRecord
has_many :post_tags
has_many :posts, through: :post_tags
end
# app/models/post_tag.rb
class PostTag < ApplicationRecord
belongs_to :post
belongs_to :tag
end

Consultar dados

# Encontrar todos os registos
Post.all
# Encontrar por ID
Post.find(1)
# Encontrar com condições
Post.where(published: true)
Post.where("created_at > ?", 1.week.ago)
# Encadear scopes
Post.published.recent.limit(10)
# Eager loading para evitar queries N+1
Post.includes(:user, :comments).where(published: true)
# Queries complexas
Post.joins(:user)
.where(users: { role: 'admin' })
.select('posts.*, users.name as author_name')
# Agregações
Post.group(:user_id).count
Post.average(:view_count)

Controllers

Controllers RESTful

Criar app/controllers/posts_controller.rb:

class PostsController < ApplicationController
before_action :authenticate_user!, except: [:index, :show]
before_action :set_post, only: [:show, :edit, :update, :destroy]
before_action :authorize_author!, only: [:edit, :update, :destroy]
def index
@posts = Post.published.recent.includes(:user).page(params[:page])
end
def show
@post.increment!(:view_count)
@comments = @post.comments.includes(:user).order(created_at: :desc)
end
def new
@post = current_user.posts.build
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
def edit
end
def update
if @post.update(post_params)
redirect_to @post, notice: 'Post updated successfully.'
else
render :edit, status: :unprocessable_entity
end
end
def destroy
@post.destroy
redirect_to posts_path, notice: 'Post deleted.'
end
private
def set_post
@post = Post.find(params[:id])
end
def post_params
params.require(:post).permit(:title, :body, :published, tag_ids: [])
end
def authorize_author!
unless @post.user == current_user
redirect_to @post, alert: 'You can only edit your own posts.'
end
end
end

Routing

Configurar routes em config/routes.rb:

Rails.application.routes.draw do
# Recursos RESTful
resources :posts do
resources :comments, only: [:create, :destroy]
member do
post :publish
post :unpublish
end
collection do
get :search
end
end
# Recursos aninhados
resources :users do
resources :posts, only: [:index]
end
# Routes personalizadas
get 'about', to: 'pages#about'
get 'contact', to: 'pages#contact'
# Namespace de API
namespace :api do
namespace :v1 do
resources :posts, only: [:index, :show, :create]
end
end
# Route raiz
root 'posts#index'
end

Views e templates

Templates ERB

Criar app/views/posts/index.html.erb:

<h1>Posts</h1>
<div class="posts">
<% @posts.each do |post| %>
<article class="post">
<h2><%= link_to post.title, post %></h2>
<p class="meta">
By <%= post.user.name %> on <%= post.created_at.strftime("%B %d, %Y") %>
</p>
<p><%= truncate(post.body, length: 200) %></p>
<% if current_user == post.user %>
<div class="actions">
<%= link_to 'Edit', edit_post_path(post) %>
<%= link_to 'Delete', post, method: :delete,
data: { confirm: 'Are you sure?' } %>
</div>
<% end %>
</article>
<% end %>
</div>
<%= paginate @posts %>
<% if user_signed_in? %>
<%= link_to 'New Post', new_post_path, class: 'btn btn-primary' %>
<% end %>

Partials

Criar app/views/posts/_form.html.erb:

<%= form_with model: @post, local: true do |f| %>
<% if @post.errors.any? %>
<div class="alert alert-danger">
<h4><%= pluralize(@post.errors.count, "error") %> prevented saving:</h4>
<ul>
<% @post.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="form-group">
<%= f.label :title %>
<%= f.text_field :title, class: 'form-control' %>
</div>
<div class="form-group">
<%= f.label :body %>
<%= f.text_area :body, class: 'form-control', rows: 10 %>
</div>
<div class="form-check">
<%= f.check_box :published, class: 'form-check-input' %>
<%= f.label :published, class: 'form-check-label' %>
</div>
<div class="form-group">
<%= f.label :tags %>
<%= f.collection_check_boxes :tag_ids, Tag.all, :id, :name %>
</div>
<%= f.submit class: 'btn btn-primary' %>
<% end %>

Autenticação com bcrypt

Configurar o model User

Adicionar ao Gemfile:

gem 'bcrypt', '~> 3.1.7'

Gerar a migração de user:

rails generate migration CreateUsers
class CreateUsers < ActiveRecord::Migration[7.0]
def change
create_table :users do |t|
t.string :email, null: false
t.string :name, null: false
t.string :password_digest, null: false
t.timestamps
end
add_index :users, :email, unique: true
end
end

Criar app/models/user.rb:

class User < ApplicationRecord
has_secure_password
has_many :posts, dependent: :destroy
validates :email, presence: true, uniqueness: true,
format: { with: URI::MailTo::EMAIL_REGEXP }
validates :name, presence: true
validates :password, length: { minimum: 8 }, allow_nil: true
end

Session Controller

Criar app/controllers/sessions_controller.rb:

class SessionsController < ApplicationController
def new
end
def create
user = User.find_by(email: params[:email])
if user && user.authenticate(params[:password])
session[:user_id] = user.id
redirect_to root_path, notice: 'Signed in successfully.'
else
flash.now[:alert] = 'Invalid email or password.'
render :new, status: :unprocessable_entity
end
end
def destroy
session[:user_id] = nil
redirect_to root_path, notice: 'Signed out.'
end
end

Helpers no Application Controller

Adicionar a app/controllers/application_controller.rb:

class ApplicationController < ActionController::Base
helper_method :current_user, :user_signed_in?
def current_user
@current_user ||= User.find(session[:user_id]) if session[:user_id]
end
def user_signed_in?
!!current_user
end
def authenticate_user!
unless user_signed_in?
redirect_to new_session_path, alert: 'Please sign in to continue.'
end
end
end

Adicionar frontend com React ou Vue

Rails com React

Adicionar ao Gemfile:

gem 'react-rails'

Gerar a instalação do React:

rails generate react:install

Criar um componente React em app/javascript/components/PostList.jsx:

import React, { useState, useEffect } from 'react';
const PostList = ({ initialPosts }) => {
const [posts, setPosts] = useState(initialPosts || []);
const [loading, setLoading] = useState(!initialPosts);
useEffect(() => {
if (!initialPosts) {
fetch('/api/v1/posts')
.then(response => response.json())
.then(data => {
setPosts(data);
setLoading(false);
});
}
}, []);
if (loading) return <div>Loading...</div>;
return (
<div className="post-list">
{posts.map(post => (
<article key={post.id} className="post">
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</div>
);
};
export default PostList;

Usar na view ERB:

<%= react_component("PostList", { initialPosts: @posts.as_json }) %>

Background jobs

Criar um job:

rails generate job ProcessImage

Editar app/jobs/processimagejob.rb:

class ProcessImageJob < ApplicationJob
queue_as :default
retry_on ActiveStorage::IntegrityError, wait: 5.seconds, attempts: 3
discard_on ActiveRecord::RecordNotFound
def perform(post_id)
post = Post.find(post_id)
# Processar imagem
post.featured_image.variant(resize_to_limit: [800, 600]).processed
# Atualizar post
post.update(image_processed: true)
end
end

Enfileirar o job:

ProcessImageJob.perform_later(@post.id)
# Com atraso
ProcessImageJob.set(wait: 5.minutes).perform_later(@post.id)

Testes

Testes de model

Criar spec/models/post_spec.rb:

require 'rails_helper'
RSpec.describe Post, type: :model do
describe 'validations' do
it { should validate_presence_of(:title) }
it { should validate_length_of(:title).is_at_least(3) }
end
describe 'associations' do
it { should belong_to(:user) }
it { should have_many(:comments).dependent(:destroy) }
end
describe 'scopes' do
let!(:published_post) { create(:post, published: true) }
let!(:draft_post) { create(:post, published: false) }
it 'returns only published posts' do
expect(Post.published).to include(published_post)
expect(Post.published).not_to include(draft_post)
end
end
end

Principais conclusões

  1. Convention over configuration: Siga as convenções do Rails para maior produtividade
  2. Fat models, skinny controllers: A lógica de negócio pertence aos models
  3. Use scopes: Mantenha as queries reutilizáveis e legíveis
  4. Eager loading: Evite queries N+1 com includes
  5. Strong parameters: Faça sempre whitelist dos atributos permitidos
  6. Background jobs: Descarregue tarefas lentas para uma melhor UX

O Rails continua a ser excelente para o desenvolvimento rápido de aplicações web completas, especialmente quando o time-to-market é importante.

 
 
 
Языки
Темы
Copyright © 1999 — 2026
ZK Interactive