Agenda
- Introducción
- Instalación, configuración
- Estructura de un proyecto Rails
- Routes, Controllers, Views
- ActiveRecord (introducción)
- Aplicación de ejemplo (parte 1)
Ruby on Rails por dentro
- Active Record
- Active Model
- Action Controller
- Action View
- Action Mailer
- Action Cable
- Active Support
- Railties
The Rails Doctrine
- Optimize for programmer happiness
- Convention over Configuration
- The menu is omakase
- No one paradigm
- Exalt beautiful code
- Provide sharp knives
- Value integrated systems
- Progress over stability
- Push up a big tent
Optimize for programmer happiness
10.days.ago
Date.tomorrow
User.find_by_name_and_surname
Convention over Configuration
class User < ApplicationRecord
has_many :posts
end
The menu is omakase
Rails tiene opiniones fuertes...
- jQuery, CoffeeScript, SASS
- ActionCable (websockets)
- Turbolinks
- Minitest > RSpec
- Fixtures > Factories
- ...
Exalt beautiful code
class Project < ApplicationRecord
belongs_to :account
has_many :participants, class_name: 'Person'
validates_presence_of :name
end
if people.include? person
if person.in? people
Instalación, configuración
$ gem install rails --no-document
34 gems installed
$ rails --version
Rails 5.2.3
$ rails new demo_blog
create README.md
create Rakefile
create .ruby-version
...
$ rails s
=> Booting Puma (...)
* Listening on tcp://localhost:3000
Estructura de un proyecto Rails
Rails scaffolds
$ rails g controller --help
$ rails g controller NAME [action ...] [options]
Otros generators:
- model
- migration
- resource
- scaffold
# app/controllers/posts_controller.rb
class PostsController < ApplicationController
def index
end
end
# app/views/posts/index.html.erb
<h1 class='title'>Blog index</h1>
# config/routes.rb
Rails.application.routes.draw do
resources :posts
root 'posts#index'
end
Routes (docs)
$ rails routes
Prefix Verb URI Pattern Controller#Action
posts GET /posts posts#index
POST /posts posts#create
new_post GET /posts/new posts#new
edit_post GET /posts/:id/edit posts#edit
post GET /posts/:id posts#show
PATCH /posts/:id posts#update
PUT /posts/:id posts#update
DELETE /posts/:id posts#destroy
root GET / posts#index
Routes: más opciones
Restricting the Routes Created (docs)
resources :posts, only: [:index, :show]
resources :posts, except: [:destroy]
Routes: más opciones
Controller Namespaces and Routing (docs)
namespace :admin do
resources :users
end
Helper users => admin_users
Request GET /users => GET /admin/users
Controller users#index => admin/users#index
Routes: más opciones
Nested resources (docs)
resources :posts do
resources :comments, only: [:create, :destroy]
end
post_comments POST /posts/:post_id/comments comments#create
post_comment DELETE /posts/:post_id/comments/:id comments#destroy
Routes: más opciones
Adding More RESTful Actions (docs)
resources :articles do
get 'pinned', on: :collection
member do
get 'likes'
end
end
pinned_articles GET /articles/pinned articles#pinned
likes_article GET /articles/:id/likes articles#likes
Routes: más opciones
Non-Resourceful Routes (docs)
get 'posts/top' # PhotosController#top
get 'photos(/:id)', to: :display # PhotosController#display, /:id opcional
get 'photos/:id/:user_id', to: 'photos#show' # params[:id], params[:user_id]
get 'exit', to: 'sessions#destroy', as: :logout
# logout_path => "/exit"
Migraciones (docs)
$ rails g model Post title:string content:text
create db/migrate/20190724032006_create_posts.rb
create app/models/post.rb
class Post < ApplicationRecord
end
Migraciones
class CreatePosts < ActiveRecord::Migration[5.2]
def change
create_table :posts do |t|
t.string :title
t.text :content
t.timestamps
end
end
end
$ rails db:migrate
== 20190724032006 CreatePosts: migrating ======================================
-- create_table(:posts) -> 0.0011s
Migraciones: otros comandos
$ rails -T db
rails db:create # Creates the database from config/database.yml [RAILS_ENV]
rails db:drop # Drops the database from config/database.yml [RAILS_ENV]
rails db:migrate # Migrate the database [RAILS_ENV, VERSION]
rails db:migrate:status # Display status of migrations
rails db:rollback # Rolls the schema back to the previous version [STEP=n]
rails db:schema:dump # Creates a db/schema.rb file
rails db:schema:load # Loads a schema.rb file into the database
rails db:seed # Loads the seed data from db/seeds.rb
rails db:setup # Creates the database, loads the schema, and seeds the data
rails db:reset # Idem db:setup, but drop the database first
rails db:version # Retrieves the current schema version number
PostsController
class PostsController < ApplicationController
def index
end
def new
@post = Post.new
end
def create
@post = Post.new(post_params)
if @post.save
redirect_to @post
else
render :new
end
end
private
def post_params
params.require(:post).permit(:title, :content)
end
end
Gemas
# en Gemfile
# Bulma CSS
gem 'bulma-rails', '~> 0.7.5'
# Simple forms
gem 'simple_form', '~> 4.1'
Instalamos, inicializamos simple_form y reiniciamos el server
$ bundle install
$ rails generate simple_form:install
Agregamos @import "bulma";
a application.css
Form (forma tradicional)
<h1>New Post</h1>
<%= form_for @post do |f| %>
<label for="title">Title</label>
<div><%= f.text_field :title %></div>
<label for="title">Content</label>
<div><%= f.text_area :content %></div>
<%= f.submit 'Create new post' %>
<% end %>
Form (Simple Form + Bulma)
<h1 class="title">New Post</h1>
<div class="section">
<%= simple_form_for @post do |f| %>
<div class="field">
<div class="control">
<%= f.input :title, wrapper: false,
input_html: {class: 'input'}, label_html: {class: 'label'} %>
</div>
</div>
<div class="field">
<div class="control">
<%= f.input :content, wrapper: false,
input_html: {class: 'textarea'}, label_html: {class: 'label'} %>
</div>
</div>
<%= f.button :submit, 'Create new post', class: 'button is-primary' %>
<% end %>
</div>
Show
def show
@post = Post.find(params[:id])
end
<section class="section">
<div class="container">
<h1 class="title"><%= @post.title %></h1>
<div class="content"><%= @post.content %></div>
</div>
</section>
Index
def index
@posts = Post.all.order("created_at DESC")
end
<% @posts.each do |post| %>
<div class="card">
<div class="card-content">
<div class="media">
<div class="media-content">
<p class="title is-4"><%= link_to post.title, post %></p>
</div>
</div>
<div class="content"> <%= post.content %> </div>
</div>
</div>
<% end %>
content_for
# en el layout
<%= yield :page_title %>
# en el partial
<% content_for :page_title, @post.title %>
Edit/Update
def edit
@post = Post.find(params[:id])
end
def update
@post = Post.find(params[:id])
if @post.update(post_params)
redirect_to @post
else
render :edit
end
end
<%= link_to "Edit", edit_post_path(@post), class: "button" %>
DRY (Don't Repeat Yourself)
Creamos un partial: app/views/posts/_form.html.erb
Lo llamamos desde new & edit
<%= render 'form' %>
Delete
@post = Post.find(params[:id])
@post.destroy
<%= link_to "Delete", post_path(@post), method: :delete, data: {confirm: "Are you sure?"} %>
DRY II
class PostsController < ApplicationController
before_action :set_post, only: [:show, :edit, :update, :destroy]
private
def set_post
@post = Post.find(params[:id])
end
end
Comments
$ rails g controller comments
$ rails g model Comment name:string comment:text post:references
$ rails db:migrate
class Post < ApplicationRecord
has_many :comments
end
class Comment < ApplicationRecord
belongs_to :post
end
# config/routes.rb
resources :posts do
resources :comments, only: [:create, :destroy]
end
Comments
class CommentsController < ApplicationController
def create
@post = Post.find(params[:post_id])
@comment = @post.comments.create(params[:comment].permit(:name, :comment))
redirect_to post_path(@post)
end
end
<%= simple_form_for [@post, @post.comments.build] do |f| %>
<%= f.input :name, wrapper: false, input_html: {class: 'input'} %>
<%= f.input :comment, wrapper: false, input_html: {class: 'textarea'} %>
<%= f.button :submit, class: 'button is-primary' %>
<% end %>
Extras...
Gema útil para desarrollo:
# Provides a better error page for Rails
gem 'better_errors', '~> 2.5', '>= 2.5.1'
Importante: Luego de editar el Gemfile hay que ejecutar bundle install
y reiniciar el server
Live Reload
- Agregar
guard
yguard-livereload
alGemfile
group :development do
# Guard is a command line tool to easily handle events on file system modifications.
gem 'guard', '~> 2.15'
# reload the browser after changes to assets/helpers/tests
gem 'guard-livereload', '~> 2.5', '>= 2.5.2', require: false
end
- Instalar la extensión de LiveReload
- Correr
bundle exec guard
en una nueva terminal