Passo a Passo
Siga os meus passos que foram utilizados na aula 1 e aprofunde ainda mais o seu aprendizado.
Aula 1
Geração do projeto
1- Abra o terminal e crie o projeto com o comando:
1 |
rails _5.0.6_ new programmerhub -d postgresql |
2- Abra o projeto no seu editor, abra seu Gemfile e substitua o conteúdo por:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
source 'https://rubygems.org' git_source(:github) do |repo_name| repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") "https://github.com/#{repo_name}.git" end gem 'rails', '~> 5.0.6' gem 'pg', '~> 0.18' gem 'puma', '~> 3.0' gem 'sass-rails', '~> 5.0' gem 'uglifier', '>= 1.3.0' gem 'coffee-rails', '~> 4.2' gem 'jquery-rails' gem 'turbolinks', '~> 5' gem 'jbuilder', '~> 2.5' gem 'devise' gem 'materialize-sass' gem 'material_icons' gem 'cancancan' gem 'mini_magick' gem 'carrierwave', '~> 1.0' gem 'ransack' gem 'will_paginate', '~> 3.1.0' gem 'acts_as_follower', github: 'tcocca/acts_as_follower', branch: 'master' gem 'acts_as_commentable' group :development, :test do gem 'byebug', platform: :mri end group :development do gem 'web-console', '>= 3.3.0' gem 'listen', '~> 3.0.5' gem 'spring' gem 'spring-watcher-listen', '~> 2.0.0' gem "better_errors" gem "binding_of_caller" end |
3- No seu terminal rode
1 |
bundle install |
Setup Inicial
1- Primeiro vamos configurar nosso projeto para usar o Materialize.
a) Altere o app/assets/stylesheets/application.css para app/assets/stylesheets/application.scss e coloque nele:
1 2 |
@import "materialize"; @import "material_icons"; |
b) Coloque no app/assets/javascripts/application.js:
1 2 3 4 5 |
//= require jquery //= require jquery_ujs //= require turbolinks //= require materialize-sprockets //= require_tree . |
2- Vamos criar nosso banco de dados com:
1 |
rails db:create |
3- Agora vamos instalar o devise com:
1 |
rails generate devise:install |
4- Agora é hora de criar nosso User com o generate do devise:
1 |
rails g devise User |
5- Na migration gerada adicione a linha a seguir para criar o campo name e description para o User:
1 2 |
t.string :name, null: false t.string :description, null: false |
*Para migrar as novas informações para o banco de dados rode no terminal:
1 |
rails db:migrate |
6- Para forçar que o usuário esteja logado no sistema para acessar os conteúdos, adicione ao seu app/controllers/application_controller.rb
1 |
before_action :authenticate_user! |
7- Agora vamos instalar o cancancan para gerenciar as permissões de usuários, no terminal rode:
1 |
rails g cancan:ability |
Customizando o devise
Vamos customizar o devise para aceitar o campo name e descripion adicionado na migration.
1- Substitua a linha devise_for :users no routes.rb para:
1 |
devise_for :users, :controllers => { registrations: 'registrations' } |
2- Agora crie um controller chamado registrations_controller.rb e coloque:
1 2 3 4 5 6 7 8 9 10 11 |
class RegistrationsController < Devise::RegistrationsController private def sign_up_params params.require(:user).permit(:name, :description, :email, :password, :password_confirmation, :avatar) end def account_update_params params.require(:user).permit(:name, :description, :email, :password, :password_confirmation, :current_password, :avatar) end end |
3- Rode no console:
1 |
rails generate devise:views |
4- Substitua o arquivo app/views/devise/sessions/new.html.erb por:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
<div class="container"> <div class="row"> <div class="col m8 offset-m2"> <h2>Log in</h2> <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %> <div class="field"> <%= f.label :email %><br /> <%= f.email_field :email, autofocus: true %> </div> <div class="field"> <%= f.label :password %><br /> <%= f.password_field :password, autocomplete: "off" %> </div> <% if devise_mapping.rememberable? -%> <div class="field"> <%= f.check_box :remember_me %> <%= f.label :remember_me %> </div> <% end -%> <div class="actions"> <button class="btn waves-effect waves-light" type="submit" name="action">Submit <i class="material-icons right">send</i> </button> </div> <% end %> <%= render "devise/shared/links" %> </div> </div> </div> |
5- Substitua o arquivo app/views/devise/registrations/new.html.erb por:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
<div class="container"> <h2>Sign up</h2> <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> <%= devise_error_messages! %> <div class="field"> <%= f.label :name %><br /> <%= f.text_field :name, autofocus: true %> </div> <div class="field"> <%= f.label :description %><br /> <%= f.text_field :description, autofocus: true %> </div> <div class="field"> <%= f.label :email %><br /> <%= f.email_field :email, autofocus: true %> </div> <div class="field"> <%= f.label :password %> <% if @minimum_password_length %> <em>(<%= @minimum_password_length %> characters minimum)</em> <% end %><br /> <%= f.password_field :password, autocomplete: "off" %> </div> <div class="field"> <%= f.label :password_confirmation %><br /> <%= f.password_field :password_confirmation, autocomplete: "off" %> </div> <div class="actions"> <button class="btn waves-effect waves-light" type="submit" name="action">Sign Up <i class="material-icons right">send</i> </button> </div> <% end %> <%= render "devise/shared/links" %> </div> |
6- Substitua o arquivo app/views/devise/registrations/edit.html.erb por:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
<div class="container"> <h2>Edit <%= resource_name.to_s.humanize %></h2> <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> <%= devise_error_messages! %> <div class="field"> <%= f.label :name %><br /> <%= f.text_field :name, autofocus: true %> </div> <div class="field"> <%= f.label :description %><br /> <%= f.text_field :description, autofocus: true %> </div> <div class="field"> <%= f.label :email %><br /> <%= f.email_field :email, autofocus: true %> </div> <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %> <div>Currently waiting confirmation for: <%= resource.unconfirmed_email %></div> <% end %> <div class="field"> <%= f.label :password %> <i>(leave blank if you don't want to change it)</i><br /> <%= f.password_field :password, autocomplete: "off" %> <% if @minimum_password_length %> <br /> <em><%= @minimum_password_length %> characters minimum</em> <% end %> </div> <div class="field"> <%= f.label :password_confirmation %><br /> <%= f.password_field :password_confirmation, autocomplete: "off" %> </div> <div class="field"> <%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br /> <%= f.password_field :current_password, autocomplete: "off" %> </div> <div class="actions"> <button class="btn waves-effect waves-light" type="submit" name="action">Update <i class="material-icons right">send</i> </button> </div> <% end %> <h3>Cancel my account</h3> <p>Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn lighten-2 red waves-light' %></p> <%= link_to "Back", :back %> </div> |
7- No seu application_controller adicione os seguintes métodos para um redirecionamento correto após o usuário fazer sign in ou sign up
1 2 3 4 5 6 7 8 9 |
protected #def after_sign_in_path_for(current_user) # posts_path #end #def after_sign_up_path_for(current_user) # posts_path #end |
Configurando o Carriewave para upload de imagens
1- Vamos configurar agora o carrierwave para o upload da imgem de perfil do usuário, rode no terminal:
1 |
rails generate uploader Avatar |
2 – No arquivo gerado (app/uploaders/avatar_uploader.rb) coloque:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class AvatarUploader < CarrierWave::Uploader::Base include CarrierWave::MiniMagick storage :file def store_dir "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" end version :thumb do process resize_to_fill: [50,50] end end |
3- Agora vamos incluir nossa imagem de perfil para o User, rode no seu console:
1 2 |
rails g migration add_avatar_to_users avatar:string rails db:migrate |
4- Agora em app/models/user.rb adicione a linha:
1 |
mount_uploader :avatar, AvatarUploader |
5- No seu arquivo config/environment.rb adicione:
1 |
require 'carrierwave/orm/activerecord' |
6- Substitua o arquivo app/views/devise/registrations/new.html.erb por:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
<div class="container"> <div class="row"> <div class="col m8 offset-m2"> <h2>Sign up</h2> <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> <%= devise_error_messages! %> <div class="field"> <div class="row"> <div class="col m3 s4"> <%= f.label 'Photo' %><br /> <%= f.file_field :avatar %><br /> </div> </div> <div class="row"> <div class="col m3 s4"> <img id="image_upload_preview" class="responsive-img circle" /> </div> </div> </div> <div class="field"> <%= f.label :name %><br /> <%= f.text_field :name, autofocus: true %> </div> <div class="field"> <%= f.label :description %><br /> <%= f.text_field :description, autofocus: true %> </div> <div class="field"> <%= f.label :email %><br /> <%= f.email_field :email, autofocus: true %> </div> <div class="field"> <%= f.label :password %> <% if @minimum_password_length %> <em>(<%= @minimum_password_length %> characters minimum)</em> <% end %><br /> <%= f.password_field :password, autocomplete: "off" %> </div> <div class="field"> <%= f.label :password_confirmation %><br /> <%= f.password_field :password_confirmation, autocomplete: "off" %> </div> <div class="actions"> <button class="btn waves-effect waves-light" type="submit" name="action">Sign Up <i class="material-icons right">send</i> </button> </div> <% end %> <%= render "devise/shared/links" %> </div> </div> </div> |
7- Substitua o arquivo app/views/devise/registrations/edit.html.erb por:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
<div class="container"> <div class="row"> <div class="col m8 offset-m2"> <h2>Edit <%= resource_name.to_s.humanize %></h2> <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> <%= devise_error_messages! %> <div class="field"> <div class="row"> <div class="col m3 s4"> <%= f.label 'Photo' %><br /> <%= f.file_field :avatar %><br /> </div> </div> <div class="row"> <div class="col m3 s4"> <img id="image_upload_preview" class="responsive-img circle" /> </div> </div> </div> <div class="field"> <%= f.label :name %><br /> <%= f.text_field :name, autofocus: true %> </div> <div class="field"> <%= f.label :description %><br /> <%= f.text_field :description, autofocus: true %> </div> <div class="field"> <%= f.label :email %><br /> <%= f.email_field :email, autofocus: true %> </div> <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %> <div>Currently waiting confirmation for: <%= resource.unconfirmed_email %></div> <% end %> <div class="field"> <%= f.label :password %> <i>(leave blank if you don't want to change it)</i><br /> <%= f.password_field :password, autocomplete: "off" %> <% if @minimum_password_length %> <br /> <em><%= @minimum_password_length %> characters minimum</em> <% end %> </div> <div class="field"> <%= f.label :password_confirmation %><br /> <%= f.password_field :password_confirmation, autocomplete: "off" %> </div> <div class="field"> <%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br /> <%= f.password_field :current_password, autocomplete: "off" %> </div> <div class="actions"> <button class="btn waves-effect waves-light" type="submit" name="action">Update <i class="material-icons right">send</i> </button> </div> <% end %> <h3>Cancel my account</h3> <p>Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete, class: 'btn lighten-2 red waves-light' %></p> <%= link_to "Back", :back %> </div> </div> </div> |
8-Agora vamos criar um arquivo dentro de app/assets/javascripts chamado load_image_preview.coffee e insira o seguinte conteúdo nele:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$(document).on "turbolinks:load", -> readURL = (input) -> if input.files and input.files[0] reader = new FileReader reader.onload = (e) -> $('#image_upload_preview').attr 'src', e.target.result return reader.readAsDataURL input.files[0] return $('#user_avatar').on 'change', -> readURL this return false |
Preparando os Likes e Comments
Para os comentários e para os follows do nosso app vamos usar gems que facilitam esse processo para nós.
1- Vamos configurar a gem act_as_follower, no seu terminal rode o comando:
1 |
rails generate acts_as_follower |
2- Para um bom funcionamento, substitua a primeira linha da migrate gerada por:
1 |
class ActsAsFollowerMigration < ActiveRecord::Migration[4.2] |
3- Agora vamos fazer a configuração da gem acts_as_commentable, no seu terminal rode o comando:
1 |
rails g comment |
4- Para um bom funcionamento, substitua a primeira linha da migrate gerada por:
1 |
class CreateComments < ActiveRecord::Migration[4.2] |
5- Rode as migrations
1 |
rails db:migrate |
Criando nossos models
1- Digite no seu terminal para criar nosso model Post
1 |
rails g model Post body:text user:references |
2- Para criar nosso model Like, digite:
1 |
rails g model Like user:references post:references |
*Agora vamos setar os relacionamentos dos nossos models
2- No seu model User substitua o conteúdo por:
1 2 3 4 5 6 7 8 9 10 11 |
class User < ApplicationRecord devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable mount_uploader :avatar, AvatarUploader has_many :posts has_many :likes acts_as_followable acts_as_follower end |
3- No model Post substitua o conteúdo por:
1 2 3 4 5 |
class Post < ApplicationRecord belongs_to :user has_many :likes acts_as_commentable end |
Vamos gerar nossos controllers
1- Digite no terminal:
1 |
rails g controller Posts index create destroy edit update --skip-routes |
*delete os arquivos create.html.erb, destroy.html.erb e update.html.erb na pasta views/posts
2- Agora vamos criar o controller Comment:
1 |
rails g controller Comments create destroy update --skip-template-engine--skip-routes |
3- Agora o controller Like:
1 |
rails g controller Likes create destroy --skip-template-engine --skip-routes |
4- Para criar o controller Follows digite no terminal:
1 |
rails g controller Follows create destroy --skip-template-engine --skip-routes |
5- Vamos criar um controller Pages para criar nossa home page
1 |
rails g controller Pages home --skip-routes |
6- Por último vamos criar nosso controle Users para criar nossa página de perfil de usuário:
1 |
rails g controller Users show followers followings search --skip-routes |
7- Vamos ajustar as rotas do nosso ap, substitua o conteúdo do seu arquivo config/routes.rb por:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
Rails.application.routes.draw do root 'pages#home' resources :likes, only: [:create, :destroy] resources :comments, only: [:create, :destroy, :edit, :update] resources :posts, only: [:index, :show, :create, :destroy, :edit, :update] resources :follows, only: [:create, :destroy] get '/profile/:id', to: "users#show", as: "user" get '/profile/:id/followings', to: "users#followings", as: "following" get '/profile/:id/followers', to: "users#followers", as: "followers" post '/users/search', to: "users#search", as: :search get '/users/search', to: "users#search" devise_for :users, :controllers => { registrations: 'registrations' } end |
Vamos criar as regras de permissão de usuário
1- No arquivo models/ability.rb substitua o conteúdo por:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Ability include CanCan::Ability def initialize(user) if user can :read, :all can :manage, Post, user_id: user.id can :manage, Follow, user_id: user.id can :manage, Like, user_id: user.id can :manage, Comment, user_id: user.id end end end |
2- Dentro de cada um dos seus controllers (exceto application, registrations e pages) coloque:
1 |
load_and_authorize_resource |
Vamos estruturar as views do nosso projeto
1- Na pasta app/views crie uma pasta com nome shared e crie dentro dela o arquivo _navbar.html.erb e insira o seguinte conteúdo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
<div class="navbar-fixed"> <nav class="top-nav"> <div class="nav-wrapper col s12"> <div class="container"> <% if current_user %> <a href="/posts" class="brand-logo"> <i class="material-icons">group</i> <span class="logo-title"> ProgrammerHub </span> </a> <% else %> <a href="/" class="brand-logo"> <i class="material-icons">group</i> <span class="logo-title"> ProgrammerHub </span> </a> <% end %> <a href="#" data-activates="side-bar" class="button-collapse"><i class="material-icons">menu</i></a> <ul class="right hide-on-med-and-down"> <% if current_user %> <li> <a class="dropdown-button" href="#!" data-activates="dropdown1"> <img src="<%= current_user.avatar.thumb.url %>" alt="" class="responsive-img circle nav-image"> </a> </li> <ul id="dropdown1" class="dropdown-content"> <li><%= link_to 'Profile', user_path(current_user) %></li> <li class="divider"></li> <li><%= link_to 'Config', edit_user_registration_path(current_user) %></li> <li class="divider"></li> <li><%= link_to('Log Out', destroy_user_session_path, :method => :delete) %></li> </ul> <% else %> <li><a href="/users/sign_in">Login</a></li> <li><a href="/users/sign_up">Register</a></li> <% end %> </ul> </div> </div> </nav> </div> <ul class="side-nav" id="side-bar"> <% if current_user %> <li><%= link_to 'Feed', posts_path %></li> <li><%= link_to 'Profile', user_path(current_user) %></li> <li><%= link_to 'Config', edit_user_registration_path(current_user) %></li> <li><div class="divider"></div></li> <li> <%= link_to('Log out', destroy_user_session_path, :method => :delete) %></li> <% else %> <li><a href="/users/sign_in">Log in</a></li> <li><div class="divider"></div></li> <li><a href="/users/sign_up">Register</a></li> <li><div class="divider"></div></li> <% end %> </ul> |
2- Na sua pasta app/assets/javascripts crie uma pasta shared e crie um arquivo navbar.coffee e insira nele:
1 2 3 4 |
$(document).on 'turbolinks:load', -> $('.button-collapse').sideNav() $('.modal').modal() return |
3- Agora na mesma pasta crie o arquivo _footer.html.erb e insira:
1 2 3 4 5 6 7 8 9 |
<div class="page-footer"> <div class="container"> <div class="copyright"> <b> <a class="copyright-link" href="https://onebitcode.com/">Created with<i class="material-icons copyright-icon">favorite</i> By OneBitCode</a> </b> </div> </div> </div> |
4- No seu arquivo views/layouts/application.html.erb substitua o conteúdo por:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<!DOCTYPE html> <html> <head> <title>RedeSocial</title> <%= csrf_meta_tags %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> </head> <body> <header> <%= render "shared/navbar" %> </header> <main> <div class="container"> <%= yield %> </div> </main> <footer> <%= render "shared/footer" %> </footer> </body> </html> |
5- Agora na pasta app/assets/stylesheets crie uma pasta shared e crie um arquivo _navbar.scss e insira nele:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
nav{ background-color: #e86252; .brand-logo{ margin-left: 10px; .logo-title{ font-size: 20px; } } .nav-image { width: 50px; margin-top: 8px; } } |
6- Na mesma página crie um _footer.scss
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
body { display: flex; min-height: 100vh; flex-direction: column; main { flex: 1 0 auto; } footer{ .page-footer{ background-color: #616161; .copyright{ padding-bottom: 16px; color: white; .copyright-link{ color: white; cursor: pointer; } .copyright-icon{ font-size: 12px; color: #e86252; } } } } } |
7- No seu arquivo stylesheets/pages.scss insira o seguinte código:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
.home{ margin-top: 15%; .home-button{ width: 300px; } .title { color: #e86252; font-size: 40px; margin-bottom: 20px; } .subtitle{ color: #012333; margin-bottom: 24px; } } |
8- No seu application.scss substitua o conteúdo por:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@import "materialize"; @import "material_icons"; @import "pages"; @import "posts"; @import "users"; @import "shared/_footer"; @import "shared/_navbar"; main .container { margin-top: 20px; } .card .card-content { padding-bottom: 3px; } |
9- Vamos inserir no nosso arquivo app/views/pages/home.html.erb o seguinte conteúdo:
1 2 3 4 5 6 7 8 |
<div class="row home"> <div class="center"> <h4 class="title">A Programmer Networking</h4> <h5 class="subtitle">Join to ProgrammerHub and connect with thousands of other software developers to learn and share</h5> <%= link_to 'Join Now', new_user_registration_path, class: "btn btn-large grey darken-2 home-button" %> </div> </div> |
Parabéns por chegar até aqui \o/,
Te vejo na aula 2 para continuar nossa jornada,
Leonardo Scorza