Crea un clon de Instagram desde cero
La interfaz de línea de comandos, terminal o consola es un método que permite a los usuarios dar instrucciones a algún programa informático por medio de una línea de texto simple.
Lo primero que debemos saber es que el terminal se utiliza para dar órdenes al sistema mediante unas palabras llamadas comandos de Linux. Estos comandos pueden servir para muchas cosas, como por ejemplo: copiar archivos, comprimir carpetas, reproducir música, descargar archivos, etc.
Para que nosotros sepamos que el sistema está preparado para recibir órdenes, en el terminal aparecerá una línea de texto llamada prompt
$
Entonces cada vez que vas a encontrar referencia a la consola
en la guía, solo copia el comando y ejecútalo en la consola.
Encontrarás referencias como la siguiente para ubicar el archivo que tienes que cambiar
app/views/pages/home.html.erb
Con instrucciones y el código/contenido que tienes que agregar o cambiar
[...]
código/contenido
[...]
consola
rails new instagram
cd instagram/
bundle install
Una vez que tenemos la primera parte lista podemos ejecutar un nuevo comando en la consola:
rails server
Eso va a arrancar nuestro servidor de aplicación.
Deberías ver algo parecido a:
=> Booting Puma
=> Rails 6.0.0 application starting in development
=> Run `rails server --help` for more startup options
Puma starting in single mode...
* Version 3.12.1 (ruby 2.6.2-p47), codename: Llamas in Pajamas
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://localhost:3000
Use Ctrl-C to stop
Que significa que nuestra aplicacion ya esta corriendo en el navegador.
En el navegador, ve a la URL: (https://localhost-[user-name].paiza-user.cloud:3000/) . Esta es la página de inicio por defecto para las aplicaciones Rails.
La página “Yay! You’re on Rails!” es la primera prueba para una nueva aplicación Rails. Ésta asegura que tienes el software configurado correctamente para servir una página.
Para crear nuestra nueva página, necesitas crear como mínimo un controlador y una vista.
El propósito de un controlador es recibir las peticiones (requests) de la aplicación. El enrutamiento (routing) decide qué controlador recibe qué petición.
A menudo, hay más de una ruta para cada controlador, y diferentes rutas pueden ser servidas por diferentes acciones (actions). El propósito de cada acción es obtener información para pasarla después a la vista.
El propósito de una vista es mostrar la información en un formato legible para los humanos. Una distinción importante que hacer, es que es el controlador, y no la vista, donde la información es recolectada. La vista sólo debería mostrar la información. Por defecto, las plantillas de las vistas están escritas en un lenguaje llamado ERB (del inglés, Embedded Ruby), que se procesa automáticamente para cada petición servida por Rails.
Para crear un nuevo controlador, necesitas ejecutar el generador de controladores y decirle que quieres un controlador llamado por ejemplo pages
con una acción llamada home
. Para ello, ejecuta lo siguiente:
NOTA El primer tab de tu consola ya tiene el servidor de Rails corriendo que lanzaste con el comando:
rails server
Ahora tienes que abrir un nuevo tab.
En el terminal aparecerá un nuevo prompt $
donde puedes ejecutar:
consola
rails generate controller pages home
Rails creará una serie de archivos y añadirá una ruta por ti.
create app/controllers/pages_controller.rb
route get 'pages/home'
invoke erb
create app/views/pages
create app/views/pages/home.html.erb
invoke test_unit
create test/controllers/pages_controller_test.rb
invoke helper
create app/helpers/pages_helper.rb
invoke test_unit
invoke assets
invoke coffee
create app/assets/javascripts/pages.coffee
invoke scss
create app/assets/stylesheets/pages.scss
Los archivos más importantes de éstos son por supuesto el controlador, que se encuentra en app/controllers/pages_controller.rb y la vista, que se encuentra en app/views/pages/home.html.erb.
Abre el archivo app/views/pages/home.html.erb en tu editor de texto y edítalo para que contenga sólo está línea de código:
<h1>¡Bienvenidos a Instagram!</h1>
En tu navegador, ve a la URL : (https://localhost-[user-name].paiza-user.cloud:3000//pages/home) y ve la nueva página que se acaba de crear.
¿Cómo conectar la raíz de tu sitio a un controlador y acción específica?
Debemos configurar la ruta de acceso a la aplicación editando el archivo config/routes.rb
, para esto debemos cambiar la siguiente línea en el mismo:
config/routes.rb
Reemplazar la línea
get 'pages/home'
…por:
root 'pages#home'
app/controllers/pages_controller.rb
class PagesController < ApplicationController
def home
end
def about
end
end
app/views/pages/about.html.erb
<h1>¿Quién soy?</h1>
<p>¡Una Rails Girl creando mi propia app de Instagram!</p>
config/routes.rb
Debajo de
root 'pages#home'
Añade:
get 'about' => 'pages#about'
“Las gemas en Ruby son las bibliotecas o paquetes (código Ruby empaquetado de una manera predeterminada) de software que se instalan en el sistema para aumentar las funcionalidades del interprete”.
Bootstrap es una biblioteca multiplataforma o conjunto de herramientas de código abierto para diseño de sitios y aplicaciones web. Contiene plantillas de diseño con tipografía, formularios, botones, cuadros, menús de navegación y otros elementos de diseño basado en HTML y CSS, así como extensiones de JavaScript adicionales.
Sass (acrónimo de Syntactically Awesome StyleSheets) es una extensión de CSS que añade características muy potentes y elegantes a este lenguaje de estilos. Sass permite el uso de variables, reglas CSS anidadas, mixins, importación de hojas de estilos y muchas otras características, al tiempo que mantiene la compatibilidad con CSS.
En
/Gemfile
Y justo antes de group :development, :test do
inserta:
[...]
gem 'bootstrap-sass'
[...]
CONTROL + C
y corre bundle install
para instalar una nueva gemaconsola
CONTROL + C
bundle install
consola
rails server
Una vez instalado Bootstrap en nuestra aplicación, crearemos un nuevo documento SASS en app/assets/stylesheets/custom_bootstrap.scss
para importar todas las funcionalidades CSS de boostrap a nuestro proyecto. En este nuevo documento debemos añadir lo siguiente:
app/assets/stylesheets/custom_bootstrap.scss
@import "bootstrap";
Igualmente, en el archivo app/assets/javascripts/application.js
debemos agregar el código de abajo para importar todas las funcionalidades JS de Boostrap en nuestro proyecto.
app/assets/javascripts/application.js
[...]
//= require bootstrap-sprockets
[...]
//= require_tree .
views/layouts/application.html.erb
Después del <head>
y antes del <title>
.
Inserta:
<meta name="viewport" content="width=device-width, initial-scale=1.0">
consola
CONTROL + C
rails server
views/layouts/application.html.erb
Dentro de la etiqueta <body>
, reemplaza
<%= yield %>
por…
<div class="container">
<%= yield %>
</div>
Por convención un parcial es un archivo de plantilla cuyo nombre empieza por un guion bajo y cuyo contenido se reutiliza en varias plantillas diferentes. Aquí el encabezado es un ejemplo de contenido que se repite en varias páginas.
Crea el archivo app/views/layouts/_header.html.erb
app/views/layouts/_header.html.erb
<nav class="navbar navbar-default" role="navigation">
<div class="container">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex1-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<%= link_to "Instagram", root_path, class: "navbar-brand" %>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse navbar-ex1-collapse">
<ul class="nav navbar-nav navbar-right">
<li><%= link_to "Home", root_path %></li>
<li><%= link_to "Quiénes somos", about_path %></li>
</ul>
</div><!-- /.navbar-collapse -->
</div>
</nav>
NOTA Embedded Ruby? – ¿Ruby embebido?
En Ruby se usa mucho ERB como sistema de plantillas para crear archivos HTML con código Ruby embebido.
En HTML, un enlace es así:
<a href="#"> aquí </a>
Estas son etiquetas de Ruby:
< % = % >
En Ruby on Rails un enlace se verá así:
<%= link_to "aquí", "#" %>
app/views/layouts/application.html.erb
Después de la etiqueta <body>
inserta:
<%= render 'layouts/header' %>
Reemplaza todo el contenido del archivo
views/pages/home.html.erb
<div class="jumbotron text-center">
<h2>¡Bienvenidos a Instagram!</h2>
<p>
<%= link_to "Iniciar sesión", "#", class: "btn btn-default btn-lg" %>
<%= link_to "Regístrate", "#", class: "btn btn-primary btn-lg" %>
</p>
</div>
Ahora agregamos un poco de estilos CSS (usando SASS).
app/assets/stylesheets/custom_bootstrap.scss
Reemplaza:
@import 'bootstrap';
por los siguientes estilos:
@import url(http://fonts.googleapis.com/css?family=Lato:400,700);
@import url(http://fonts.googleapis.com/css?family=Oleo+Script);
$body-bg: #fafafa !important;
$font-family-sans-serif: 'Lato', 'Helvetica Neue', Helvetica, Arial, sans-serif;
$navbar-height: 70px;
$navbar-default-bg: white;
$navbar-default-brand-color: #3897F0;
$navbar-default-link-color: #3897F0;
$brand-primary: #3897F0 !default;
$jumbotron-bg: white;
@import 'bootstrap-sprockets';
@import 'bootstrap';
.navbar-default .navbar-brand {
font-family: 'Oleo Script', cursive;
font-size: 25px;
font-weight: bold;
color: #262626;
}
.navbar-nav {
font-weight: bold;
}
La mayoría de las aplicaciones web necesitan que los usuarios puedan crear una cuenta, modificar su perfil, iniciar y cerrar sesión. En el contexto de rails hay una gema que se integra naturalmente con él y gestiona estos componentes; esta gema se llama Devise.
/Gemfile
[...]
gem 'devise'
[...]
CONTROL + C
y corre bundle install
para instalar una nueva gemaconsola
CONTROL + C
bundle install
consola
rails server
NOTA El primer tab de tu consola todavía tiene el servidor de Rails corriendo. Sigue usando el otro tab.
consola
rails generate devise:install
Generar las vistas para la personalización del manejo de sesiones de usuario.
consola
rails g devise:views
consola
rails generate devise User
La línea de arriba crea un modelo de usuario y un nuevo archivo: app/models/user.rb
Ve a db/migration y deberías tener en los archivos algo así como db/migration/20170218022322_devise_create_users.rb
El número es la fecha de creación.
consola
rails db:migrate
Este comando toma el archivo de migración y lo ejecuta, de manera que genere tablas en la base de datos.
CONTROL + C (para parar el servidor)
rails server (para volver a iniciar el servidor)
Tendrás que reiniciar la aplicación cada vez que instales una gema o cada vez que ejecutes rails db:migrate
Los mensajes flash son los mensajes en sitios web que dicen “Gracias por registrarse en …” o “Gracias por suscribirse a …”
app/views/layouts/application.html.erb
Agrega lo siguiente Debajo de <div class="container">
y antes de <%= yield %>
:
<% flash.each do |name, msg| %>
<% if msg.is_a?(String) %>
<div class="alert alert-<%= name.to_s == 'notice' ? 'success' : 'danger' %> fade in">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
<%= content_tag :div, msg, :id => "flash_#{name}" %>
</div>
<% end %>
<% end %>
app/views/pages/home.html.erb
¡Actualicemos la página de inicio!
<div class="jumbotron text-center">
<h2>¡Bienvenidos a Instagram!</h2>
<% if user_signed_in? %>
# haz algo
<% else %>
<p>
<%= link_to "Iniciar sesión", new_user_session_path, class: "btn btn-default btn-lg" %>
<%= link_to "Regístrate", new_user_registration_path, class: "btn btn-primary btn-lg" %>
</p>
<% end %>
</div>
app/views/layout/_header.html.erb
Reemplaza
<ul class="nav navbar-nav navbar-right">
<li><%= link_to "Home", root_path %></li>
<li><%= link_to "Quiénes somos", about_path %></li>
</ul>
por
<ul class="nav navbar-nav navbar-right">
<li><%= link_to "Home", root_path %></li>
<li><%= link_to "About", about_path %></li>
<% if user_signed_in? %>
<li><%= link_to "Cerrar sesión", destroy_user_session_path, method: :delete %></li>
<% else %>
<li><%= link_to "Regístrate", new_user_registration_path %></li>
<li><%= link_to "Iniciar sesión", new_user_session_path %></li>
<% end %>
</ul>
NOTA Usa el tabulador, la tecla Tab (Tab ↹) nos permite indentar el texto que este en nuestra selección y así mantener el código organizado e indentado.
app/views/devise/registrations/new.html.erb
<div class="form-wrapper">
<h1>Regístrate</h1>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>
<div class="form-group">
<%= f.label :email %>
<%= f.email_field :email, autofocus: true, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label :password %>
<%= f.password_field :password, class: "form-control" %>
</div>
<div class="form-group">
<%= f.submit "Regístrate", class: "btn btn-success" %>
</div>
<% end %>
<%= render "devise/shared/links" %>
</div>
app/views/devise/registrations/edit.html.erb
Esta vista se encarga del formulario de edición de la información de usuarios en tu app.
<div class="form-wrapper">
<h1>Editar <%= resource_name.to_s.humanize %></h1>
<%= form_for(resource, :as => resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
<%= devise_error_messages! %>
<div class="form-group">
<%= f.label :email %>
<%= f.email_field :email, class: "form-control", autofocus: true %>
</div>
<div class="form-group">
<%= f.label :password %> <i>(déjala en blanco si no quieres cambiarla)</i>
<%= f.password_field :password, class: "form-control", autocomplete: "off" %>
</div>
<div class="form-group">
<%= f.label :current_password %> <i>(necesitamos tu contraseña actual para confirmar los cambios)</i>
<%= f.password_field :current_password, class: "form-control" %>
</div>
<div class="form-group">
<%= f.submit "Actualizar", class: "btn btn-success" %>
</div>
<% end %>
<h3>Cancelar mi cuenta</h3>
<p>Unhappy? <%= button_to "Cancelar mi cuenta", registration_path(resource_name), data: { confirm: "¿Estás segura/o?" }, method: :delete, class: "btn btn-warning" %></p>
<%= link_to "Volver", :back %>
</div>
app/views/devise/passwords/new.html.erb
Esta vista está encargada de mostrar el formulario para la solicitud de contraseña al sistema en caso de olvido.
<div class="form-wrapper">
<h1>¿Olvidaste tu contraseña?</h1>
<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %>
<%= devise_error_messages! %>
<div class="form-group">
<%= f.label :email %>
<%= f.email_field :email, class: "form-control", autofocus: true %>
</div>
<div class="form-group">
<%= f.submit "Envíame las instrucciones para restablecer mi contraseña", class: "btn btn-success" %>
</div>
<% end %>
<%= render "devise/shared/links" %>
</div>
app/views/devise/passwords/edit.html.erb
En esta vista se encuentra el formulario para el cambio de la contraseña de un usuario de tu app
<div class="form-wrapper">
<h1>Cambia tu contraseña</h1>
<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %>
<%= devise_error_messages! %>
<%= f.hidden_field :reset_password_token %>
<div class="form-group">
<%= f.label :password, "Nueva contraseña" %>
<%= f.password_field :password, class: "form-control", autofocus: true %>
</div>
<div class="form-group">
<%= f.label :password_confirmation, "Confirmar nueva contraseña" %>
<%= f.password_field :password_confirmation %>
</div>
<div class="form-group">
<%= f.submit "Cambiar mi contraseña", class: "btn btn-success" %>
</div>
<% end %>
<%= render "devise/shared/links" %>
</div>
app/views/devise/sessions/new.html.erb
¡Este es el formulario de login (inicio de sesión) de tu Instagram app!
<div class="form-wrapper">
<h1>Iniciar Sesion</h1>
<%= form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f| %>
<div class="form-group">
<%= f.label :email %>
<%= f.email_field :email, class: "form-control", autofocus: true %>
</div>
<div class="form-group">
<%= f.label :password %>
<%= f.password_field :password, class: "form-control" %>
</div>
<div class="checkbox">
<%= f.check_box :remember_me %>
<%= f.label :remember_me %>
</div>
<div class="form-group">
<%= f.submit "Ingresa", class: "btn btn-success" %>
</div>
<% end %>
<%= render "devise/shared/links" %>
</div>
Al final del archivo: app/assets/stylesheets/custom_bootstrap.scss
.form-wrapper {
width: 60%;
margin: 20px auto;
background-color: #fff;
padding: 40px;
border: 1px solid #eeefef;
border-radius: 3px;
}
app/views/layouts/_header.html.erb
Debajo de <% if user_signed_in? %>
:
<li><%= link_to "Mi cuenta", edit_user_registration_path %></li>
Posts
serán nuestras publicaciones o, mejor dicho, ¡Las imágenes que publicamos en nuestro Instagram!
Un scaffold es un generador automático de modelo + controlador + vistas
rails generate scaffold posts description:string
rails db:migrate
El scaffold también crea un archivo de estilos CSS básicos pero nosotros vamos a usar nuestro propio CSS así que podemos borrar este archivo.
/app/assets/stylesheets/scaffolds.scss
app/controllers/posts_controller.rb
class PostsController < ApplicationController
before_action :set_post, only: [:show, :edit, :update, :destroy]
def index
@posts = Post.all
end
def show
end
def new
@post = Post.new
end
def edit
end
def create
@post = Post.new(post_params)
if @post.save
redirect_to @post, notice: '¡Post creado satisfactoriamente!'
else
render :new
end
end
def update
if @post.update(post_params)
redirect_to @post, notice: '¡Post actualizado satisfactoriamente!'
else
render :edit
end
end
def destroy
@post.destroy
redirect_to posts_url
end
private
# Use callbacks to share common setup or constraints between actions.
def set_post
@post = Post.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def post_params
params.require(:post).permit(:description)
end
end
Modifica el parcial del formulario apps/views/posts/_form.html.erb
Reemplaza todo el contenido por:
<%= form_for(@post) do |f| %>
<% if @post.errors.any? %>
<div class="alert alert-danger alert-dismissable"><button aria-hidden="true" class="close" data-dismiss="alert" type="button">×</button>
<ul class="list-unstyled">
<% @post.errors.full_messages.each do |msg| %>
<%= content_tag :li, msg, :id => "error_#{msg}" if msg.is_a?(String) %>
<% end %>
</ul>
</div>
<% end %>
<div class="form-group">
<%= f.label :description %>
<%= f.text_field :description, class: "form-control" %>
</div>
<div class="form-group">
<%= f.submit class: "btn btn-primary" %>
</div>
<% end %>
Cambia todo lo que hay en estos dos archivos: app/views/posts/edit.html.erb, app/views/posts/new.html.erb por:
<div class="form-wrapper">
<%= render 'form' %>
</div>
app/views/layouts/_header.html.erb
Debajo de <% if user_signed_in? %>
:
<li><%= link_to 'Nuevo post', new_post_path %></li>
index
de Postsreemplaza root 'pages#home'
por root 'posts#index'
Asociaciones: https://makeitrealcamp.gitbook.io/ruby-on-rails-5/asociaciones
Un Post belongs_to
un Usuario.
app/models/post.rb
class Post < ApplicationRecord
belongs_to :user
end
Esto significa que cada publicación estará relacionada con un usuario.
consola
rails generate migration add_user_id_to_posts user_id:integer:index
rails db:migrate
rails db:migrate
porque acabamos de crear una migración.consola
CONTROL + C (para parar el servidor)
rails server (para volver a iniciar el servidor)
has_many
Postsapp/models/user.rb
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :posts
end
Una vez que un usuario tiene acceso a la plataforma, hay que ver los permisos que este usuario tiene.
Tenemos que asegurar que un usuario no puede cambiar la publicación de otro usuario.
También que las acciones (crear, cambiar o borrar una publicación) se puede hacer solamente si estas autenticado.
app/controllers/posts_controller.rb
class PostsController < ApplicationController
before_action :set_post, only: [:show, :edit, :update, :destroy]
def index
@posts = Post.all
end
def show
end
def new
@post = current_user.posts.build
end
def edit
end
def create
@post = current_user.posts.build(post_params)
if @post.save
redirect_to @post, notice: '¡Post creado satisfactoriamente!'
else
render :new
end
end
def update
if @post.update(post_params)
redirect_to @post, notice: '¡Post actualizado satisfactoriamente!'
else
render :edit
end
end
def destroy
@post.destroy
redirect_to posts_url
end
private
# Use callbacks to share common setup or constraints between actions.
def set_post
@post = Post.find_by(id: params[:id])
end
def correct_user
@post = current_user.posts.find_by(id: params[:id])
redirect_to posts_path, notice: "¡No estás autorizada/o para editar este post!" if @post.nil?
end
# Never trust parameters from the scary internet, only allow the white list through.
def post_params
params.require(:post).permit(:description)
end
end
Recursos: https://github.com/plataformatec/devise
Añade before_action
al Controlador Posts
app/controllers/posts_controller.rb
Debajo de before_action :set_post ...
before_action :authenticate_user!, except: [:index, :show]
:correct_user
Añade el método before_action
a tu controlador de Posts
app/controllers/posts_controller.rb
Debajo de before_action :authenticate_user! ...
before_action :correct_user, only: [:edit, :update, :destroy]
Paperclip requiere la instalación de ImageMagick. Necesitas esto para procesamiento de imagen. Para instalar ImageMagick, usa los comandos abajo.
consola
sudo apt-get update
sudo apt-get install imagemagick
Paperclip es una gema que permite mediante el comando has_attached_file
poder adjuntar imágenes de manera fácil.
https://github.com/thoughtbot/paperclip
Vamos a agregarla en nuestro archivo /Gemfile
gem 'paperclip'
consola
bundle install
Actualiza el model Post
/app/models/post.rb
class Post < ApplicationRecord
belongs_to :user
has_attached_file :image, styles: { medium: "600x600#" }
validates_attachment_content_type :image, :content_type => /\Aimage\/.*\Z/
end
consola
rails generate paperclip post image
Ejecuta y verifica la migración
consola
rails db:migrate
consola
CONTROL + C
rails server
/app/views/posts/_form.html.erb
Reemplaza la primera línea por:
<%= form_for @post, html: { multipart: true } do |f| %>
Y justo antes de
<div class="form-group">
<%= f.label :description %>
Añade:
<div class="form-group">
<%= f.label :image %>
<%= f.file_field :image %>
</div>
/app/controllers/concerns/posts_controller.rb
def post_params
.
def post_params
params.require(:post).permit(:description, :image)
end
.
index
de Posts/app/views/posts/index.html.erb
<% if user_signed_in? %>
<div class="main-wrapper">
<div class="row">
<% @posts.each do |post| %>
<div class="col-sm-4">
<div class="image center-block">
<%= link_to (image_tag post.image.url(:medium), class:'img-responsive'), post_path(post) %>
</div>
</div>
<% end %>
</div>
</div>
<% else %>
<div class="jumbotron text-center">
<h1>¡Bienvenidos a Instagram!</h1>
<p>
<%= link_to "Iniciar sesión", new_user_session_path, class: "btn btn-default btn-lg" %>
<%= link_to "Regístrate", new_user_registration_path, class: "btn btn-primary btn-lg" %>
</p>
</div>
<% end %>
show
de Posts/app/views/posts/show.html.erb
<div class="posts-wrapper row">
<div class="post">
<div class="image center-block">
<%= image_tag @post.image.url(:medium),class:'img-responsive' %>
</div>
<div class="post-head">
<div class="name">
<%= @post.user.email if @post.user %>
</div>
</div>
<p class="description">
<%= @post.description %>
</p>
</div>
</div>
Abajo del archivo app/assets/stylesheets/custom_bootstrap.scss
.main-wrapper {
margin-top: 100px
}
.posts-wrapper {
padding-top: 40px;
margin: 0 auto;
max-width: 642px;
width: 100%;
}
.post {
background-color: #fff;
border-color: #edeeee;
border-style: solid;
border-radius: 3px;
border-width: 1px;
margin-bottom: 60px;
}
.post-head {
height: 64px;
padding: 14px 20px;
color: #125688;
font-size: 15px;
line-height: 18px;
.thumbnail {}
.name {
display: block;
}
}
.image {
padding-bottom: 30px;
}
.description {
padding: 24px 24px;
font-size: 15px;
line-height: 18px;
}
app/views/posts/show.html.erb
Dentro de <div class="post">
abajo colocamos:
<div class="text-center edit-links">
<%= link_to "← Volver", posts_path %>
|
<%= link_to "Editar Post", edit_post_path(@post) %>
</div>
y añade los siguientes estilos
app/assets/stylesheets/custom_bootstrap.scss
.edit-links {
margin-top: 20px;
margin-bottom: 40px;
}
y en app/views/posts/edit.html.erb, lo más arriba, añade
<div class="text-center">
<%= image_tag @post.image.url(:medium) %>
</div>
app/views/posts/edit.html.erb
Completamente abajo colocamos:
<div class="text-center edit-links">
<%= link_to "Borrar Post", post_path(@post), method: :delete, data: { confirm: "¿Estás segura que quieres eliminar este post?" } %>
|
<%= link_to "cancelar", posts_path %>
</div>
consola
rails generate migration AddNameToUsers name:string
consola
rails db:migrate
consola
CONTROL + C
rails server
Actualiza app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
before_action :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [:name])
devise_parameter_sanitizer.permit(:account_update, keys: [:name])
end
end
app/views/devise/registrations/new.html.erb, app/views/devise/registrations/edit.html.erb
Justo antes de
<div class="form-group">
<%= f.label :email %>
Añade
<div class="form-group">
<%= f.label :name %>
<%= f.text_field :name, autofocus: true, class: "form-control" %>
</div>
Crea el archivo app/controllers/registrations_controller.rb:
class RegistrationsController < Devise::RegistrationsController
private
def sign_up_params
params.require(:user).permit(:email, :name, :password, :password_confirmation)
end
def account_update_params
params.require(:user).permit(:email, :name, :password, :password_confirmation, :current_password)
end
end
/config/routes.rb
Reemplaza
devise_for :users
con…
devise_for :users, :controllers => { registrations: 'registrations' }
app/views/posts/show.html.erb
Reemplaza
<%= @post.user.email if @post.user %>
``
con...
```rhtml
<%= @post.user.name if @post.user %>
De esta manera sólo tú puedes editar tus posts. Para poner esto de otra manera: Un usuario sólo puede editar sus posts (y no los posts de otros usuarios). ¿Tiene algún sentido?
app/views/posts/show.html.erb
Reemplaza
<div class="text-center edit-links">
<%= link_to "← Volver", posts_path %>
|
<%= link_to "Editar Post", edit_post_path(@post) %>
</div>
con
<% if @post.user == current_user %>
<div class="text-center edit-links">
<%= link_to "← Volver", posts_path %>
|
<%= link_to "Edit post", edit_post_path(@post) %>
</div>
<% else %>
<div class="text-center edit-links">
<%= link_to "← Volver", posts_path %>
</div>
<% end %>
app/controllers/posts_controller.rb
Agrega debajo de private
private
def owned_post
unless current_user == @post.user
flash[:alert] = "¡Este post no te pertenece!"
redirect_to root_path
end
end
Después, inserta un before_action
en la parte superior del controlador, especificando el método owned_post
solamente para las acciones de edición, actualización y destruir.
before_action :owned_post, only: [:edit, :update, :destroy]
consola
rails g model Comment user:references post:references content:text
Este comando nos genera una migración db/migrate/ para añadir los campos en la base de datos y un modelo app/model/comment.rb.
consola
rails db:migrate
consola
CONTROL + C
rails server
Ahora nuestro modelo app/model/comment.rb está configurado para indicar a quién pertenecen los comentarios.
class Comment < ApplicationRecord
belongs_to :user
belongs_to :post
end
Por último, en app/models/user.rb and app/models/post.rb añade la siguiente línea a cada uno:
has_many :comments, dependent: :destroy
En config/routes.rb Reemplaza
resources :posts
con…
resources :posts do
resources :comments
end
consola
rails g controller comments
El controlador de comentarios solo va a tener las acciones de crear y borrar comentarios.
app/controllers/comments_controller.rb
class CommentsController < ApplicationController
before_action :set_post
def create
@comment = @post.comments.build(comment_params)
@comment.user_id = current_user.id
if @comment.save
flash[:success] = "¡Has comentado este post!"
redirect_to request.referer
else
flash[:alert] = "Revisa el formulario de comentarios, algo salió mal :/"
render root_path
end
end
def destroy
@comment = @post.comments.find(params[:id])
@comment.destroy
flash[:success] = "Comentario eliminado :("
redirect_to root_path
end
private
def comment_params
params.require(:comment).permit(:content)
end
def set_post
@post = Post.find(params[:post_id])
end
end
Reemplaza app/views/posts/show.html.erb
<div class="posts-wrapper">
<div class="post">
<div class="image center-block">
<%= image_tag @post.image.url(:medium), class:'img-responsive' %>
</div>
<div class="post-bottom">
<div class="description">
<div class="user-name">
<%= @post.user.name %>
</div>
<%= @post.description %>
</div>
<% if @post.comments %>
<% @post.comments.each do |comment| %>
<div class="comment">
<div class="user-name">
<%= comment.user.name %>
</div>
<div class="comment-content">
<%= comment.content %>
</div>
<% if comment.user == current_user %>
<%= link_to post_comment_path(@post, comment), method: :delete, data: { confirm: "¿Estás segura?" } do %>
<span class="glyphicon glyphicon-remove delete-comment"></span>
<% end %>
<% end %>
</div>
<% end %>
<% end %>
</div>
<div class="comment-like-form row">
<div class="like-button col-sm-1">
<span class="glyphicon glyphicon-heart-empty"></span>
</div>
<div class="comment-form col-sm-11">
<%= form_for [@post, @post.comments.new] do |f| %>
<%= f.text_field :content, placeholder: 'Añade un comentario...' %>
<% end %>
</div>
</div>
</div>
</div>
<% if @post.user.id == current_user.id %>
<div class="text-center edit-links">
<%= link_to "Cancelar", posts_path %>
|
<%= link_to "Editar el post", edit_post_path(@post) %>
</div>
<% else %>
<div class="text-center edit-links">
<%= link_to "← Volver", posts_path %>
</div>
<% end %>
En app/assets/stylesheets/custom_bootstrap.scss
Borra los estilos debajo de
.navbar-brand {
font-weight: bold;
}
Y reemplaza con
.main-wrapper {
margin-top: 100px
}
.image {
padding-bottom: 30px;
}
.posts-wrapper {
padding-top: 40px;
margin: 0 auto;
max-width: 642px;
width: 100%;
}
.post {
background-color: #fff;
border-color: #edeeee;
border-style: solid;
border-radius: 3px;
border-width: 1px;
margin-bottom: 60px;
.post-head {
flex-direction: row;
height: 64px;
padding-left: 24px;
padding-right: 24px;
padding-top: 24px;
color: #125688;
font-size: 15px;
line-height: 18px;
.user-name, .time-ago {
display: inline;
}
.user-name {
font-weight: 500;
}
.time-ago {
color: #A5A7AA;
float: right;
}
}
}
.post-bottom {
.user-name, .comment-content {
display: inline;
}
.description {
margin-bottom: 7px;
}
.user-name {
font-weight: 500;
margin-right: 0.3em;
color: #125688;
font-size: 15px;
}
.user-name, .caption-content {
display: inline;
}
.comment {
margin-top: 7px;
.user-name {
font-weight: 500;
margin-right: 0.3em;
}
.delete-comment {
float: right;
color: #515151;
}
}
margin-bottom: 7px;
padding-top: 24px;
padding-left: 24px;
padding-right: 24px;
padding-bottom: 10px;
font-size: 15px;
line-height: 18px;
}
.comment_content {
font-size: 15px;
line-height: 18px;
border: medium none;
width: 100%;
color: #4B4F54;
}
.comment-like-form {
padding-top: 24px;
margin-top: 13px;
margin-left: 24px;
margin-right: 24px;
min-height: 68px;
align-items: center;
border-top: 1px solid #EEEFEF;
flex-direction: row;
justify-content: center;
}
.form-wrapper {
width: 60%;
margin: 20px auto;
background-color: #fff;
padding: 40px;
border: 1px solid #eeefef;
border-radius: 3px;
}
.edit-links {
margin-top: 20px;
margin-bottom: 40px;
}
Recursos: Control de versiones con Git y GitHub
¡Ahora veremos en secuencia los pasos necesarios para subir tu primer proyecto a la plataforma Github!
Dashboard
.
https://c9.io/account/sshSSH keys
ssh-rsa...
SSH and GPG keys
.
https://github.com/settings/sshgit@github.com:sunombre/nombredelproyecto.git
git init
git remote add origin git@github.com:sunombre/nombredelproyecto.git
git add .
git commit -m "Mi Primer commit"
git push -u origin master
Ahora tienes tu repositorio actualizado en GitHub
¿Cómo subir nuestra aplicación en la web, de forma que otros puedan verla? Con un servicio llamado Heroku que permite subir tu aplicación en un servidor gratis en cuestión de segundos.
wget -O- https://toolbelt.heroku.com/install-ubuntu.sh | sh
heroku login
Email: (Introduce tu correo electrónico)
Password ( Introduce tu contraseña - se mostrará en blanco y es normal )
heroku keys:add
heroku create #crea una nueva URL para la aplicación
/gemfile
group :development, :test do
gem 'sqlite3'
end
group :production do
gem 'pg'
gem 'rails_12factor'
end
Nota: Después de crear un grupo producción
a tu Gemfile, debes cambiar a utilizar bundle install --without production
bundle install --without production
git add --all
git commit -m "¡Lista para subir a Heroku!"
git push origin master
git push heroku master
heroku open
heroku rename instagram #Reemplaza "instagram" con el nombre de tu proyecto
heroku run rake db:migrate #Para correr las migraciones
¿Cómo intercambiar información con el servidor sin tener que refrescar la página? Ese es el problema que soluciona Ajax. En nuestro caso sería como añadir comentarios sin tener que refrescar la página.
Ajax en Rails se maneja con parciales, entonces crea un nuevo archivo
app/views/comments/_comment.html.erb
<div class="comment" id="comment_<%= comment.id %>">
<div class="user-name">
<%= comment.user.name %>
</div>
<div class="comment-content">
<%= comment.content %>
</div>
<% if comment.user == current_user %>
<%= link_to post_comment_path(post, comment), method: :delete, data: { confirm: "¿Estás segura?" } do %>
<span class="glyphicon glyphicon-remove delete-comment"></span>
<% end %>
<% end %>
</div>
show.html.erb
Acabamos de mudar el comentario aparte en un archivo separado. Ahora, vamos a tener que ajustar nuestro show
para seguir monstrando los comentarios de manera apropiada.
En app/views/posts/show.html.erb
Reemplaza
<% @post.comments.each do |comment| %>
<div class="comment">
<div class="user-name">
<%= comment.user.name %>
</div>
<div class="comment-content">
<%= comment.content %>
</div>
<% if comment.user == current_user %>
<%= link_to post_comment_path(@post, comment), method: :delete, data: { confirm: "¿Estás segura?" } do %>
<span class="glyphicon glyphicon-remove delete-comment"></span>
<% end %>
<% end %>
</div>
<% end %>
con…
<%= render @post.comments, post: @post %>
Y sigue funcionando igual.
remote: true
a nuestro formularioEn app/views/posts/show.html.erb
reemplaza
<% if post.comments %>
<%= render @post.comments, post: @post %>
<% end %>
con…
<div class="comments" id="comments_<%= @post.id %>">
<% if @post.comments %>
<%= render @post.comments, post: @post %>
<% end %>
</div>
y las líneas
<%= form_for [@post, @post.comments.new] do |f| %>
<%= f.text_field :content, placeholder: 'Añade un comentario...' %>
<% end %>
con…
<%= form_for([@post, @post.comments.build], remote: true) do |f| %>
<%= f.text_field :content, placeholder: 'Añade un comentario...', id: "comment_content_#{@post.id}" %>
<% end %>
app/controllers/comments_controller.rb
Reemplaza el action
para crear con:
def create
@comment = @post.comments.build(comment_params)
@comment.user_id = current_user.id
if @comment.save
respond_to do |format|
format.html { redirect_to root_path }
format.js
end
else
flash[:alert] = "Revisa el formulario de comentarios, algo salió mal :/"
render root_path
end
end
¡jQuery al rescate! Crea un nuevo archivo en la carpeta app/views/comments/ un archivo create.js.erb. En ese archivo, agrega la siguiente combinación de Javascript / Ruby
$('#comments_<%= @post.id %>').append("<%=j render 'comments/comment', post: @post, comment: @comment %>");
$('#comment_content_<%= @post.id %>').val('')
Añadir remote: true
al enlace para borrar
app/views/comments/_comment.html
Al final de esta línea y antes de do
, añade , remote: true
<%= link_to post_comment_path(post, comment), method: :delete, data: { confirm: "¿Estás segura?" } do %>
Quedaría:
<%= link_to post_comment_path(post, comment), method: :delete, data: { confirm: "¿Estás segura?" }, remote: true do %>
Al igual que antes, ahora podemos asegurar que rails responde no sólo con HTML, sino también con Javascript.
Añade el método responds_to
a la acción destroy
dentro del comments_controller
def destroy
@comment = @post.comments.find(params[:id])
if @comment.user_id == current_user.id
@comment.delete
respond_to do |format|
format.html { redirect_to root_path }
format.js
end
end
end
Y por último pero no menos importante..
¡Simplemente estamos añadiendo nuestra lista de comentarios actualizada!
Crea el nuevo archivo destroy.js.erb dentro de la carpeta app/views/comments/ ( en la misma ubicación que el archivo create.js.erb ).
$('#comments_<%= @post.id %>').html("<%= j render @post.comments, post: @post, comment: @comment %>");