diff --git a/.gitignore b/.gitignore
index 64442bb..14a523f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -39,3 +39,4 @@
/yarn-error.log
yarn-debug.log*
.yarn-integrity
+dump.rdb
\ No newline at end of file
diff --git a/app/channels/application_cable/connection.rb b/app/channels/application_cable/connection.rb
index 8d6c2a1..e3ce64d 100644
--- a/app/channels/application_cable/connection.rb
+++ b/app/channels/application_cable/connection.rb
@@ -2,5 +2,20 @@
module ApplicationCable
class Connection < ActionCable::Connection::Base
+ # identified_by :current_user
+ # def connect
+ # self.current_user = find_verified_user
+ # end
+
+ # private
+ # def find_verified_user
+ # if
+ # verified_user = User.find_by(id: cookies.encrypted['session']['current_user_id'])
+
+ # verified_user
+ # else
+ # reject_unauthorized_connection
+ # end
+ # end
end
end
diff --git a/app/channels/poll_channel.rb b/app/channels/poll_channel.rb
new file mode 100644
index 0000000..16f302b
--- /dev/null
+++ b/app/channels/poll_channel.rb
@@ -0,0 +1,10 @@
+class PollChannel < ApplicationCable::Channel
+
+ def subscribed
+ stream_from "poll_#{params[:room]}"
+ end
+
+ def speak(data)
+ ActionCable.server.broadcast("poll_#{params[:room]}", { message: data["message"] })
+ end
+end
\ No newline at end of file
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index d71aa11..e8119ff 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -19,6 +19,10 @@ def current_user
def current_session_expire
current_session = session[:expire_at]
current_time = Time.current
- session.delete(:current_user_id) if current_session && current_session < current_time
+ if current_session && current_session < current_time
+ session.delete(:current_user_id)
+ else
+ current_session = 20.minutes.from_now
+ end
end
end
diff --git a/app/controllers/chats_controller.rb b/app/controllers/chats_controller.rb
new file mode 100644
index 0000000..4259676
--- /dev/null
+++ b/app/controllers/chats_controller.rb
@@ -0,0 +1,3 @@
+class ChatsController < ApplicationController
+
+end
\ No newline at end of file
diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb
new file mode 100644
index 0000000..04fbd85
--- /dev/null
+++ b/app/controllers/invites_controller.rb
@@ -0,0 +1,30 @@
+class InvitesController < ApplicationController
+ rescue_from ActiveRecord::RecordNotFound do
+ render :new
+ flash[:error] = 'User not found'
+ end
+
+ def new
+ @poll = Poll.find(params[:poll_id])
+ authorize! @poll, to: :invite?
+ end
+
+ def create
+ @poll = Poll.find(params[:poll_id])
+ authorize! @poll, to: :invite?
+ @invited_user = User.where(username: params[:query]).or(User.where(email: params[:query])).take!
+ if !invited?
+ @poll.members << @invited_user
+ redirect_to polls_path, success: "You invite #{@invited_user.username} to your poll"
+ else
+ render :new
+ flash[:error] = 'User alredy invited'
+ end
+ end
+
+ private
+ def invited?
+ @poll.memberships.where(user_id: @invited_user.id).exists?
+ end
+
+end
\ No newline at end of file
diff --git a/app/controllers/polls_controller.rb b/app/controllers/polls_controller.rb
new file mode 100644
index 0000000..c2dde5a
--- /dev/null
+++ b/app/controllers/polls_controller.rb
@@ -0,0 +1,68 @@
+class PollsController < ApplicationController
+
+ rescue_from ActiveRecord::RecordNotFound do
+ redirect_to polls_path, error: I18n.t('flash.poll.not_found')
+ end
+
+ def index
+ authorize! Poll, to: :index?
+ @active = current_user.polls.active
+ @upcoming = current_user.polls.upcoming
+ @ended = current_user.polls.ended
+ end
+
+ def show
+ @poll = Poll.find(params[:id])
+ authorize! @poll, to: :show?
+ @options = @poll.options
+ @members = @poll.members.count
+ end
+
+ def new
+ authorize! Poll, to: :create?
+ @poll = Poll.new
+ 2.times { @poll.options.build }
+ end
+
+ def create
+ authorize! Poll, to: :create?
+ @poll = current_user.own_polls.new(poll_params)
+ if @poll.save
+ @poll.members << current_user
+ redirect_to polls_path, success: I18n.t('flash.poll.create')
+ else
+ render :new
+ end
+ end
+
+ def edit
+ @poll = Poll.find(params[:id])
+ @options = @poll.options
+ authorize! @poll, to: :update?
+ end
+
+ def update
+ @poll = Poll.find(params[:id])
+ authorize! @poll, to: :update?
+
+ if @poll.update(poll_params)
+ redirect_to poll_path(@poll.id), success: I18n.t('flash.poll.update')
+ else
+ render :new
+ end
+ end
+
+ def destroy
+ @poll = Poll.find(params[:id])
+ authorize! @poll, to: :destroy?
+ @poll.destroy
+ redirect_to polls_path, success: I18n.t('flash.poll.deleted')
+ end
+
+
+ private
+
+ def poll_params
+ params.require(:poll).permit(:title, :description, :start_date, :end_date, options_attributes: [:id, :vote_option, :_destroy])
+ end
+end
diff --git a/app/controllers/votes_controller.rb b/app/controllers/votes_controller.rb
new file mode 100644
index 0000000..b42694e
--- /dev/null
+++ b/app/controllers/votes_controller.rb
@@ -0,0 +1,14 @@
+class VotesController < ApplicationController
+
+ def update
+ @poll = current_user.polls.find(params[:poll_id])
+ @option = @poll.options.find(params[:id])
+ authorize! @poll, to: :vote?
+
+ if @poll.memberships.update(poll_option_id: @option.id)
+ redirect_to poll_path(@poll.id), success: 'Successfully voted'
+ else
+ render :show
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/javascript/channels/consumer.js b/app/javascript/channels/consumer.js
index 8ec3aad..40c20f8 100644
--- a/app/javascript/channels/consumer.js
+++ b/app/javascript/channels/consumer.js
@@ -3,4 +3,7 @@
import { createConsumer } from "@rails/actioncable"
+createConsumer('http://127.0.0.1:3000/cable');
+
export default createConsumer()
+
diff --git a/app/javascript/channels/poll_channel.js b/app/javascript/channels/poll_channel.js
new file mode 100644
index 0000000..9382f83
--- /dev/null
+++ b/app/javascript/channels/poll_channel.js
@@ -0,0 +1,36 @@
+import consumer from "./consumer"
+
+
+
+const poll = document.querySelector('#poll');
+const poll_id = poll.dataset.pollId;
+
+ const pollChannel = consumer.subscriptions.create({channel: "PollChannel", room: `poll_${poll_id}`}, {
+ connected() {
+ // Called when the subscription is ready for use on the server
+
+ console.log("Connected to the poll room!");
+ },
+ received(data) {
+ let mess = document.createElement('span'),
+ col = document.createElement('div');
+ col.classList.add('col', 'mb-4')
+ mess.classList.add('bg-info', 'rounded-pill', 'text-white', 'p-2');
+ mess.innerHTML = data.message;
+ col.insertAdjacentElement('afterbegin', mess);
+ document.querySelector('#chat_holder').insertAdjacentElement('beforeend', col)
+
+ console.log(data);
+ },
+
+ disconnected() {
+ // Called when the subscription has been terminated by the server
+ },
+ speak(message) {
+ this.perform('speak', { message: message })
+ }
+ });
+
+
+export default pollChannel;
+
\ No newline at end of file
diff --git a/app/javascript/packs/add_button_script/script.js b/app/javascript/packs/add_button_script/script.js
new file mode 100644
index 0000000..52fdb6e
--- /dev/null
+++ b/app/javascript/packs/add_button_script/script.js
@@ -0,0 +1,30 @@
+document.addEventListener('DOMContentLoaded', () => {
+
+ const addButton = document.querySelector('#btn'),
+ options = document.querySelector('#options');
+
+
+ let counter = 2;
+
+ addButton.addEventListener('click', (e) => {
+ const optionArray = document.querySelectorAll('[name*="[vote_option]"]');
+ e.preventDefault();
+ if (optionArray.length < 5) {
+ const col = document.createElement('div');
+ const input = `
+ `;
+
+ col.classList.add('col-4');
+ col.innerHTML = input;
+ options.insertAdjacentElement('beforeend', col);
+
+ counter++;
+ } else {
+ addButton.style.display = 'none';
+
+ }
+ });
+
+});
\ No newline at end of file
diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js
index f710851..f15ffea 100644
--- a/app/javascript/packs/application.js
+++ b/app/javascript/packs/application.js
@@ -7,7 +7,27 @@ import Rails from "@rails/ujs"
import Turbolinks from "turbolinks"
import * as ActiveStorage from "@rails/activestorage"
import "channels"
+import pollChannel from "../channels/poll_channel"
Rails.start()
-Turbolinks.start()
+// Turbolinks.start()
ActiveStorage.start()
+
+require('./add_button_script/script');
+
+document.addEventListener('DOMContentLoaded', () => {
+ const btn = document.querySelector('#button-addon2'),
+ input = document.querySelector('#message_input');
+
+ btn.addEventListener('click', (e) => {
+ e.preventDefault();
+ let message = input.value;
+ if (message.length > 0) {
+ pollChannel.speak(message);
+ input.value = "";
+ }
+ });
+})
+
+
+
diff --git a/app/models/poll.rb b/app/models/poll.rb
new file mode 100644
index 0000000..cdae8b7
--- /dev/null
+++ b/app/models/poll.rb
@@ -0,0 +1,28 @@
+class Poll < ApplicationRecord
+ belongs_to :user
+ has_many :memberships, class_name: 'PollMembership', foreign_key: "poll_id"
+ has_many :members, -> { distinct }, through: :memberships, source: :user, dependent: :destroy
+ has_many :options, class_name: "PollOption", dependent: :destroy
+
+ accepts_nested_attributes_for :options, allow_destroy: true, reject_if: lambda {|attributes| attributes['vote_option'].blank?}
+
+
+ validates :title, presence: true
+ validates :start_date, presence: true
+ validates :end_date, presence: true
+ validate :end_date_before_start_date
+
+ scope :upcoming, -> { where('start_date > ?', Date.today) }
+ scope :active, -> { where('start_date <= ? and end_date >= ?', Date.today, Date.today) }
+ scope :ended, -> { where('end_date < ?', Date.today) }
+
+ private
+ def end_date_before_start_date
+ return if start_date.blank? || end_date.blank?
+
+ errors.add(:end_date, "can't be earlier than start date ") if end_date <= start_date
+ end
+
+
+
+end
diff --git a/app/models/poll_membership.rb b/app/models/poll_membership.rb
new file mode 100644
index 0000000..83239fc
--- /dev/null
+++ b/app/models/poll_membership.rb
@@ -0,0 +1,5 @@
+class PollMembership < ApplicationRecord
+ belongs_to :user
+ belongs_to :poll
+ belongs_to :poll_option, dependent: :destroy, optional: true
+end
diff --git a/app/models/poll_option.rb b/app/models/poll_option.rb
new file mode 100644
index 0000000..a1d3c62
--- /dev/null
+++ b/app/models/poll_option.rb
@@ -0,0 +1,6 @@
+class PollOption < ApplicationRecord
+ has_many :memberships, dependent: :nullify, class_name: 'PollMembership'
+
+ belongs_to :poll, counter_cache: :vote_count
+
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index 7d38b2b..87f95df 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -5,6 +5,10 @@ class User < ApplicationRecord
VALID_EMAIL_REGEX = /\A[\w+]+@[a-z\d]+\.[a-z]+\z/i
has_secure_password
+ has_many :own_polls, dependent: :destroy, class_name: 'Poll'
+ has_many :memberships, class_name: 'PollMembership', foreign_key: "user_id"
+ has_many :polls,-> { distinct }, through: :memberships, source: :poll
+
validates :username, presence: true
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }, uniqueness: true
diff --git a/app/policies/application_policy.rb b/app/policies/application_policy.rb
index f1e4ed9..e441cdd 100644
--- a/app/policies/application_policy.rb
+++ b/app/policies/application_policy.rb
@@ -7,4 +7,5 @@ class ApplicationPolicy < ActionPolicy::Base
def logged_in?
user.present?
end
+
end
diff --git a/app/policies/poll_policy.rb b/app/policies/poll_policy.rb
new file mode 100644
index 0000000..049b3f5
--- /dev/null
+++ b/app/policies/poll_policy.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+class PollPolicy < ApplicationPolicy
+ def index?
+ logged_in?
+ end
+
+ def create?
+ logged_in?
+ end
+
+ def show?
+ invited?
+ end
+
+ def update?
+ owner?
+ end
+
+ def destroy?
+ owner?
+ end
+
+ def invite?
+ owner?
+ end
+ def vote?
+ invited? && !voted?
+ end
+
+ def owner?
+ user.id == record.user_id
+ end
+
+ def invited?
+ record.memberships.where(user_id: user.id).exists?
+ end
+
+ def voted?
+ record.memberships.where(user_id: user.id).and(record.memberships.where.not(poll_option_id: nil)).exists?
+ end
+end
diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb
index 1210a33..daa2ab3 100644
--- a/app/policies/user_policy.rb
+++ b/app/policies/user_policy.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
class UserPolicy < ApplicationPolicy
-
def create?
!logged_in?
end
diff --git a/app/views/invites/_invite_form.html.erb b/app/views/invites/_invite_form.html.erb
new file mode 100644
index 0000000..01c8a11
--- /dev/null
+++ b/app/views/invites/_invite_form.html.erb
@@ -0,0 +1,8 @@
+<%= form_tag poll_invites_path, method: :post, class: "border rounded p-4 mt-5" do %>
+
+ <%= text_field_tag :query, params[:query], class: "form-control" %>
+
+
+ <%= submit_tag "Send invite", class: "btn btn-primary" %>
+
+<% end %>
\ No newline at end of file
diff --git a/app/views/invites/new.html.erb b/app/views/invites/new.html.erb
new file mode 100644
index 0000000..0d9d35d
--- /dev/null
+++ b/app/views/invites/new.html.erb
@@ -0,0 +1,9 @@
+
+
+
+
Invite friends to poll.
+
Type username or email
+ <%= render "invite_form"%>
+
+
+
\ No newline at end of file
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index 8c4e97f..ed8b624 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -7,7 +7,7 @@
<%= csp_meta_tag %>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
- <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
+ <%= javascript_pack_tag 'application' %>
diff --git a/app/views/pages/index.html.erb b/app/views/pages/index.html.erb
index 74f3a88..aa747cc 100644
--- a/app/views/pages/index.html.erb
+++ b/app/views/pages/index.html.erb
@@ -1,5 +1,5 @@
-
-
-
Make your Poll easier!!
-
+
+
+
Welcome to Fun Poll App
+
\ No newline at end of file
diff --git a/app/views/polls/_active.html.erb b/app/views/polls/_active.html.erb
new file mode 100644
index 0000000..4cede93
--- /dev/null
+++ b/app/views/polls/_active.html.erb
@@ -0,0 +1,18 @@
+
+
+
+
+
Title
+ <%= link_to active.title, active, class: "nav-link" %>
+
+
+
Author
+
<%= active.user.username %>
+
+
+
Start date
+
<%= active.start_date %>
+
+
+
+
\ No newline at end of file
diff --git a/app/views/polls/_edit_poll_form.html.erb b/app/views/polls/_edit_poll_form.html.erb
new file mode 100644
index 0000000..73db8b9
--- /dev/null
+++ b/app/views/polls/_edit_poll_form.html.erb
@@ -0,0 +1,42 @@
+<%= form_for @poll, method: :patch, class: "border rounded p-4 mt-5" do |f| %>
+ <% if f.object.errors.any? %>
+
+ <%= f.object.errors.full_messages.each do |msg| %>
+
<%= msg %>
+ <% end %>
+
+ <% end %>
+
+
+ <%= f.text_field :title, placeholder: "Title", class: "form-control" %>
+
+
+ <%= f.text_area :description, placeholder: "Description", class: "form-control" %>
+
+
+
+ <%= f.label :start_date, class: "form-label"%>
+ <%= f.date_field :start_date, class: "form-control"%>
+
+
+ <%= f.label :end_date, class: "form-label"%>
+ <%= f.date_field :end_date, class: "form-control"%>
+
+
+
+ <%= f.fields_for :options, method: :patch do |option| %>
+
+ <%= option.text_field :vote_option, placeholder: 'Option', class: "form-control" %>
+ <%= option.check_box :_destroy %>
+
+ <% end %>
+
+
+
+ <%= f.submit "Edit poll", class: "btn btn-primary" %>
+
+
+ <%= f.button "Add option", class: "btn btn-success", style: "width: 100%;" %>
+
+
+ <% end %>
diff --git a/app/views/polls/_ended.html.erb b/app/views/polls/_ended.html.erb
new file mode 100644
index 0000000..655e233
--- /dev/null
+++ b/app/views/polls/_ended.html.erb
@@ -0,0 +1,18 @@
+
+
+
+
+
Title
+ <%= link_to ended.title, ended, class: "nav-link" %>
+
+
+
Author
+
<%= ended.user.username %>
+
+
+
End date
+
<%= ended.start_date %>
+
+
+
+
\ No newline at end of file
diff --git a/app/views/polls/_new_poll_form.html.erb b/app/views/polls/_new_poll_form.html.erb
new file mode 100644
index 0000000..a326d5d
--- /dev/null
+++ b/app/views/polls/_new_poll_form.html.erb
@@ -0,0 +1,42 @@
+ <%= form_for @poll, class: "border rounded p-4 mt-5" do |f| %>
+ <% if f.object.errors.any? %>
+
+ <%= f.object.errors.full_messages.each do |msg| %>
+
<%= msg %>
+ <% end %>
+
+ <% end %>
+
+
+ <%= f.text_field :title, placeholder: "Title", class: "form-control" %>
+
+
+ <%= f.text_area :description, placeholder: "Description", class: "form-control" %>
+
+
+
+ <%= f.label :start_date, class: "form-label"%>
+ <%= f.date_field :start_date, class: "form-control"%>
+
+
+ <%= f.label :end_date, class: "form-label"%>
+ <%= f.date_field :end_date, class: "form-control"%>
+
+
+
+ <%= f.fields_for :options do |option| %>
+
+ <%= option.text_field :vote_option, placeholder: 'Option', class: "form-control" %>
+ <%= option.check_box :_destroy %>
+
+ <% end %>
+
+
+
+ <%= f.submit "Add poll", class: "btn btn-primary" %>
+
+
+ Add option
+
+
+ <% end %>
diff --git a/app/views/polls/_options.html.erb b/app/views/polls/_options.html.erb
new file mode 100644
index 0000000..31872a0
--- /dev/null
+++ b/app/views/polls/_options.html.erb
@@ -0,0 +1,7 @@
+
+
+ <%= form_for options, url: poll_vote_path(@poll.id), method: :patch do |f|%>
+ <%= f.submit options.vote_option, class: 'nav-link border mb-1'%>
+ <% end %>
+
+
\ No newline at end of file
diff --git a/app/views/polls/_upcoming.html.erb b/app/views/polls/_upcoming.html.erb
new file mode 100644
index 0000000..ae32f06
--- /dev/null
+++ b/app/views/polls/_upcoming.html.erb
@@ -0,0 +1,18 @@
+
+
+
+
+
Title
+ <%= link_to upcoming.title, upcoming, class: "nav-link" %>
+
+
+
Author
+
<%= upcoming.user.username %>
+
+
+
Start date
+
<%= upcoming.start_date %>
+
+
+
+
\ No newline at end of file
diff --git a/app/views/polls/edit.html.erb b/app/views/polls/edit.html.erb
new file mode 100644
index 0000000..ff7df57
--- /dev/null
+++ b/app/views/polls/edit.html.erb
@@ -0,0 +1,8 @@
+
+
+
+
Edit your poll.
+ <%= render "edit_poll_form" %>
+
+
+
\ No newline at end of file
diff --git a/app/views/polls/index.html.erb b/app/views/polls/index.html.erb
new file mode 100644
index 0000000..1e602ed
--- /dev/null
+++ b/app/views/polls/index.html.erb
@@ -0,0 +1,20 @@
+
+
+
Your poll
+ <%= link_to '+ New Poll', new_poll_path, class: "btn btn-primary"%>
+
+
+
+ <% if current_user.polls.empty?%>
+
You have no polls yet.
+ <% else %>
+ Uppcoming polls
+ <%= render partial: "upcoming", collection: @upcoming %>
+ Active polls
+ <%= render partial: "active", collection: @active %>
+ Closed pools
+ <%= render partial: "ended", collection: @ended%>
+ <% end %>
+
+
+
\ No newline at end of file
diff --git a/app/views/polls/new.html.erb b/app/views/polls/new.html.erb
new file mode 100644
index 0000000..1237fde
--- /dev/null
+++ b/app/views/polls/new.html.erb
@@ -0,0 +1,8 @@
+
+
+
+
Create your own poll.
+ <%= render "new_poll_form"%>
+
+
+
diff --git a/app/views/polls/show.html.erb b/app/views/polls/show.html.erb
new file mode 100644
index 0000000..83ddca8
--- /dev/null
+++ b/app/views/polls/show.html.erb
@@ -0,0 +1,55 @@
+
>
+
+
+
<%= @poll.title %>
+
+
+ <% if allowed_to?(:invite?, @poll ) %>
+ <%= link_to "Invite user", new_poll_invite_path(@poll.id), class:"btn btn-success pl-1 pr-1 w-50"%>
+ <% end %>
+ <% if allowed_to?(:update?, @poll ) %>
+ <%= link_to "Edit poll", edit_poll_path(@poll.id), class:"btn btn-warning pl-1 pr-1 w-50"%>
+ <% end %>
+ <% if allowed_to?(:destroy?, @poll ) %>
+ <%= link_to "Delete poll", @poll, method: :delete, data: { confirm: "Are you sure?" }, class:"btn btn-danger pl-1 pr-1 w-50"%>
+ <% end %>
+
+
+
+
+
+
+
Details
+
Description
+
<%= @poll.description %>
+
+
+
Start date
+
<%= @poll.start_date %>
+
+
+
End date
+
<%= @poll.end_date %>
+
+
+
Participians count <%= @members%>
+
+
+
+
+
+
+ <%= render partial: 'options', collection: @options%>
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/views/shared/_navigation.html.erb b/app/views/shared/_navigation.html.erb
index 9eb20e4..e990e3e 100644
--- a/app/views/shared/_navigation.html.erb
+++ b/app/views/shared/_navigation.html.erb
@@ -6,6 +6,9 @@
<%= current_user.username %>
+
+ <%= link_to "My Poll", polls_path, class: 'nav-link text-dark' %>
+
<% if allowed_to?(:destroy?, :session)%>
<%= link_to "Logout", logout_path, method: :delete, class: 'nav-link text-dark' %>
diff --git a/config/locales/en.yml b/config/locales/en.yml
index ab1f68a..b1c7f37 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -33,24 +33,26 @@ en:
reset_password:
reset:
subject: "Reset your password"
-
- user_mailer:
+ user_mailer:
welcome:
- subject: "Registration succesfully done"
-
- flash:
- success:
- log_out: "You have successfuly logged out"
+ subject: "Registration succesfully done"
+ flash:
+ success:
+ log_out: 'You have successfuly logged out'
log_in: "Hello %{user_name}"
register: "Account was successfully created"
reset_password: "We send email on your accoun. Follow instructions to reset password"
update: "Your password successfully updated. Please log in."
-
- flash:
- error:
+ poll:
+ create: "Poll created"
+ update: "Poll successfylly updated"
+ deleted: "Your poll deleted"
+ not_found: "Poll not found"
+ error:
log_in: "Wrong email or password"
- policy: "You can't do that"
- token_expire: "Your token has expire. Please try again."
+ policy: "You can't do that"
+ token_expire: "Your token has expire. Please try again."
+
diff --git a/config/routes.rb b/config/routes.rb
index e289b6b..58c05d1 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -3,13 +3,29 @@
Rails.application.routes.draw do
root 'pages#index'
get '/pages', to: 'pages#index'
+
get '/registration', to: 'users#new'
post '/registration', to: 'users#create'
+
get '/login', to: 'sessions#new'
post '/login', to: 'sessions#create'
+
delete '/logout', to: 'sessions#destroy'
+
get '/reset_password', to: 'reset_password#new'
post '/reset_password', to: 'reset_password#create'
get '/reset_password/edit', to: 'reset_password#edit'
patch '/reset_password/update', to: 'reset_password#update'
+
+
+ # get '/poll/invite/:id/find', to: 'polls#invite', as: 'find'
+ # post '/poll/invite/:id', to: 'polls#invite_user', as: 'invite'
+
+ resources :polls do
+ resources :invites, only: [:new, :create]
+ resources :votes, only: [:update]
+ end
+
+
+
end
diff --git a/db/migrate/20210530201549_create_polls_table.rb b/db/migrate/20210530201549_create_polls_table.rb
new file mode 100644
index 0000000..c2ffffe
--- /dev/null
+++ b/db/migrate/20210530201549_create_polls_table.rb
@@ -0,0 +1,13 @@
+class CreatePollsTable < ActiveRecord::Migration[6.1]
+ def change
+ create_table :polls do |t|
+ t.string :title, null: false
+ t.text :description
+ t.date :start_date, null: false
+ t.date :end_date, null: false
+ t.belongs_to :user, foreign_key: true
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20210530202427_create_poll_memberships_table.rb b/db/migrate/20210530202427_create_poll_memberships_table.rb
new file mode 100644
index 0000000..b25c109
--- /dev/null
+++ b/db/migrate/20210530202427_create_poll_memberships_table.rb
@@ -0,0 +1,10 @@
+class CreatePollMembershipsTable < ActiveRecord::Migration[6.1]
+ def change
+ create_table :poll_memberships do |t|
+ t.belongs_to :user, foreign_key: true
+ t.belongs_to :poll, foreign_key: true
+ t.belongs_to :poll_option, foreign_key: true, null: true
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20210605134611_create_poll_options.rb b/db/migrate/20210605134611_create_poll_options.rb
new file mode 100644
index 0000000..ad08580
--- /dev/null
+++ b/db/migrate/20210605134611_create_poll_options.rb
@@ -0,0 +1,9 @@
+class CreatePollOptions < ActiveRecord::Migration[6.1]
+ def change
+ create_table :poll_options do |t|
+ t.text :vote_option, null: false
+ t.belongs_to :poll, foreign_key: true
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20210610203501_add_vote_count_to_poll.rb b/db/migrate/20210610203501_add_vote_count_to_poll.rb
new file mode 100644
index 0000000..d526a95
--- /dev/null
+++ b/db/migrate/20210610203501_add_vote_count_to_poll.rb
@@ -0,0 +1,5 @@
+class AddVoteCountToPoll < ActiveRecord::Migration[6.1]
+ def change
+ add_column :polls, :vote_count, :integer
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index abc7480..ee81370 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -1,5 +1,3 @@
-# frozen_string_literal: true
-
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
@@ -12,13 +10,51 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20_210_518_104_322) do
- create_table 'users', force: :cascade do |t|
- t.string 'username', null: false
- t.string 'email', null: false
- t.string 'password_digest'
- t.datetime 'created_at', precision: 6, null: false
- t.datetime 'updated_at', precision: 6, null: false
- t.index ['email'], name: 'index_users_on_email', unique: true
+ActiveRecord::Schema.define(version: 2021_06_10_203501) do
+
+ create_table "poll_memberships", force: :cascade do |t|
+ t.integer "user_id"
+ t.integer "poll_id"
+ t.integer "poll_option_id"
+ t.datetime "created_at", precision: 6, null: false
+ t.datetime "updated_at", precision: 6, null: false
+ t.index ["poll_id"], name: "index_poll_memberships_on_poll_id"
+ t.index ["poll_option_id"], name: "index_poll_memberships_on_poll_option_id"
+ t.index ["user_id"], name: "index_poll_memberships_on_user_id"
end
+
+ create_table "poll_options", force: :cascade do |t|
+ t.text "vote_option", null: false
+ t.integer "poll_id"
+ t.datetime "created_at", precision: 6, null: false
+ t.datetime "updated_at", precision: 6, null: false
+ t.index ["poll_id"], name: "index_poll_options_on_poll_id"
+ end
+
+ create_table "polls", force: :cascade do |t|
+ t.string "title", null: false
+ t.text "description"
+ t.date "start_date", null: false
+ t.date "end_date", null: false
+ t.integer "user_id"
+ t.datetime "created_at", precision: 6, null: false
+ t.datetime "updated_at", precision: 6, null: false
+ t.integer "vote_count"
+ t.index ["user_id"], name: "index_polls_on_user_id"
+ end
+
+ create_table "users", force: :cascade do |t|
+ t.string "username", null: false
+ t.string "email", null: false
+ t.string "password_digest"
+ t.datetime "created_at", precision: 6, null: false
+ t.datetime "updated_at", precision: 6, null: false
+ t.index ["email"], name: "index_users_on_email", unique: true
+ end
+
+ add_foreign_key "poll_memberships", "poll_options"
+ add_foreign_key "poll_memberships", "polls"
+ add_foreign_key "poll_memberships", "users"
+ add_foreign_key "poll_options", "polls"
+ add_foreign_key "polls", "users"
end
diff --git a/dump.rdb b/dump.rdb
index f8bc7f3..34c7ea5 100644
Binary files a/dump.rdb and b/dump.rdb differ
diff --git a/spec/controllers/polls_controller_spec.rb b/spec/controllers/polls_controller_spec.rb
new file mode 100644
index 0000000..c15dfea
--- /dev/null
+++ b/spec/controllers/polls_controller_spec.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe PollsController, type: :controller do
+ it 'render new poll form' do
+ expect(get: poll_new_path).to route_to(controller: 'polls', action: 'new')
+ end
+
+ it 'created poll' do
+ expect(post: poll_new_path).to route_to(controller: 'polls', action: 'create')
+ end
+end
diff --git a/spec/factories/poll_memberships.rb b/spec/factories/poll_memberships.rb
new file mode 100644
index 0000000..1a46bb1
--- /dev/null
+++ b/spec/factories/poll_memberships.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :membership, class: PollMembership do
+ poll
+ user
+ poll_option { nil }
+ end
+end
\ No newline at end of file
diff --git a/spec/factories/poll_option.rb b/spec/factories/poll_option.rb
new file mode 100644
index 0000000..442435d
--- /dev/null
+++ b/spec/factories/poll_option.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ sequence :vote_option do |n|
+ "option#{n}"
+ end
+end
+
+FactoryBot.define do
+ factory :poll_option, class: PollOption do
+ poll { nil }
+ vote_option
+ end
+end
\ No newline at end of file
diff --git a/spec/factories/polls.rb b/spec/factories/polls.rb
new file mode 100644
index 0000000..10bc9be
--- /dev/null
+++ b/spec/factories/polls.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :poll, class: Poll do
+ title { 'title' }
+ description { 'description' }
+ start_date { Date.current }
+ end_date { Date.tomorrow }
+ user
+
+ end
+end
diff --git a/spec/models/poll_membership_spec.rb b/spec/models/poll_membership_spec.rb
new file mode 100644
index 0000000..82439a7
--- /dev/null
+++ b/spec/models/poll_membership_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe PollMembership, type: :model do
+ describe 'Association' do
+ it { should belong_to(:user) }
+ it { should belong_to(:poll) }
+ it { should belong_to(:poll_option)}
+ end
+end
diff --git a/spec/models/poll_option_spec.rb b/spec/models/poll_option_spec.rb
new file mode 100644
index 0000000..1c44a9d
--- /dev/null
+++ b/spec/models/poll_option_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe PollOption, type: :model do
+ describe 'Associations' do
+ it { should belong_to(:poll) }
+ it { should have_many(:voting).through(:memberships) }
+ it { should have_many(:polls).through(:memberships) }
+ end
+end
\ No newline at end of file
diff --git a/spec/models/poll_spec.rb b/spec/models/poll_spec.rb
new file mode 100644
index 0000000..eccb4f0
--- /dev/null
+++ b/spec/models/poll_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe Poll, type: :model do
+ describe 'Validations' do
+ it { should validate_presence_of(:title) }
+ it { should validate_presence_of(:start_date) }
+ it { should validate_presence_of(:end_date) }
+ end
+ describe 'Associations' do
+ it { should belong_to(:user) }
+ it { should have_many(:members).through(:memberships) }
+ it { should have_many(:memberships) }
+ it { should have_many(:options) }
+ it { should accept_nested_attributes_for(:options) }
+ end
+ describe 'Scopes' do
+ let(:upcoming_poll) { create(:poll, start_date: Date.tomorrow)}
+ it 'includes polls whith start date above current date' do
+ expect(Poll.upcoming).to include(upcoming_poll)
+ end
+ let(:current_poll) { create(:poll, start_date: Date.yesterday, end_date: Date.tomorrow)}
+ it 'includes polls with current date between start date and end date' do
+ expect(Poll.active).to include(current_poll)
+ end
+ let(:ended_poll) { create(:poll, end_date: Date.yesterday)}
+ it 'includes polls whith end date belong current date' do
+ expect(Poll.ended).to include(ended_poll)
+ end
+
+ end
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 36cbcb9..6e94676 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -10,4 +10,9 @@
it { should validate_length_of(:password) }
it { should validate_uniqueness_of(:email) }
end
+ describe 'Associations' do
+ it { should have_many(:polls).through(:memberships) }
+ it { should have_many(:own_polls) }
+ it { should have_many(:memberships) }
+ end
end
diff --git a/spec/policies/poll_policy_spec.rb b/spec/policies/poll_policy_spec.rb
new file mode 100644
index 0000000..441b252
--- /dev/null
+++ b/spec/policies/poll_policy_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe PollPolicy, type: :policy do
+ let(:user) { build_stubbed :user }
+ let(:record) { build_stubbed :poll}
+ let(:memberships) { build_stubbed :membership}
+ let(:context) { { user: user } }
+
+ describe_rule :create? do
+ failed 'when user guest' do
+ let(:user) { nil }
+ end
+
+ succeed 'when user logged in'
+ end
+ describe_rule :index? do
+ failed 'when user guest' do
+ let(:user) {nil}
+ end
+ succeed 'when user login'
+ end
+
+ describe_rule :show? do
+ failed 'when user not a member' do
+ # let(:memberships) { build_stubbed(:memberships, user: nil) }
+ end
+ succeed 'when user member' do
+ let(:membership) {create(:membership, user: user, poll: record)}
+ end
+ end
+
+ describe_rule :update? do
+ failed 'when user not owner' do
+ let(:user) { build_stubbed(:user) }
+ end
+ succeed 'when user owner'
+ end
+
+end
diff --git a/spec/requests/poll_requests_spec.rb b/spec/requests/poll_requests_spec.rb
new file mode 100644
index 0000000..0a7906f
--- /dev/null
+++ b/spec/requests/poll_requests_spec.rb
@@ -0,0 +1,29 @@
+require 'rails_helper'
+
+RSpec.describe 'PollRequest', type: :request do
+ subject(:poll) { build(:poll) }
+
+ it 'create pol with valid parametrs' do
+ post poll_new_path, params: { poll: { title: poll.title, description: poll.description,
+ start_date: poll.start_date, end_date: poll.end_date } }
+ expect(response).to redirect_to(root_path)
+ end
+
+ it 'render poll form when fail registration with title nil' do
+ post poll_new_path, params: { poll: { title: nil, description: poll.description,
+ start_date: poll.start_date, end_date: poll.end_date } }
+ expect(response).to render_template('new')
+ end
+
+ it 'render poll form when fail registration with start date nil' do
+ post poll_new_path, params: { poll: { title: poll.title, description: poll.description,
+ start_date: nil, end_date: poll.end_date } }
+ expect(response).to render_template(:new)
+ end
+
+ it 'render poll form when fail registration with end date nil' do
+ post poll_new_path, params: { poll: { title: poll.title, description: poll.description,
+ start_date: poll.start_date, end_date: nil } }
+ expect(response).to render_template(:new)
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 01f7c97..2c90976 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -13,7 +13,7 @@
# a separate helper file that requires the additional dependencies and performs
# the additional setup, and require it from the spec files that actually need
# it.
-#
+require 'action_policy/rspec/dsl'
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
RSpec.configure do |config|
# rspec-expectations config goes here. You can use an alternate