
Neste tutorial você vai aprender como desenvolver um sistema de votação completo e um sistema administrativo associado para gerenciar os usuários, campanhas e likes usando Ruby On Rails e Bulma.
Através deste projeto você vai evoluir suas habilidades na criação de sistemas com Ruby On Rails, criação de sistemas administrativos (que são fundamentais na maior parte dos projetos) e uso do javascript para fazer chamadas assíncronas para o backend, então se prepare e venha com a gente construir esse projeto em 20 minutos 😁
Ferramentas
1. Ruby 2.5
2. Ruby on Rails 5.2
3. yarn
4. Devise
5. Rails admin
6. Active Storage
Detalhes do APP
Vamos desenvolver um sistema de votação que vai permitir que os usuários votem aprovando ou desaprovando uma campanha que criaremos na parte administrativa, esse APP também nos permitirá bloquear essa campanha quando ela chegar ao final e ver o resultado da votação.
Cada tipo de usuário terá uma função definida no app, a função do administrador é criar, bloquear, editar e excluir a campanha enquanto o usuário, tem o papel de votar se gostou ou não gostou de uma determinada campanha.
Para o desenvolvimento das telas de votação o sistema contará com o framework bulma para facilitar a criação de páginas responsívas e para a criação do administrativo usaremos a gem do Rails Admin que nos permitirá criar um admin completo em poucos minutos.
Também utilizaremos o Devise para o controle de sessão e o Active Storage para salvar as imagens das campanhas.
Para uma explicação mais aprofundada sobre o Rails Admin dê uma olhada no curso completo do OneBitCode: Criando um sistema Administrativo Completo com Rails Admin


Passo a Passo da criação do APP
Preparando nossa Aplicação
1. Agora vamos criar um novo projeto com o nome your vote.
1 |
rails new your_vote |
2. Entre na pasta do projeto que foi criado.
1 |
cd your_vote |
3. Adicione no Gemfile:
1 2 |
gem 'devise' gem 'rails_admin', '~> 1.4', '>= 1.4.2' |
4. Instale as gems com o seguinte comando:
1 |
bundle install |
5. O arquivo package.json deve ficar com o seguinte conteúdo (estamos incluindo o Bulma, Fontawesome e jquery):
1 2 3 4 5 6 7 8 9 10 11 12 13 |
{ "name": "voting", "private": true, "dependencies": { "@fortawesome/fontawesome-free": "^5.8.1", "@rails/webpacker": "3.5", "bulma": "0.7.2", "jquery": "^3.3.1" }, "devDependencies": { "webpack-dev-server": "2.11.2" } } |
O Yarn é um gerenciador de pacotes javascript rápido, seguro e confiável que foi integrado no rails > 5.1 para facilitar ainda mais a gestão das dependências. Assim como o Bundler o Yarn possui um arquivo onde as dependências são declaradas chamado package.json
6. Para instalar as dependências listadas no package.json:
1 |
yarn install |
7. Para importar, deixe o arquivo _app/assets/javascripts/application.js_ com o seguinte conteúdo:
1 2 3 4 5 |
//= require rails-ujs //= require activestorage //= require turbolinks //= require jquery/dist/jquery.min //= require_tree . |
8. Renomeie o arquivo application.css para application.scss:
1 |
mv app/assets/stylesheets/application.css app/assets/stylesheets/application.scss |
9. Para realizar os imports, o arquivo app/assets/stylesheets/application.scss deve ficar com o seguinte conteúdo:
1 2 |
@import "bulma/bulma"; @import "@fortawesome/fontawesome-free/css/all.css"; |
10. Para instalar o Active Storage (gerar a migration dele) rode no console:
1 |
rails active_storage:install |
11. Para configurar o devise no seu app execute o seguinte comando no terminal:
1 |
rails generate devise:install |
12. Para instalar o Rails Admin rode no console.
1 |
rails g rails_admin:install |
13. Ao rodar o comando, o Rails Admin perguntará em qual rota você gostaria de instalar. Neste tutorial deixaremos em /admin como no default, para isso aperte enter, mas você poderá escolher outra rota ou até mesmo instalar na home.
1 |
Where do you want to mount rails_admin? Press for [admin] |
Criando os modelos e configurando o Rails Admin
1. Vamos gerar os modelos do sistema.
1 2 3 4 5 6 7 |
rails generate devise user name:string rails generate migration add_admin_to_users admin:boolean rails generate model campaign title description:text user:references blocked:boolean rails generate model like campaign:references user:references kind:boolean |
2. Para configurar o acesso ao administrativo substitua o arquivo config/initializers/rails_admin.rb por:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
RailsAdmin.config do |config| config.authenticate_with do warden.authenticate! scope: :user end config.current_user_method(&:current_user) config.authorize_with do redirect_to main_app.root_path unless current_user.admin? end config.actions do dashboard # mandatory index # mandatory new export bulk_delete show edit delete show_in_app end end |
Para que o Rails Admin libere o acesso correto ao user admin, usamos as seguintes configurações:
– config.authenticate_with: informamos ao RailsAdmin que o usuário tem que estar devidamente autenticado.
– config.authorize_with: informamos que o usuário tem que ser do tipo admin para ter a autorização de usar o administrativo.
3. Para criar o banco de dados e as tabelas do app rode no terminal o seguinte comando:
1 |
rails db:create db:migrate |
4. Adicione ao modelo user em app/models/user.rb.
1 2 3 |
has_many :campaigns has_many :likes,dependent: :delete_all validates_presence_of :name |
5. Em app/models/campaign.rb substitua:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class Campaign < ApplicationRecord #Para utilizar o Active Storage basta adicionar attached ao model has_one_attached :image belongs_to :user has_many :likes validates_presence_of :title, :description before_update :is_blocked private # verifica se a campanha está bloqueada e aborta o update da Campaing def is_blocked campaign = Campaign.find(self.id) if campaign.blocked throw(:abort) end end end |
No model Campaign é onde utilizaremos o Active Storage para armazenar as fotos, para isso adicione ao modelo has_one_attached.
Campaign pertence ao usuário, para criar um modelo que pertence a outro modelo utiliza-se o references.
ex: rails g model campaign user:referencesO modelo campaign pode possuir mais de um like, então para funcionar corretamente utilizamos o has_many: likes.
Utilizamos validates_presence_of para informar que aquele atributo não pode ser nulo.
Foi utlizado um callback before_update, o método is_blocked foi criado para verificar se aquela campaign está bloqueada, se a campanha estiver bloqueada será abortado todo o processo de update. Com esse método conseguimos garantir que não ocorrerá nenhuma alteração na campanha depois do bloqueio da mesma.
6. Substitua o código do modelo like em app/models/like.rb:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class Like < ApplicationRecord belongs_to :campaign belongs_to :user before_update :is_blocked private # verifica se a campanha está bloqueada e aborta o update do Like def is_blocked if self.campaign.blocked throw(:abort) end end end |
Nesse modelo também temos um callback before_update, o método is_blocked foi criado para verificar se à campaign que usuário está tentando votar apresenta-se bloqueada, se a campanha estiver bloqueada nenhum usuário irá conseguir realizar a votação.
Preparando a Campaign
1. Crie um controller com o nome campaigns com os métodos index e show usando o seguinte comando:
1 |
rails generate controller campaigns index show |
2. Para criar o controlador do like rode no terminal:
1 |
rails generate controller like |
3. Substitua o código de app/controllers/applications_controller.rb por:
1 2 3 4 5 6 7 8 9 10 11 |
class ApplicationController < ActionController::Base before_action :configure_permitted_parameters, if: :devise_controller? before_action :authenticate_user! protected # permitir adicionar o atributo name através do metodo do devise sign_up def configure_permitted_parameters devise_parameter_sanitizer.permit(:sign_up, keys: [:name]) end end |
Como adicionamos o atributo name, precisamos informar para o devise que o método sing_up irá receber um parâmetro novo, para isso adicionamos o método configure_permitted_parameters.
4. Troque o código do arquivo app/controllers/campaigns_controller.rb por esse:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class CampaignsController < ApplicationController before_action :set_campaigns, only: [:show] def index @campaigns = Campaign.all end def show set_likes_count if @campaign.blocked end private def set_campaigns @campaign = Campaign.find(params[:id]) end def set_likes_count @likes = @campaign.likes.where(kind: true).count @dislikes = @campaign.likes.where(kind: false).count end end |
5. Substitua o código do arquivo app/views/campaigns/index.html.erb por esse:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<section id="dashboard" class="section has-text-white has-text-centered dashboard"> <div class="campaigns"> <h2 class="is-size-5 has-text-white-semibold">All Campaigns</h2> <% if @campaigns.count > 0 %> <br/> <section class="container"> <div class="columns features"> <%= render @campaigns %> </div> </div> <% end %> </div> </section> |
Esse código será responsável por mostrar todas as campanhas sendo que a variável @campaigns foi instanciada no método index do controller campaign.
6. Substitua o código do arquivo app/views/campaigns/show.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 |
<section class="container"> <div class="columns features is-centered"> <div class="column is-4"> <div class="section"> <div class="card is-shady"> <div class="card-image"> <div class="section"> <figure class="image is-3by3"> <%= image_tag @campaign.image %> </figure> </div> </div> <div class="card-content"> <div class="content section"> <h2 class="title has-text-centered"><%= @campaign.title %></h2> <p><%= @campaign.description %></p> <time datetime="2016-1-1"><%= @campaign.created_at %></time> </div> </div> <% if @campaign.blocked %> <div class="section"> <div class="has-text-centered"> <i><%= @likes %> gostaram</i> </div> <div class="has-text-centered"> <i><%= @dislikes %> não gostaram</i> </div> </div> <% else %> <div class="has-text-centered section"> <i id="liked"></i> <i id="disliked"></i> </div> <% end %> </div> </div> </div> </div> </section> |
Esse código será responsável por mostrar de forma detalhada uma determinada campaign.
7. Crei um arquivo _campaign.html.erb em app/views/campaigns e adicione:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<div class="column is-4"> <%= link_to campaign do %> <div class="card is-shady"> <div class="card-image section"> <figure class="image is-3by3"> <%= image_tag campaign.image %> </figure> </div> <div class="card-content"> <div class="content section"> <h4><%= campaign.title %> <%= (campaign.blocked ? "- Bloqueada" : "") %></h4> <p><%= campaign.description %></p> </div> </div> </div> <% end %> </div> |
Aqui será executado pelo código presente no index.html, <%= render @campaigns %>. Nesta parte ocorrerá a criação, com o auxílio do framework Bulma, dos cards da campaing.
Customizando o Devise
1. Nesta parte iremos customizar as telas do devise para isso vamos gerar as views do devise.
1 |
rails generate devise:views |
2. Em app/views/devise/registrations/new.html.erb troque 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 |
<section class="section"> <div class="container column is-half"> <h1 class="is-size-3 is-size-4-mobile has-text-white-semibold has-text-white has-text-centered">Sign up</h1> </div> <div class="container column is-half login"> <form class="new_user" id="new_user" action="/users" accept-charset="UTF-8" method="post"> <%= hidden_field_tag :authenticity_token, form_authenticity_token %> <%= devise_error_messages! %> <div class="field"> <p class="control has-icons-left"> <input autofocus="autofocus" class="input" placeholder="Full name" autocomplete="name" type="text" name="user[name]" id="user_name" > <span class="icon is-small is-left"> <i class="fa fa-user"></i> </span> </p> </div> <div class="field"> <p class="control has-icons-left has-icons-right"> <input class="input" placeholder="Email" autofocus="autofocus" autocomplete="email" type="email" value="" name="user[email]" id="user_email"> <span class="icon is-small is-left"> <i class="fas fa-envelope"></i> </span> <span class="icon is-small is-right"> <i class="fas fa-check"></i> </span> </p> </div> <div class="field"> <p class="control has-icons-left"> <input class="input" placeholder="Password" autocomplete="current-password" type="password" name="user[password]" id="user_password" > <span class="icon is-small is-left"> <i class="fas fa-lock"></i> </span> </p> </div> <div class="field"> <p class="control has-icons-left"> <input class="input" placeholder="Password Confirmation" autocomplete="current-password" type="password" name="user[password_confirmation]" id="user_password_confirmation" > <span class="icon is-small is-left"> <i class="fas fa-lock"></i> </span> </p> </div> <div class="field"> <p class="control center"> <button class="button is-fullwidth is-rounded is-primary" value="Sign up" type="submit" name="commit"> Sign up </button> </p> </div> </form> </div> <div class="container column is-half center"> <br/> <a class="button is-fullwidth is-rounded is-outlined is-link" href="/users/sign_in">Login</a><br /> </div> </section> |
3. Em app/views/devise/sessions/new.html.erb troque 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 |
<section class="section"> <div class="container column is-half"> <h1 class="is-size-3 is-size-4-mobile has-text-white-semibold has-text-white has-text-centered">Login</h1> </div> <div class="container column is-half login"> <form class="new_user" id="new_user" action="/users/sign_in" accept-charset="UTF-8" method="post"> <%= hidden_field_tag :authenticity_token, form_authenticity_token %> <%= devise_error_messages! %> <div class="field"> <p class="control has-icons-left has-icons-right"> <input autofocus="autofocus" class="input" placeholder="Email" autofocus="autofocus" autocomplete="email" type="email" value="" name="user[email]" id="user_email"> <span class="icon is-small is-left"> <i class="fas fa-envelope"></i> </span> <span class="icon is-small is-right"> <i class="fas fa-check"></i> </span> </p> </div> <div class="field"> <p class="control has-icons-left"> <input class="input" placeholder="Password" autocomplete="current-password" type="password" name="user[password]" id="user_password" > <span class="icon is-small is-left"> <i class="fas fa-lock"></i> </span> </p> </div> <div class="field"> <input name="user[remember_me]" type="hidden" value="0" /><input type="checkbox" value="1" name="user[remember_me]" id="user_remember_me" /> <label for="user_remember_me">Remember me</label> </div> <div class="field"> <p class="control center"> <button class="button is-fullwidth is-rounded is-link" type="submit" name="commit"> Login </button> </p> </div> </form> </div> <div class="container column is-half center"> <br/> <a class="button is-fullwidth is-rounded is-outlined is-primary" href="/users/sign_up">Sign up</a> <a class="button is-fullwidth is-rounded is-outlined is-warning" href="/users/password/new">Forgot your password?</a> </div> </section> |
Criando o Menu principal
1. Vamos criar uma pasta chamada shared em app/views/ e um arquivo dentro da pasta chamado _menu.html.erb e dentro do arquivo coloque:
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 |
<nav class="navbar" role="navigation" aria-label="main navigation"> <div class="navbar-brand"> <a class="navbar-item" href="/"> <strong>Your Vote</strong> </a> </div> <div id="navbarBasicExample" class="navbar-menu"> <div class="navbar-start"> <% if current_user && current_user.admin %> <a class="navbar-item" href="/admin"> Administrativo </a> <% end %> </div> <div class="navbar-end"> <div class="navbar-item"> <div class="buttons"> <% if !current_user %> <%= link_to('Log in', new_user_session_path, :class => "button is-info") %> <%= link_to('Sign up', new_user_registration_path, :class => "button is-primary") %> <% end %> <% if current_user %> <%= link_to('Logout', destroy_user_session_path, method: :delete, :class => "button is-light") %> <% end %> </div> </div> </div> </div> </nav> |
Esse código é resposável por criar o menu do sistema com as opções: Home, Log in, Sign up, Logout e o administrativo caso o usuário seja o administrador.
2. Atualize o arquivo app/views/layouts/application.html.erb com:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<!DOCTYPE html> <html> <head> <title>Vote</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <%= csrf_meta_tags %> <%= csp_meta_tag %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> </head> <body> <%= render "shared/menu" %> <%= yield %> </body> </html> |
Estilizando as Views
1. Agora vamos ajustar as views alterando o scss, em app/assets/stylesheets/application.scss troque por:
1 2 3 4 5 6 7 |
@import "bulma/bulma"; @import "@fortawesome/fontawesome-free/css/all.css"; @import "./**/*"; html{ background-color: $dark; } |
O @import “./**/*”; está importanto todos os arquivos que esteja dentro da pasta stylesheets
2. Em app/assets/stylesheets/campaigns.scss coloque:
1 2 3 4 5 6 7 8 9 10 |
.card { .image{ height: 100px; } .card-backgroud{ background-color: $grey-dark; border-radius: 5px; } } |
3. Crie um arquivo devise.scss em app/assets/stylesheets/ e adicione:
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 |
body { margin-top: 10px; margin-left: 10px; margin-right: 10px; } .login { margin-top: 10px; } .title{ color: white } .center{ text-align: center; } .label{ color: white } .is-link{ color: white } .field{ color: white } .button{ margin-top: 10px; } .a{ margin-top: 10px; } |
Implementando o Voto (Javascript e Controller)
1. O usuário irá realizar a votação na tela show da campaign e para realizar a requisição usaremos js, em app/assets/javascripts/ crie um arquivo campaigns.js e coloque:
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 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
$(document).on('ready turbolinks:load', function(){ var voidLiked = `<i class="far fa-grin-beam fa-2x" style="color:#D5D3D3"></i>`; var liked = `<i class="fas fa-grin-beam fa-2x"></i>`; var disliked = `<i class="fas fa-angry fa-2x"></i>`; var voidDisliked = `<i class="far fa-angry fa-2x" style="color:#D5D3D3"></i>`; var campaign_id = $(location).attr('href').split("/").pop(); var like_id = 0; getVote(); $("#liked").click(function(){ var vote; if($("#liked").html() == liked){ vote = false; $("#liked").html('').prepend(voidLiked); $("#disliked").html('').prepend(disliked); }else{ vote = true; $("#liked").html('').prepend(liked); $("#disliked").html('').prepend(voidDisliked); } sendVote(vote); }); $("#disliked").click(function(){ var vote; if($("#disliked").html() == disliked){ vote = true; $("#liked").html('').prepend(liked); $("#disliked").html('').prepend(voidDisliked); }else{ vote = false; $("#liked").html('').prepend(voidLiked); $("#disliked").html('').prepend(disliked); } sendVote(vote); }); function getVote(){ // get like kind from campaign_id var settings = { "async": true, "crossDomain": true, "url": window.location.href.split("campaign")[0]+"like/"+campaign_id, "method": "GET", "headers": { "cache-control": "no-cache" } } $.ajax(settings).done(function (response) { if(response.length == 0){ $( "#liked" ).append( voidLiked ); $( "#disliked" ).append( voidDisliked ); }else{ like_id = response[0].id if(response[0].kind){ $("#liked").html('').prepend(liked); $("#disliked").html('').prepend(voidDisliked); }else{ $("#disliked").html('').prepend(disliked); $( "#liked" ).append( voidLiked ); } } }); } function sendVote(vote){ if(like_id == 0){ createVote(vote); }else{ updateVote(vote); } } function createVote(vote){ var data = JSON.stringify({"like": {"kind": vote,"campaign_id": parseInt(campaign_id)} }); var settings = { "async": true, "crossDomain": true, "url": window.location.href.split("campaign")[0]+"like", "method": "POST", "headers": { "cache-control": "no-cache", "Content-Type": "application/json" }, "processData": false, "data": data } $.ajax(settings).done(function (response) { if(response.kind == vote){ like_id = response.id alert("Created!"); }else{ alert("Something went wrong!"); } }); } function updateVote(vote){ var data = JSON.stringify({"like": {"kind": vote} }); var settings = { "async": true, "crossDomain": true, "url": window.location.href.split("campaign")[0]+"like/"+like_id, "method": "PUT", "headers": { "cache-control": "no-cache", "Content-Type": "application/json" }, "processData": false, "data": data } $.ajax(settings).done(function (response) { if(response.kind == vote){ like_id = response.id alert("Updated!"); }else{ alert("Something went wrong!"); } }); } }); |
Essas funções são responsáveis por: atualizar o voto da campaign caso o usuário já tenha votado, pegar o evento click e realizar as trocas dos ícones e enviar a escolha do usuário, se gostou ou não gostou, para os métodos create ou update do controlador like.
Importante: caso exista algum arquivo .coffee em app/assets/javascripts, excluí-los.
2. Em app/controllers/like_controller 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 |
class LikeController < ApplicationController before_action :set_campaign, only: [:show] before_action :set_like, only: [:update] def show @like = Like.where(campaign: @campaign, user: current_user) render json: @like end def create @like = Like.new(like_params.merge(user: current_user)) if @like.save render json: @like else render json: @like.errors, status: :unprocessable_entity end end def update if @like.update(like_params) render json: @like else render json: @like.errors, status: :unprocessable_entity end end private def like_params params.require(:like).permit(:kind, :campaign_id) end def set_campaign @campaign = Campaign.find(params[:id]) end def set_like @like = Like.find(params[:id]) end end |
3. Em app/controllers/application_controller troque por:
1 2 3 4 5 6 7 8 9 10 |
class ApplicationController < ActionController::Base before_action :configure_permitted_parameters, if: :devise_controller? before_action :authenticate_user! skip_before_action :verify_authenticity_token protected def configure_permitted_parameters devise_parameter_sanitizer.permit(:sign_up, keys: [:name]) end end |
Como estamos fazendo requisição via ajax é necessário adicionar skip_before_action para evitar o erro InvalidAuthenticityToken
4. Agora para finalizar o desenvolvimento vamos alterar o arquivo routes em config/routes.rb por:
1 2 3 4 5 6 7 |
Rails.application.routes.draw do devise_for :users mount RailsAdmin::Engine => '/admin', as: 'rails_admin' root to: "campaigns#index" resources :campaigns, only: [:index, :show] resources :like, only: [:show, :create, :update] end |
Testando nossa Aplicação
1. Execute o seguinte commando e abra o browser em https://localhost:3000.
1 |
rails s |
2. Crie um usuário e acesse a página de listagem de campanhas:

3 – Pare o servidor e abra o rails console (digitando rails c) e rode nele:
1 |
User.last.update(admin: true) |
Transformamos nossa usuário em admin.
4 – Suba novamente o servidor e visite http://localhost:3000/admin
5 – Crie uma nova campanha (com nome, descrição, imagem e user como dono):
6 – Visite http://localhost:3000 e veja que sua campanha está na listagem.
dating a girl with a twin sister
7 – Entre nela e faça seu voto o/
speed dating los angeles over 50
8 – Edite ela no admin para bloqueá-la (assim podemos ver a contagem final de votos):
9 – Visite novamente sua campanha.
10 – Pronto, agora você pode deixar online, criar suas campanhas e compartilhar o link 😀
Conclusão
Através da criação deste sistema de votação conseguimos perceber como o Rails Admin pode facilitar e acelerar a criação de um projeto quando precisamos desenvolver a parte administrativa e como o Bulma nos permite criar interfaces bonitas e responsívas facilmente.
Se você gostou do projeto ou tem alguma dúvida ou sugestão deixe um comentário ai em baixo para sabermos, se possível divulgue este artigo para seus(uas) amigos(as) programadores(as) para espalhar o conhecimento.
Muito Obrigado 😁

Não perca nenhum conteúdo
Receba nosso resumo semanal com os novos posts, cursos, talks e vagas o/
Primeira vez no OneBitCode? Curtiu esse conteúdo?
O OneBitCode tem muito mais para você!
O OneBitCode traz conteúdos de qualidade, e em português, sobre programação com foco em Ruby on Rails e também JavaScript.
Além disso, aqui sempre levamos à você conteúdos valiosos sobre a carreira de programação, dicas sobre currículos, portfólios, perfil profissional, soft skills, enfim, tudo o que você precisa saber para continuar evoluindo como Programador(a)!
Fique por dentro de todos os conteúdos o/
Nossas redes sociais:
📹 • https://youtube.com/Onebitcode [Live todas as terças-feiras às 19h)
💻 • https://linkedin.com/company/onebitcode
🙂 • https://facebook.com/onebitcode
📱 • https://instagram.com/one_bit_code
🐦 • https://twitter.com/onebitcode
Nossos cursos:
🥇 • Programador Full Stack Javascript em 8 Semanas
💎 • Curso Completo de Ruby
⚙ • Minicurso: API Rails 5 Completo
🐞 • Minicurso de Testes para Ruby on Rails com RSpec
Espero que curta nossos conteúdos e sempre que precisar de ajuda, fala com a gente!
Estamos aqui para você 🙂
Bem-vindo à família OneBitCode o/
Não é possivel desbloquear um campanha que foi bloqueada.
“Campaign failed to be updated
-”
Para deletar uma campanha com votos, acrescentar “dependent: :delete_all” no “has_many :likes” do Model da campanha
Obrigado pela contribuição,
Inclui no artigo o delete_all 🙂
Só uma obs, vc colocou no delete_all no model User e não no Campaign.
Parabéns mais uma vez pelo tutorial. Um adendo que poderia fazer é nas views que exibem
campaign
você poderia adicionar e apresentar da forma abaixo, para caso o usuário não adicione imagem:Obrigado por acompanhar o tutorial o/
Show o tutorial! Só o botão de votação que não ta rolando. Tentando entender aqui qual foi.