Passo a Passo
Siga os meus passos que foram utilizados na aula 1 e aprofunde ainda mais o seu aprendizado.
Aula 2
Ajustando o CanCanCan
1- Dentro de cada um dos seus controllers (exceto nos controllers application, follows, registrations e pages) coloque:
1 |
load_and_authorize_resource |
Exibindo nossas flash messages
1- Adicione ao application.html.erb.
1 2 3 4 5 6 7 |
<% flash.each do |message_type, message| %> <% if message_type == 'alert' %> <%= javascript_tag "Materialize.toast('#{message}', 4000, 'red')" %> <% else message_type == 'notice' %> <%= javascript_tag "Materialize.toast('#{message}', 4000, 'green')" %> <% end %> <% end %> |
Vamos implementar nossos controllers
1-No seu controller posts_controllers.rb 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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
class PostsController < ApplicationController load_and_authorize_resource before_action :set_post, only: [:show, :edit, :update, :destroy] def index @user = current_user @post = Post.new posts = current_user.posts.map {|post| post} current_user.all_following.each { |user| user.posts.each{|post| posts << post } } @posts = posts.sort_by &:created_at end def create @post = Post.new(post_params) respond_to do |format| if @post.save format.json { render json: @post, status: :created } format.html { redirect_to posts_path, notice: 'Post was successfully created.' } else format.json { render json: @post.errors, status: :unprocessable_entity } format.html { redirect_to posts_path } end end end def show @comment = Comment.new @comments = @post.comments end def destroy @post.destroy respond_to do |format| format.json { head :no_content } format.html { redirect_to posts_path, notice: 'Post was successfully destroyed.' } end end def edit end def update end private def set_post @post = Post.find(params[:id]) end def post_params params.require(:post).permit(:body).merge(user: current_user) end end |
2- No seu follows_controller.rb substitua 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 |
class FollowsController < ApplicationController before_action :set_user def create respond_to do |format| if current_user.follow(@user) format.json { render @user, status: :created } format.html { redirect_to :back, notice: 'User followed with success.' } else format.json { render json: nil, status: :unprocessable_entity } format.html { redirect_to :back } end end end def destroy respond_to do |format| if current_user.stop_following(@user) format.json { head :no_content } format.html { redirect_to :back, notice: 'User unfollowed with success.'} else format.json { render json: nil, status: :unprocessable_entity } format.html { redirect_to :back } end end end private def set_user @user = User.find(params[:user_id]) end end |
3- Agora no controller Likes 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 |
class LikesController < ApplicationController before_action :set_post before_action :set_event, only: [:destroy] load_and_authorize_resource def create @like = Like.new(user: current_user, post: @post) respond_to do |format| if @like.save format.json { render json: @like } format.html { redirect_to :back, notice: 'Post liked with success.' } else format.json { render json: @like.errors, status: :unprocessable_entity } format.html { redirect_to :back } end end end def destroy @like.destroy respond_to do |format| format.json { head :no_content } format.html { redirect_to :back, notice: 'Post unliked with success.' } end end private def set_post @post = Post.find(params[:id]) end def set_event @like = Like.find_by(user: current_user, post: @post) @event = @like end end |
4- No arquivo pages_controllers substitua o conteúdo por:
1 2 3 4 5 6 |
class PagesController < ApplicationController skip_before_filter :authenticate_user! def home end end |
5- No seu controller Comments 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 43 44 45 46 |
class CommentsController < ApplicationController load_and_authorize_resource before_action :set_comment, only: [:destroy, :update] before_action :set_post, only: [:create] def create @comment = @post.comments.create(comment_params) respond_to do |format| if @comment.save format.json { render json: @comment, status: :created } format.html { redirect_to :back, notice: 'Comment created with success.' } else format.json { render json: @comment.errors, status: :unprocessable_entity } format.html { redirect_to :back } end end end def destroy @comment.destroy respond_to do |format| format.json { render json: :no_content } format.html { redirect_to :back, notice: 'Comment deleted with success.' } end end def update #desafio end private def set_post @post = Post.find(params[:comment][:post_id]) end def set_comment @comment = Comment.find(params[:id]) end def comment_params params.require(:comment).permit(:comment).merge(user: current_user) end end |
6- Por último, altere seu arquivo users_controllers.rb para:
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 |
class UsersController < ApplicationController # load_and_authorize_resource before_action :set_user, only: [:show, :following, :followers] def show @posts = @user.posts @following = @user.following_users.limit(5) @followers = @user.followers_by_type('User').limit(5) end def following @following = @user.following_users end def followers @followers = @user.followers_by_type('User') end def search @user = current_user @query = User.ransack(params[:q]) @users = @query.result(distinct: true) end private def set_user @user = User.find(params[:id]) end end |
Views do feed e do post show
1- Na pasta views/shared crie duas partials, _post.html.erb e _user-info.html.erb
2- No arquivo shared/_post.html.erb insira o 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 |
<div class="card hoverable"> <div class="card-content"> <div class="row"> <div class="col s2 m2 l2"> <img src="<%= post.user.avatar.thumb.url %>" alt="" class="circle responsive-img left"> </div> <div class="col s6 m6 l6"> <p class="post-user-name"><%= link_to post.user.name, user_path(post.user_id) %></p> </div> <div class="col s3 m3 l3"> <b class="post-date"><%= post.created_at.strftime("%Hh%M") %></b> </div> <% if current_user.id == post.user_id %> <div class="col s1 m1 l1"> <%= link_to post_path(post), method: "delete" do %> <i class="material-icons post-options">delete</i> <% end %> </div> <% end %> </div> <div class="row"> <div class="col m12"> <p class="post-body"> <%= link_to truncate(post.body, length: 2000), post_path(post), class: 'grey-text text-darken-2' %> </p> </div> </div> <div class="row"> <div class="post-actions"> <%= link_to post_path(post) do %> <i class='material-icons action-link'>chat_bubble</i> <%= post.comments.count %> <% end %> <% if liked?(current_user, post) %> <%= link_to unlike_post_path(post), method: "delete" do %> <i class="material-icons liked">favorite</i><%= post.likes.count %> <% end %> <% else %> <%= link_to like_post_path(post), method: "post" do %> <i class="material-icons to-like">favorite</i><%= post.likes.count %> <% end %> <% end %> </div> </div> </div> </div> |
3- No seu arquivo app/helpers/application_helper.rb substitua o conteúdo por:
1 2 3 4 5 |
module ApplicationHelper def liked?(user, post) user.likes.find_by(post_id: post.id) end end |
4-No arquivo shared/_user-info.html.erb coloque o 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 |
<div class="card user-info"> <div class="card-content"> <div class="row"> <div class="user-info"> <div class="col s4 m2 l4"> <img src="<%= @user.avatar.thumb.url %>" alt="" class="circle responsive-img"> </div> <div class="col s6 m6 l6"> <p class="user-name"><%= link_to @user.name, user_path(@user) %></p> </div> <% if @user.id != current_user.id %> <% if current_user.following?(@user) %> <div class="col s2 m2 l2"> <%= link_to unfollow_path(user_id: @user.id), method: :delete do %> <i class="material-icons btn-unfollow">grade</i> <% end %> </div> <% else %> <div class="col s2 m2 l2"> <%= link_to follow_path(user_id: @user.id), method: :post do %> <i class="material-icons btn-follow">grade</i> <% end %> </div> <% end %> <% end %> </div> </div> <div class="divider"></div> <div class="row activity-info"> <div class="col s4 m4 l4"> <p class="center">Followers</p> <p class="center p-info"><%= link_to @user.followers_count, followers_path(@user) %></p> </div> <div class="col s4 m4 l4"> <p class="center">Following</p> <p class="center p-info"><%= link_to @user.follow_count, following_path(@user) %></p> </div> <div class="col s4 m4 l4"> <p class="center">Posts</p> <p class="center p-info"><%= link_to @user.posts.count, user_path(@user) %></p> </div> </div> </div> </div> |
5- Agora em views/posts/index.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 26 27 28 29 30 31 |
<div class="row"> <div class="col s12 m12 l3"> <div class="row"> <%= render 'shared/user-info' %> </div> </div> <div class="col l8 offset-l1 s12 page-content center"> <div class="row"> <div class="card hoverable"> <div class="card-content"> <div class="row"> <div class="col m12"> <%= form_for @post, url: "/posts", :method => "post" do |f| %> <div class="input-field"> <%= f.text_area :body, placeholder: "What are you thinking about?", class: "materialize-textarea col l9" %> </div> <%= f.submit "Send", class: "btn red lighten-2" %> <% end %> </div> </div> </div> </div> </div> <div class="row"> <% @posts.reverse_each do |post| %> <%= render 'shared/post', post: post %> <% end %> </div> </div> </div> |
6- Não menos importante, vamos criar os estilos para nossas classes, no arquivo assets/stylesheets/users.scss substitua 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 |
.user-name { font-size: 16px; float: left; color: #aaa; } .btn-follow { color: #bdbdbd; } .activity-info { margin-top: 20px; } p.p-info { color: #aaa; font-size: 20px; } .user-card-search { margin-left: 15%; width: 70%; } .user-description { float: left; color: grey; margin-top: 0; display: block; } .follow-title { color: #aaa; } .tabs .tab a{ font-size: 12px; } |
7- Agora no arquivo assets/stylesheets/posts.scss substitua 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 |
.post-user-name { font-size: 18px; float: left; margin-left: 20px; a { color: #aaa; } } .post-date { font-size: 14px; float: right; color: #aaa; } .post-options { float: right; margin-left: 40px; margin-top: -15px; } .card a i.post-options { color: #aaa; } .post-body { text-align: left; } .to-like { color: #aaa; } .liked { color: red; } .post-actions { float: right; margin-right: 50px; } .action-link { color: #aaa; } .post-show .card { margin-left: 15%; width: 70%; } .comment { margin-top: 20px; } |
8 – Atualize o application.scss colocando:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@import "materialize"; @import "material_icons"; @import "shared/navbar"; @import "shared/footer"; @import "pages"; @import "users"; @import "posts"; main .container { margin-top: 20px; } .card .card-content { padding-bottom: 3px; } |
#Agora é hora de criar a action show dos posts
8- No seu arquivo views/posts/show.html.erb substitua o conteudo 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 |
<div class="post-show"> <div class="row center"> <%= render 'shared/post',post: @post %> </div> <div class="row"> <div class="card hoverable"> <div class="card-content"> <div class="row"> <div class="col m12"> <h5>Comments</h5> </div> </div> <div class="row"> <div class="col m12"> <%= form_for @comment, url: "/comments", :method => "post" do |f| %> <div class="input-field"> <%= f.text_area :comment, placeholder: "What are you thinking about this post?", class: "materialize-textarea col l9" %> <%= f.hidden_field :post_id, value: @post.id %> </div> <%= f.submit "Comment", class: "btn red lighten-2" %> <% end %> </div> </div> <% @comments.reverse_each do |comment| %> <div class="divider"></div> <div class="comment"> <div class="row"> <div class="col m2"> <img src="<%= comment.user.avatar.thumb.url %>" alt="" class="circle responsive-img user-image"> </div> <div class="col m7"> <a href="/users/<%= comment.user_id %>" class="comment-user-name grey-text text-darken-2"> <p><%= comment.user.name %></p> </a> </div> <div class="col m2"> <b class="grey-text right"><%= comment.created_at.strftime('%Hh%M') %></b> </div> </div> <div class="row"> <div class="col m12"> <p class="comment-content"><%= comment.comment %></p> </div> </div> </div> <% end %> </div> </div> </div> </div> |
10 – Precisamos fazer um pequeno ajuste nas rotas para o like e unlike, no routes.rb coloque:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
Rails.application.routes.draw do root 'pages#home' resources :comments, only: [:create, :destroy, :edit, :update] resources :posts, only: [:index, :show, :create, :destroy, :edit, :update] do member do post '/like', to: "likes#create", as: "like" delete '/like', to: "likes#destroy", as: "unlike" end end post '/follows', to: "follows#create", as: "follow" delete '/follows', to: "follows#destroy", as: "unfollow" get '/profile/:id', to: "users#show", as: "user" get '/profile/:id/following', to: "users#following", 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 |
11 – Precisamos ajustar o model Post para apagar os likes quando um post for apagado, coloque no model:
1 2 3 4 5 |
class Post < ApplicationRecord belongs_to :user has_many :likes, dependent: :destroy acts_as_commentable end |
12 – Vamos ajustar o application_controller.rb para captura o erro de record not found
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
class ApplicationController < ActionController::Base protect_from_forgery with: :exception before_action :authenticate_user! rescue_from ActiveRecord::RecordNotFound, with: :record_not_found protected def after_sign_in_path_for(current_user) posts_path end def after_sign_up_path_for(current_user) posts_path end private def record_not_found flash[:alert] = "Record not Found" redirect_to root_url end end |
Views, profile, following e followers
1- No seu arquivo views/users/show.html.erb insira o 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 62 |
<div class="row"> <div class="col s12 m12 l3"> <div class="row"> <%= render 'shared/user-info' %> </div> <div class="row"> <div class="card"> <div class="card-tabs"> <ul class="tabs"> <li class="tab"> <a class="active" href="#followers">Followers</a> </li> <li class="tab"> <a href="#following">Following</a> </li> </ul> </div> <div class="card-content grey lighten-4"> <div id="followers"> <ul class="collection"> <% @followers.each do |f| %> <li class="collection-item avatar"> <img src="<%= f.avatar.url %>" class="circle"> <span class="title"> <%= link_to truncate(f.name, length: 15), user_path(f) %> </span> <p> <%= truncate(f.description, length: 15) %> </p> </li> <% end %> </ul> <%= link_to "See all followers", followers_path(@user) %> </div> <div id="following"> <ul class="collection"> <% @following.each do |f| %> <li class="collection-item avatar"> <img src="<%= f.avatar.url %>" class="circle"> <span class="title"><%= link_to f.name, user_path(f) %></span> <p> <%= truncate(f.description, length: 15) %> </p> </li> <% end %> </ul> <%= link_to "See all following", following_path(@user) %> </div> </div> </div> </div> </div> <div class="col l1"></div> <div class="col l8 page-content center"> <div class="row"> <% @posts.each do |post| %> <%= render 'shared/post', post: post %> <% end %> </div> </div> </div> |
2- Agora no seu arquivo /views/users/followers.html.erb insirá o conteúdo:
1 2 3 4 5 6 7 8 9 10 |
<div class="row"> <div class="col s12 m12 l4"> <%= render 'shared/user-info' %> </div> <div class="col l1"></div> <div class="col l7 page-content"> <h4 class="follow-title">Followers</h4> <%= render 'shared/list-user', users: @followers %> </div> </div> |
3- No arquivo /views/users/following.html.erb insira:
1 2 3 4 5 6 7 8 9 10 |
<div class="row"> <div class="col s12 m12 l4"> <%= render 'shared/user-info' %> </div> <div class="col l1"></div> <div class="col l7 page-content"> <h4 class="follow-title">Following</h4> <%= render 'shared/list-user', users: @following %> </div> </div> |
4- Agora vamos criar nossas partials, em /views/shared crie um arquivo chamado _list-user.html.erb e coloque nele:
1 2 3 |
<% users.each do |user| %> <%= render 'shared/card-user', user: user %> <% end %> |
5- Vamos criar a partial /shared/_card-user.html.erb que é chamada dentro de _list-user e inserir o 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 |
<div class="row"> <div class="card user-card"> <div class="card-content"> <div class="row"> <div class="col s2 m2 l2"> <img src="<%= user.avatar.thumb.url %>" alt="" class="circle responsive-img"> </div> <div class="col s10 m10 l10"> <div class="row"> <p class="post-user-name"> <%= link_to user.name, user_path(user.id) %> </p> </div> <div class="row"> <p> <%= link_to truncate(user.description, length: 150), user_path(user), class: 'grey-text text-darken-2' %> </p> </div> </div> </div> </div> </div> </div> |
Implementando o search user
1- Para ser possível que o campo de busca fique no header, altere seu application_controller para:
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 |
class ApplicationController < ActionController::Base protect_from_forgery with: :exception before_action :authenticate_user! before_action :set_search rescue_from ActiveRecord::RecordNotFound, with: :record_not_found protected def after_sign_in_path_for(current_user) posts_path end def after_sign_up_path_for(current_user) posts_path end def set_search @q = User.ransack(params[:q]) end private def record_not_found flash[:alert] = "Record not Found" redirect_to root_url end end |
2- Agora altere o seu arquivo views/shared/_navbar.html.erb para:
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 77 78 79 80 |
<div class="navbar-fixed"> <nav class="top-nav"> <div class="nav-wrapper col s12"> <div class="container"> <div class="row"> <div class="col m3"> <% 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> </div> <ul class="right hide-on-med-and-down"> <% if current_user %> <div class="row"> <div class="col m7 offset-m3"> <li class="search-field"> <%= search_form_for @q, url: search_path do |f| %> <%= f.search_field :name_cont, placeholder: "Search.." %> <% end %> </li> </div> <div class="col m2"> <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> </div> <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> </div> <% else %> <li><a href="/users/sign_in">Login</a></li> <li><a href="/users/sign_up">Register</a></li> <% end %> </ul> </div> </div> </div> </nav> </div> <ul class="side-nav" id="side-bar"> <% if current_user %> <li class="search-field"> <%= search_form_for @q, url: search_path do |f| %> <%= f.search_field :name_cont, placeholder: "Search.." %> <% end %> </li> <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> |
3- No seu arquivo _navbar.scss substitua o conteúdo por:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
nav{ .brand-logo{ margin-left: 10px; .logo-title{ font-size: 20px; } } .nav-image { width: 50px; margin-top: 8px; } } .search-field { margin-right: 400px; width: 300px; } |
4- Agora seu arquivo views/users/search.html.erb adicione o conteudo:
1 2 3 4 5 6 7 8 9 10 |
<div class="row"> <div class="col s12 m12 l4"> <%= render 'shared/user-info' %> </div> <div class="col l1"></div> <div class="col l7 page-content"> <h4 class="follow-title">People</h4> <%= render 'shared/list-user', users: @users %> </div> </div> |
Parabéns por chegar até aqui \o/,
Te vejo na aula 3 para continuar nossa jornada,
Leonardo Scorza