Вот несколько вопросов для вас:
- Вы когда-нибудь участвовали в опросах на сайте? Да нет?
- Вы сами создали опросы? Да нет?
- Как насчет создания сегодня веб-приложения, позволяющего создавать пользователям собственные опросы и участвовать в них ?! Да нет?
В этой статье я собираюсь показать вам, как создать веб-приложение, которое позволяет аутентифицированным пользователям создавать, управлять и участвовать в опросах. При его создании мы обсудим следующее:
- Вложенные атрибуты в Rails и самоцвете Cocoon
- Отношения многие ко многим с промежуточной таблицей
- Аутентификация через Facebook с использованием стратегии OmniAuth
- Использование плагина jQuery Validate и AJAX для улучшения работы пользователя
- Кеширование моделей, счетчики кэшей (с гемом cache_culture) и энергичная загрузка для повышения производительности
- Визуализация статистики опроса с помощью индикаторов прогресса Bootstrap (и немного математики)
Все это займет всего шесть итераций! Здорово? Тогда начнем!
Исходный код доступен на GitHub .
Рабочую демонстрацию можно найти по адресу http://sitepoint-poller.herokuapp.com .
Некоторые наземные работы
Я не смог придумать какое-нибудь классное название для нашего веб-сервиса, поэтому для простоты мы назовем его Poller. Создайте новое приложение Rails без набора тестов по умолчанию:
$ rails new poller -T
У нас будет много опросов, созданных разными пользователями с неограниченным количеством вариантов голосования. Создание единой таблицы, в которой будут содержаться как темы для голосования, так и список возможных вариантов, нецелесообразно, поскольку каждый пользователь может голосовать только один раз. Кроме того, опция, которую выбирает каждый пользователь, также будет записана для подсчета общего количества голосов. Таким образом, связь между опцией голосования и пользователем будет существовать.
Поэтому давайте создадим две отдельные таблицы — polls
vote_options
Первая таблица имеет только одно поле (кроме id
created_at
updated_at
-
topic
text
Таблица vote_options
-
title
string
-
poll_id
integer
vote_option
polls
Создайте и примените соответствующие миграции:
$ rails g model Poll topic:text
$ rails g model VoteOption title:string poll:references
$ rake db:migrate
Измените файлы модели, добавив связи и некоторые проверки:
модели / poll.rb
[...]
has_many :vote_options, dependent: :destroy
validates :topic, presence: true
[...]
модели / vote_option.rb
[...]
validates :title, presence: true
[...]
Теперь пришло время разобраться с представлениями, контроллерами и маршрутами. Создайте PollsController
polls_controller.rb
class PollsController < ApplicationController
def index
@polls = Poll.all
end
def new
@poll = Poll.new
end
def create
@poll = Poll.new(poll_params)
if @poll.save
flash[:success] = 'Poll was created!'
redirect_to polls_path
else
render 'new'
end
end
private
def poll_params
params.require(:poll).permit(:topic)
end
end
и соответствующие маршруты:
конфиг / routes.rb
resources :polls
root to: 'polls#index'
Я фанат Twitter Bootstrap, чтобы помочь нам создать красивый дизайн, поэтому добавьте его в Gemfile :
Gemfile
[...]
gem 'bootstrap-sass'
[...]
Не забудьте запустить bundle install
Переименуйте ваш application.css в application.css.scss и замените его содержимое на:
@import 'bootstrap';
@import 'bootstrap/theme';
Измените макет следующим образом:
макеты / application.html.erb
[...]
<div class="navbar navbar-inverse">
<div class="container">
<div class="navbar-header">
<%= link_to 'Poller', root_path, class: 'navbar-brand' %>
</div>
<ul class="nav navbar-nav">
<li><%= link_to 'Add poll', new_poll_path %></li>
</ul>
</div>
</div>
<div class="container">
<% flash.each do |key, value| %>
<div class="alert alert-<%= key %>">
<%= value %>
</div>
<% end %>
<div class="page-header">
<h1><%= yield :page_header %></h1>
</div>
<%= yield %>
</div>
[...]
Мы используем yield :page_header
div
h1
Создайте несколько видов:
опросы / index.html.erb
<% content_for(:page_header) {"Participate in our polls right now!"} %>
опросы / new.html.erb
<% content_for(:page_header) {"Create a new poll"} %>
<%= render 'form' %>
опросы / _form.html.erb
<%= form_for @poll do |f| %>
<%= render 'shared/errors', object: @poll %>
<div class="form-group">
<%= f.label :topic %>
<%= f.text_area :topic, rows: 3, required: true, class: 'form-control' %>
</div>
<%= f.submit 'Create', class: 'btn btn-primary btn-lg' %>
<% end %>
общий / _errors.html.erb
<% if object.errors.any? %>
<div class="alert alert-warning">
<h4>The following errors were found:</h4>
<ul>
<% object.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
Земляная работа завершена, и мы готовы перейти к сути статьи.
Создание опций голосования
Остановись на секунду и взгляни на то, что уже сделано. У нас есть несколько методов контроллера и несколько представлений для просмотра и создания новых опросов. Однако у нас нет страницы для создания вариантов голосования для определенного опроса. Должен ли отдельный контроллер и представление быть созданы для этой цели? Предположим, вам нужно десять вариантов голосования, хотите ли вы отправить форму десять раз?
Создание вариантов голосования наряду с созданием опроса намного лучше. Это может быть достигнуто путем использования вложенных атрибутов Rails, чтобы позволить сохранять атрибуты связанных записей через родительский элемент.
Прежде всего, мы должны включить вложенные атрибуты в poll.rb (потому что Poll
VoteOption
модели / poll.rb
[...]
accepts_nested_attributes_for :vote_options, :reject_if => :all_blank, :allow_destroy => true
[...]
:reject_if => :all_blank
:allow_destroy => true
редактировать страницу опроса, которую мы вскоре создадим).
Чтобы разрешить создание неограниченного количества вариантов голосования на одной странице, вы можете написать несколько вспомогательных функций и немного JavaScript. Однако для этой демонстрации мы будем использовать драгоценный камень Cocoon, созданный Натаном Ван дер Аувера, чтобы помочь нам быстро достичь желаемого результата. Этот гем помогает в создании динамических вложенных форм и работает с базовыми формами Rails, Formtastic и Simple_form.
Добавьте эти драгоценные камни в свой Gemfile:
Gemfile
[...]
gem 'jquery-turbolinks'
gem "cocoon"
[...]
и запустите bundle install
jquery-turbolinks следует добавлять, только если вы используете Turbolinks . Он возвращает событие jQuery document.ready
Включите соответствующие файлы JavaScript:
application.js
[...]
//= require jquery.turbolinks
//= require cocoon
[...]
Последнее, что нужно сделать перед началом построения вложенной формы, это немного изменить метод контроллера, чтобы разрешить некоторые новые атрибуты:
polls_controller.rb
[...]
def poll_params
params.require(:poll).permit(:topic, vote_options_attributes: [:id, :title, :_destroy])
end
[...]
Разрешение :_destroy
Давайте перейдем к актуальной форме:
опросы / _form.html.erb
<%= form_for @poll do |f| %>
<%= render 'shared/errors', object: @poll %>
<div class="form-group">
<%= f.label :topic %>
<%= f.text_area :topic, rows: 3, required: true, class: 'form-control' %>
</div>
<div class="panel panel-default">
<div class="panel-heading">Options</div>
<div class="panel-body">
<%= f.fields_for :vote_options do |options_form| %>
<%= render 'vote_option_fields', f: options_form %>
<% end %>
<div class="links">
<%= link_to_add_association 'add option', f, :vote_options %>
</div>
</div>
</div>
<%= f.submit 'Create', class: 'btn btn-primary btn-lg' %>
<% end %>
Вспомогательный метод fields_for
vote_option_fields
link_to_add_association
div class="links"
Этот метод принимает имя ссылки для отображения на странице, объект построителя форм и множественное имя ассоциации. Необходимо обернуть этого помощника partial
Кроме того, этот помощник ожидает найти частичные « singular_association_name _fields» внутри того же каталога, откуда он был вызван. Если вы хотите, чтобы он использовал другое частичное, используйте link_to_add_association 'add something', f, :somethings,
:partial => 'shared/something_fields'
<div class="nested-fields">
<div class="form-group">
<%= f.label :title %>
<%= f.text_field :title, class: 'form-control', required: true %>
</div>
<%= link_to_remove_association "remove option", f %>
</div>
Есть много других опций, которые можно передать этому помощнику.
Создайте новый фрагмент:
опросы / vote_option_fields.html.erb
nested-fields
Здесь требуется обертка с классом link_to_remove_association
:allow_destroy => true
Когда отправляется родительская форма, соответствующая запись также удаляется (при условии, что вы указали accepts_nested_attributes_for
_destroy
index
Загрузите свой сервер и попробуйте создать опрос и некоторые связанные параметры. Кажется довольно легко, а?
Перечисление и управление опросами
Мы должны связать некоторые свободные концы в этой точке. В частности, страница [...]
def edit
@poll = Poll.find_by_id(params[:id])
end
def update
@poll = Poll.find_by_id(params[:id])
if @poll.update_attributes(poll_params)
flash[:success] = 'Poll was updated!'
redirect_to polls_path
else
render 'edit'
end
end
def destroy
@poll = Poll.find_by_id(params[:id])
if @poll.destroy
flash[:success] = 'Poll was destroyed!'
else
flash[:warning] = 'Error destroying poll...'
end
redirect_to polls_path
end
[...] Это можно легко исправить.
Сначала добавьте еще несколько методов в контроллер:
polls_controller.rb
[...]
<% @polls.each do |poll| %>
<div class="well">
<h2><%= poll.topic %></h2>
<div class="btn-group">
<%= link_to 'Edit', edit_poll_path(poll), class: 'btn btn-default' %>
<%= link_to 'Delete', poll_path(poll),
method: :delete,
class: 'btn btn-danger', data: {confirm: 'Are you sure?'} %>
</div>
</div>
<% end %>
Затем добавьте код для отображения всех доступных опросов:
опросы / index.html.erb
.well {
h2 {
margin-top: 0;
}
}
Также добавьте простой стиль для удаления верхнего поля для заголовков:
application.css.scss
<% content_for(:page_header) {"Edit poll"} %>
<%= render 'form' %>
Наконец, создайте представление редактирования:
опросы / edit.html.erb
[...]
gem 'omniauth-facebook'
[...]
На данный момент, опросы сделаны. Пришло время реализовать основные функции: аутентификацию и фактическое голосование.
Аутентификация
Давайте позволим пользователям проходить аутентификацию через Facebook. Для этого нам потребуется стратегия Facebook для OmniAuth , которую я описал в одном из моих предыдущих постов .
Gemfile
bundle install
Затем запустите Rails.application.config.middleware.use OmniAuth::Builder do
provider :facebook,
ENV['FACEBOOK_KEY'], ENV['FACEBOOK_SECRET'],
scope: 'public_profile', display: 'page', image_size: 'square'
end
Хорошо, теперь настройте вашего нового провайдера Facebook, создав файл omniauth.rb в каталоге config / initializers со следующим содержимым:
конфиг / Инициализаторы / omniauth.rb
uid
Получите пару ключ-секрет, создав новое приложение на странице разработчиков Facebook, откройте вкладку «Настройки» и добавьте следующую информацию:
- Добавить новую «Платформу» («Веб-сайт»)
- Заполните «URL сайта» с адресом вашего сайта
- Заполните «Домены приложения» (должен быть получен из URL сайта)
- Заполните контактный E-mail.
Затем перейдите на вкладку «Статус и обзор» и сделайте ваше приложение активным (это делает его доступным для всех, к вашему сведению). Вернитесь в «Dashboard» и обратите внимание на «App ID» и «App Secret» — вот те ключи, которые вы ищете. Добавьте к инициализатору OmniAuth.
Создать таблицу для хранения информации о пользователе. Нам понадобятся только некоторые основные данные:
-
string
name
-
string
image_url
-
string
$ rails g model User name:string image_url:string uid:string:index
Создайте соответствующую миграцию:
uid
Этот $ rake db:migrate
Теперь примените вашу миграцию:
get '/auth/:provider/callback', to: 'sessions#create'
get '/auth/failure', to: 'sessions#auth_fail'
get '/sign_out', to: 'sessions#destroy', as: :sign_out
Добавьте несколько новых маршрутов:
routes.rb
/auth/:provider/callback
В маршруте class SessionsController < ApplicationController
def create
user = User.from_omniauth(request.env['omniauth.auth'])
cookies[:user_id] = user.id
flash[:success] = "Welcome, #{user.name}!"
redirect_to root_url
end
def destroy
cookies.delete(:user_id)
flash[:success] = "Goodbye!"
redirect_to root_url
end
def auth_fail
render text: "You've tried to authenticate via #{params[:strategy]}, but the following error
occurred: #{params[:message]}", status: 500
end
end
На контроллере:
sessions_controller.rb
request.env['omniauth.auth']
from_omniauth
Метод class User < ActiveRecord::Base
Как видите, в этом контроллере ничего особенного не происходит.
class << self
def from_omniauth(auth)
uid = auth.uid
info = auth.info.symbolize_keys!
user = User.find_or_initialize_by(uid: uid)
user.name = info.name
user.image_url = info.image
user.save!
user
end
end
end
модели / user.rb
current_user
Нам понадобится метод для проверки подлинности пользователя. Традиционно это называется [...]
def current_user
@current_user ||= User.find_by(id: cookies[:user_id]) if cookies[:user_id]
end
helper_method :current_user
[...]
application_controller.rb
helper_method
current_user
[...]
<ul class="nav navbar-nav">
<li><%= link_to 'Add poll', new_poll_path %></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<% if current_user %>
<li><%= image_tag current_user.image_url, alt: current_user.name %></li>
<li><%= link_to 'Logout', sign_out_path %></li>
<% else %>
<li><%= link_to 'Sign in', '/auth/facebook' %></li>
<% end %>
</ul>
[...]
Отлично. Последнее, что нужно сделать, это разрешить пользователю аутентифицировать или отображать информацию о пользователе вместе со ссылкой «Выйти», если вы уже вошли в систему:
макеты / application.html.erb
<% @polls.each do |poll| %>
<div class="well">
<h2><%= poll.topic %></h2>
<p>
<% if current_user %>
<%= link_to 'Participate!', poll_path(poll), class: 'btn btn-primary btn-lg block' %>
<% else %>
Please sign in via <%= link_to 'Facebook', '/auth/facebook' %> to participate in this poll.
<% end %>
</p>
<div class="btn-group">
<%= link_to 'Edit', edit_poll_path(poll), class: 'btn btn-default' %>
<%= link_to 'Delete', poll_path(poll),
method: :delete,
class: 'btn btn-danger', data: {confirm: 'Are you sure?'} %>
</div>
</div>
<% end %>
Прежде чем перейти к следующей итерации, давайте также добавим кнопку «Участвовать» рядом с каждым опросом, если пользователь аутентифицирован:
опросы / index.html.erb
[...]
def show
@poll = Poll.find_by_id(params[:id])
end
[...]
Потребуется новый метод контроллера:
polls_controller.rb
votes
Система аутентификации готова, и пришло время приступить к разработке функций голосования.
голосование
Как мы обсуждали ранее, необходимо установить связь между пользователем и опциями голосования, чтобы отслеживать, какой пользователь выбрал какую опцию. Каждый пользователь может голосовать за множество вариантов (однако он не может голосовать за несколько вариантов, принадлежащих одному опросу), и каждый вариант может быть выбран многими пользователями. Следовательно, нам нужна взаимосвязь «многие ко многим» с промежуточной таблицей (прямое отношение «многие ко многим» также может быть использовано, но оно не такое гибкое).
Давайте назовем эту новую промежуточную таблицу $ rails g model Vote user:references vote_option:references
[...]
add_index :votes, [:vote_option_id, :user_id], unique: true
[...]
Затем немного измените файл миграции:
Миграции / xxx_create_votes.rb
vote_option_id
Это создаст кластеризованный индекс, который обеспечивает уникальность для комбинации user_id
$ rake db:migrate
Очевидно, что один и тот же пользователь не может получить несколько голосов за один и тот же вариант.
Затем примените эту миграцию:
class Vote < ActiveRecord::Base
belongs_to :user
belongs_to :vote_option
end
Ваш файл модели должен выглядеть так:
модели / vote.rb
User
Добавьте эти ассоциации в модели VoteOption
[...]
has_many :votes, dependent: :destroy
has_many :vote_options, through: :votes
[...]
модели / user.rb
[...]
has_many :votes, dependent: :destroy
has_many :users, through: :votes
[...]
модели / vote_option.rb
show
Создайте представление для действия <% content_for(:page_header) {"Share your opinion"} %>
<h2><%= @poll.topic %></h2>
<%= render 'voting_form' %>
опросы / show.html.erb
<%= form_tag votes_path, method: :post, remote: true, id: 'voting_form' do %>
<%= hidden_field_tag 'poll[id]', @poll.id %>
<%= render partial: 'polls/vote_option', collection: @poll.vote_options, as: :option %>
<% if current_user.voted_for?(@poll) %>
<p>You have already voted!</p>
<% else %>
<%= submit_tag 'Vote', class: 'btn btn-lg btn-primary' %>
<% end %>
<% end %>
Фактическая форма голосования взята на отдельную часть — это скоро пригодится.
опросы / _voting_form.html.erb
votes_path
Здесь мы используем несуществующий маршрут voted_for?
Эта форма будет отправлена асинхронно. @poll.vote_options
Метод проверяет, участвовал ли уже пользователь в указанном опросе — он будет создан в ближайшее время.
Как вы можете видеть, мы используем [...]
загруженную загрузку :
def show
@poll = Poll.includes(:vote_options).find_by_id(params[:id])
end
[...]
polls_controller.rb
[...]
resources :votes, only: [:create]
[...]
Добавить новый маршрут:
routes.rb
<div class="form-group">
<%= content_tag(:label) do %>
<% unless current_user.voted_for?(@poll) %>
<%= radio_button_tag 'vote_option[id]', option.id %>
<% end %>
<%= option.title %>
<% end %>
</div>
и создайте частичное:
опросы / _vote_option.html.erb
content_tag
label
voted_for?
Переключатель не отображается, если пользователь уже участвовал в опросе.
Настало время реализовать [...]
метод:
def voted_for?(poll)
vote_options.any? {|v| v.poll == poll }
end
[...]
модели / user.rb
def voted_for?(poll)
Rails.cache.fetch('user_' + id.to_s + '_voted_for_' + poll.id.to_s) { vote_options.any? {|v| v.poll == poll } }
end
Здесь мы проверяем, есть ли у пользователя какие-либо варианты голосования, относящиеся к указанному опросу. Позже вы можете использовать кэширование моделей для повышения производительности приложения, например:
create
и очищать этот кэш каждый раз, когда пользователь участвует в опросе.
Наконец, нам нужен контроллер и метод class VotesController < ApplicationController
def create
if current_user && params[:poll] && params[:poll][:id] && params[:vote_option] && params[:vote_option][:id]
@poll = Poll.find_by_id(params[:poll][:id])
@option = @poll.vote_options.find_by_id(params[:vote_option][:id])
if @option && @poll && !current_user.voted_for?(@poll)
@option.votes.create({user_id: current_user.id})
else
render js: 'alert(\'Your vote cannot be saved. Have you already voted?\');'
end
else
render js: 'alert(\'Your vote cannot be saved.\');'
end
end
end
votes_controller.rb
$('#voting_form').replaceWith('<%= j render 'polls/voting_form' %>');
Здесь проверьте, что все необходимые параметры были отправлены, что пользователь прошел проверку подлинности, и пользователь еще не участвовал в опросе. Если все эти условия выполняются, создайте новое голосование. В противном случае, показать предупреждение с сообщением об ошибке. На взгляд:
голосов / create.js.erb
show
Здесь замените старую форму голосования новой, используя ранее созданную часть.
Давайте добавим немного визуализации для наших опросов.
Отображение статистики голосования
В настоящее время на странице <div class="form-group">
<%= content_tag(:label) do %>
<% unless current_user.voted_for?(@poll) %>
<%= radio_button_tag 'vote_option[id]', option.id %>
<% end %>
<%= option.title %>
<% end %>
<%= visualize_votes_for option %>
</div>
Действительно, это нужно исправить! Однако показывать только количество голосов скучно, поэтому давайте представим статистику, используя преимущества стилей Bootstrap:
опросы / _vote_option.html.erb
visualize_votes_for
Здесь я добавил только одну строку, вызывающую module PollsHelper
def visualize_votes_for(option)
content_tag :div, class: 'progress' do
content_tag :div, class: 'progress-bar',
style: "width: #{option.poll.normalized_votes_for(option)}%" do
"#{option.votes.count}"
end
end
end
end
хелперы / polls_helper.rb
div
Оберните progress-bar
div.progress
div.progress-bar
Эти классы предоставляются
Bootstrap и изначально предназначались для отображения индикаторов выполнения , но я думаю, что мы можем использовать их и в этом случае.
style
normalized_votes_for
Ширина указана в процентах и, очевидно, должна быть не более 100%. Чтобы убедиться в этом, я использую метод [...]
def normalized_votes_for(option)
votes_summary == 0 ? 0 : (option.votes.count.to_f / votes_summary) * 100
end
[...]
модели / poll.rb
votes_summary
Прежде всего, убедитесь, что count
option.votes.count
Так что, если нет голосов за какой-либо вариант опроса, просто верните ноль. В противном случае проверьте, сколько голосов было отдано за указанную опцию, используя метод to_f
Этот результат делится на общее количество голосов, а затем умножается на 100, чтобы преобразовать его в проценты.
Обратите внимание, что votes_summary
[...]
В противном случае результатом деления всегда будет целое число.
def votes_summary
vote_options.inject(0) {|summary, option| summary + option.votes.count}
end
[...]
Этот метод прост. Например, если опрос имеет 10 голосов, вариант A имеет 3 голоса, а вариант B имеет 7 голосов:
- Вариант А : (3/10) * 100 = 30 (%)
- Вариант B : (7/10) * 100 = 70 (%)
Большой! И наконец, нам нужен метод inject
модели / poll.rb
0
Здесь, summary
используется для накопления значений (здесь [...]
.progress {
background-image: linear-gradient(to bottom, #bbb 0%, #ccc 100%)
}
[...]votes_summary
Еще раз, вы можете использовать модель кэширования для повышения производительности.
Последнее, что нужно сделать, это изменить фон индикатора выполнения, чтобы он выглядел немного лучше:
application.css.scss
<%= form_tag votes_path, method: :post, remote: true, id: 'voting_form' do %>
<%= hidden_field_tag 'poll[id]', @poll.id %>
<%= render partial: 'polls/vote_option', collection: @poll.vote_options, as: :option %>
<p><b>Total votes: <%= @poll.votes_summary %></b></p>
[...]
Этот метод [...]
<% @polls.each do |poll| %>
<div class="well">
<h2><%= poll.topic %> <small>(voted: <%= poll.votes_summary %>)</small></h2>
[...]
опросы / _voting_form.html.erb
voted_for?
опросы / index.html.erb
_voting_form
Прежде чем двигаться дальше, я должен предупредить вас о небольшом подвохе. Если вы запустите сервер и попытаетесь проголосовать в опросе, счетчик голосов обновится, но кнопка «Голосовать» останется. Обновите страницу, и кнопка «Голосование» будет заменена текстом «Вы уже проголосовали». Это происходит потому, что Rails кэшировал ассоциацию в false
метод. Когда часть reset
[...]
Существует как минимум три возможных решения этой проблемы.
def create
[...]
@option.votes.create({user_id: current_user.id}) + current_user.votes.create({vote_option_id: @option.id})
current_user.vote_options.reset
[...]
Первый — просто очистить кэш ассоциации после создания нового голосования, используя метод voted_for?
votes_controller.rb
[...]
def voted_for?(poll)
votes.any? {|v| v.vote_option.poll == poll}
end
[...]
Второй переписывает force_reload
метод немного:
user.rb
[...]
def voted_for?(poll)
vote_options(true).any? {|v| v.poll == poll }
end
[...]
Таким образом, мы напрямую указываем промежуточную модель, и Rails мгновенно узнает, что было создано новое голосование.
Третье решение — установить option.votes.count
user.rb
[...]
gem 'counter_culture', '~> 0.1.23'
[...]
Если вы знаете другие решения этой проблемы, пожалуйста, поделитесь ими в комментариях.
Немного кеширования
Возможно, вы заметили, что bundle install
Как насчет добавления кэша счетчика для улучшения этого запроса?
Одним из возможных способов решения этой проблемы является использование драгоценного камня counter_culture, созданного Магнусом фон Келлером.
Бросьте драгоценный камень в свой Gemfile:
Gemfile
$ rails g counter_culture VoteOption votes_count
$ rake db:migrate
и запустите counter_culture :vote_option
Далее запустите генератор и примените созданную миграцию:
[...]
//= require jquery.validate
[...]
Также добавьте следующую строку кода в * voice.rb *:
validate()
Теперь кеш счетчика для подсчета голосов будет создан и управляться автоматически. Круто, не правда ли?
Драгоценный камень counter_culture может использоваться в более сложных сценариях и имеет множество опций, поэтому ознакомьтесь с его документацией.
Немного о проверке на стороне клиента
Чтобы немного улучшить взаимодействие с пользователем, добавьте некоторую проверку на стороне клиента, чтобы проверить, выбрал ли пользователь один из вариантов перед голосованием. Если нет — покажите ошибку вместо отправки формы. Существует отличный плагин jQuery под названием jQuery Validate, который, как следует из названия, помогает создавать различные правила проверки. Просто скачайте файлы плагина, поместите их в vendor / assets / javascripts и включите в ваш проект jquery.validate.js и Additional-method.js :
application.js
[...]
<script data-turbolinks-track="true">
$(document).ready(function() {
var voting_form = $('#voting_form');
voting_form.validate({
focusInvalid: false,
errorClass: 'alert alert-warning',
errorElement: "div",
errorPlacement: function(error, element) { voting_form.before(error); },
rules: {
'vote_option[id]': {
required: true
}
},
messages: {
'vote_option[id]': {
required: "Please select one of the options."
}
}
});
});
</script>
В простейшем случае все, что вам нужно сделать, это использовать метод focusInvalid
Однако у нас есть немного более сложный сценарий, поэтому мы должны предоставить несколько вариантов:
опросы / show.html.erb
errorClass
errorElement
errorPlacement
before
error
Я хочу, чтобы он был помещен перед формой, поэтому я использую метод rules
vote_option[id]
messages
До тех пор, пока мы хотим убедиться, что установлен только один из class UsersController < ApplicationController
def show
@user = User.find_by_id(params[:id])
end
end<% content_for(:page_header) do %>
<%= image_tag @user.image_url, alt: @user.name %>
<%= "#{@user.name}'s profile" %>
<% end %>
<h2>Participation in polls</h2>
<% @user.vote_options.each do |option| %>
<div class="panel panel-default">
<div class="panel-heading"><%= link_to option.poll.topic, poll_path(option.poll) %></div>
<div class="panel-body">
<%= option.title %>
</div>
</div>
<% end %>[...]
def show
@user = User.includes(:vote_options).find_by_id(params[:id])
end
[...][...]
resources :users, only: [:show]
[...]
На этом этапе вы можете проверить, как работает валидация, просто попытавшись отправить форму без включенных переключателей. Потрясающие!
Отображение профиля пользователя
Мы сделали с основной функциональностью. Пользователи могут участвовать в опросах и проверять статистику. Последнее, что нужно сделать, это создать страницу профиля пользователя, чтобы показать опросы, в которых он участвовал. Это можно сделать за три простых шага: создать контроллер, создать представление и добавить новый маршрут.
users_controller.rb
[...]
<ul class="nav navbar-nav navbar-right">
<% if current_user %>
<li><%= image_tag current_user.image_url, alt: current_user.name %></li>
<li><%= link_to 'Profile', user_path(current_user) %></li>
<li><%= link_to 'Logout', sign_out_path %></li>
<% else %>
<li><%= link_to 'Sign in', '/auth/facebook' %></li>
<% end %>
</ul>
[...]
пользователи / show.html.erb
end
Возьмите вариант голосования каждого пользователя и отобразите его вместе с темой опроса. Используйте готовую загрузку здесь, чтобы улучшить производительность:
users_controller.rb
do … end
Не забудьте настроить маршрут:
routes.rb
{}
Последнее, что нужно сделать, это предоставить ссылку на профиль пользователя:
макеты / application.html.erb
bundle install
Загрузите свой сервер и проверьте его!
Вывод
При создании этого приложения Poller мы обсуждали nested_attributes, драгоценный камень Cocoon, рассмотрели нормализацию подсчета голосов и обсудили некоторые вопросы кэширования, такие как кэширование модели и использование драгоценного камня counter_culture. Кроме того, мы рассмотрели плагин jQuery Validate и некоторые модные стили Bootstrap. Думаю, на сегодня этого достаточно!
Конечно, это приложение можно улучшить, добавив больше кэширования, возможность повторного голосования или отмены голосования, немного его стилизации и т. Д. Не стесняйтесь клонировать мой репозиторий с демо-кодом и экспериментировать с ним!
Надеюсь, вы нашли эту статью полезной и интересной (я должен создать опрос, чтобы выяснить это?). Спасибо, что остаетесь со мной до конца и до скорой встречи!