biovision-poll 0.0.170908
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +28 -0
- data/Rakefile +26 -0
- data/app/assets/config/biovision_poll_manifest.js +2 -0
- data/app/assets/javascripts/biovision/poll/application.js +13 -0
- data/app/assets/stylesheets/biovision/poll/polls.scss +0 -0
- data/app/controllers/admin/poll_answers_controller.rb +23 -0
- data/app/controllers/admin/poll_questions_controller.rb +22 -0
- data/app/controllers/admin/polls_controller.rb +27 -0
- data/app/controllers/biovision/poll/application_controller.rb +7 -0
- data/app/controllers/poll_answers_controller.rb +55 -0
- data/app/controllers/poll_questions_controller.rb +55 -0
- data/app/controllers/polls_controller.rb +59 -0
- data/app/helpers/biovision/poll/application_helper.rb +6 -0
- data/app/helpers/polls_helper.rb +37 -0
- data/app/jobs/biovision/poll/application_job.rb +6 -0
- data/app/mailers/biovision/poll/application_mailer.rb +8 -0
- data/app/models/application_record.rb +3 -0
- data/app/models/biovision/poll/application_record.rb +7 -0
- data/app/models/poll.rb +56 -0
- data/app/models/poll_answer.rb +62 -0
- data/app/models/poll_question.rb +65 -0
- data/app/models/poll_vote.rb +21 -0
- data/app/uploaders/poll_image_uploader.rb +46 -0
- data/app/views/admin/poll_answers/entity/_in_list.html.erb +20 -0
- data/app/views/admin/poll_answers/show.html.erb +58 -0
- data/app/views/admin/poll_questions/_toggleable.html.erb +7 -0
- data/app/views/admin/poll_questions/entity/_in_list.html.erb +20 -0
- data/app/views/admin/poll_questions/show.html.erb +72 -0
- data/app/views/admin/poll_votes/entity/_in_list.html.erb +16 -0
- data/app/views/admin/polls/_nav_item.html.erb +6 -0
- data/app/views/admin/polls/_toggleable.html.erb +7 -0
- data/app/views/admin/polls/entity/_in_list.html.erb +20 -0
- data/app/views/admin/polls/index.html.erb +16 -0
- data/app/views/admin/polls/show.html.erb +61 -0
- data/app/views/layouts/biovision/poll/application.html.erb +14 -0
- data/app/views/poll_answers/_form.html.erb +42 -0
- data/app/views/poll_answers/edit.html.erb +19 -0
- data/app/views/poll_answers/new.html.erb +19 -0
- data/app/views/poll_questions/_form.html.erb +54 -0
- data/app/views/poll_questions/edit.html.erb +18 -0
- data/app/views/poll_questions/new.html.erb +18 -0
- data/app/views/polls/_form.html.erb +46 -0
- data/app/views/polls/edit.html.erb +17 -0
- data/app/views/polls/new.html.erb +15 -0
- data/config/locales/polls-ru.yml +131 -0
- data/config/routes.rb +18 -0
- data/db/migrate/20170906000001_create_polls.rb +57 -0
- data/db/migrate/20170906000002_create_poll_questions.rb +22 -0
- data/db/migrate/20170906000003_create_poll_answers.rb +20 -0
- data/db/migrate/20170906000004_create_poll_votes.rb +20 -0
- data/lib/biovision/poll.rb +7 -0
- data/lib/biovision/poll/engine.rb +14 -0
- data/lib/biovision/poll/version.rb +5 -0
- data/lib/tasks/biovision/poll_tasks.rake +4 -0
- metadata +253 -0
data/app/models/poll.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
class Poll < ApplicationRecord
|
2
|
+
include HasOwner
|
3
|
+
include Toggleable
|
4
|
+
|
5
|
+
PER_PAGE = 10
|
6
|
+
NAME_LIMIT = 140
|
7
|
+
DESCRIPTION_LIMIT = 255
|
8
|
+
|
9
|
+
toggleable %i(active anonymous_votes open_results show_on_homepage visible)
|
10
|
+
|
11
|
+
mount_uploader :image, PollImageUploader
|
12
|
+
|
13
|
+
belongs_to :user
|
14
|
+
belongs_to :agent, optional: true
|
15
|
+
belongs_to :region, optional: true
|
16
|
+
belongs_to :pollable, polymorphic: true
|
17
|
+
has_many :poll_questions, dependent: :delete_all
|
18
|
+
|
19
|
+
validates_presence_of :name, :description
|
20
|
+
validates_length_of :name, maximum: NAME_LIMIT
|
21
|
+
validates_length_of :description, maximum: DESCRIPTION_LIMIT
|
22
|
+
|
23
|
+
scope :recent, -> { order('id desc') }
|
24
|
+
scope :visible, -> { where(visible: true) }
|
25
|
+
scope :active, -> { where(active: true).where('end_date >= now() or end_date is null') }
|
26
|
+
|
27
|
+
# @param [Integer] page
|
28
|
+
def self.page_for_administration(page = 1)
|
29
|
+
recent.page(page).per(PER_PAGE)
|
30
|
+
end
|
31
|
+
|
32
|
+
# @param [Integer] page
|
33
|
+
def self.page_for_visitors(page = 1)
|
34
|
+
visible.recent.page(page).per(PER_PAGE)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.entity_parameters
|
38
|
+
Poll.toggleable_attributes + %i(image name description end_date)
|
39
|
+
end
|
40
|
+
|
41
|
+
# @param [User] user
|
42
|
+
def editable_by?(user)
|
43
|
+
return false if user.nil?
|
44
|
+
privilege = :chief_poll_manager
|
45
|
+
owned_by?(user) || UserPrivilege.user_has_privilege?(user, privilege)
|
46
|
+
end
|
47
|
+
|
48
|
+
# @param [User] user
|
49
|
+
def visible_to?(user)
|
50
|
+
visible? || owned_by?(user)
|
51
|
+
end
|
52
|
+
|
53
|
+
def regional?
|
54
|
+
!region_id.nil?
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
class PollAnswer < ApplicationRecord
|
2
|
+
TEXT_LIMIT = 140
|
3
|
+
|
4
|
+
mount_uploader :image, PollImageUploader
|
5
|
+
|
6
|
+
belongs_to :poll_question, counter_cache: true
|
7
|
+
has_many :poll_votes, dependent: :delete_all
|
8
|
+
|
9
|
+
after_initialize :set_next_priority
|
10
|
+
|
11
|
+
before_validation { self.text = text.to_s.strip }
|
12
|
+
before_validation :normalize_priority
|
13
|
+
|
14
|
+
validates_presence_of :text
|
15
|
+
validates_length_of :text, maximum: TEXT_LIMIT
|
16
|
+
validates_uniqueness_of :text, scope: [:poll_question_id]
|
17
|
+
|
18
|
+
scope :ordered_by_priority, -> { order('priority asc, text asc') }
|
19
|
+
scope :siblings, ->(item) { where(poll_question_id: item.poll_question_id) }
|
20
|
+
|
21
|
+
def self.entity_parameters
|
22
|
+
%i(image text)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.creation_parameters
|
26
|
+
entity_parameters + %i(poll_question_id)
|
27
|
+
end
|
28
|
+
|
29
|
+
# @param [User] user
|
30
|
+
def editable_by?(user)
|
31
|
+
poll_question.poll.editable_by?(user)
|
32
|
+
end
|
33
|
+
|
34
|
+
def poll
|
35
|
+
poll_question&.poll
|
36
|
+
end
|
37
|
+
|
38
|
+
# @param [Integer] delta
|
39
|
+
def change_priority(delta)
|
40
|
+
new_priority = priority + delta
|
41
|
+
adjacent = self.class.siblings(self).find_by(priority: new_priority)
|
42
|
+
if adjacent.is_a?(self.class) && (adjacent.id != id)
|
43
|
+
adjacent.update!(priority: priority)
|
44
|
+
end
|
45
|
+
update(priority: new_priority)
|
46
|
+
|
47
|
+
self.class.siblings(self).map { |e| [e.id, e.priority] }.to_h
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def set_next_priority
|
53
|
+
if id.nil? && priority == 1
|
54
|
+
self.priority = self.class.siblings(self).maximum(:priority).to_i + 1
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def normalize_priority
|
59
|
+
self.priority = PRIORITY_RANGE.first if priority < PRIORITY_RANGE.first
|
60
|
+
self.priority = PRIORITY_RANGE.last if priority > PRIORITY_RANGE.last
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
class PollQuestion < ApplicationRecord
|
2
|
+
include Toggleable
|
3
|
+
|
4
|
+
TEXT_LIMIT = 140
|
5
|
+
COMMENT_LIMIT = 140
|
6
|
+
PRIORITY_RANGE = (1..100)
|
7
|
+
|
8
|
+
toggleable :multiple_choice
|
9
|
+
|
10
|
+
mount_uploader :image, PollImageUploader
|
11
|
+
|
12
|
+
belongs_to :poll, counter_cache: true
|
13
|
+
has_many :poll_answers, dependent: :delete_all
|
14
|
+
|
15
|
+
after_initialize :set_next_priority
|
16
|
+
|
17
|
+
before_validation { self.text = text.to_s.strip }
|
18
|
+
before_validation :normalize_priority
|
19
|
+
|
20
|
+
validates_presence_of :text
|
21
|
+
validates_length_of :text, maximum: TEXT_LIMIT
|
22
|
+
validates_length_of :comment, maximum: COMMENT_LIMIT
|
23
|
+
validates_uniqueness_of :text, scope: [:poll_id]
|
24
|
+
|
25
|
+
scope :ordered_by_priority, -> { order('priority asc, text asc') }
|
26
|
+
scope :siblings, ->(item) { where(poll_id: item.poll_id) }
|
27
|
+
|
28
|
+
def self.entity_parameters
|
29
|
+
PollQuestion.toggleable_attributes + %i(image text comment)
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.creation_parameters
|
33
|
+
entity_parameters + %i(poll_id)
|
34
|
+
end
|
35
|
+
|
36
|
+
# @param [User] user
|
37
|
+
def editable_by?(user)
|
38
|
+
poll.editable_by?(user)
|
39
|
+
end
|
40
|
+
|
41
|
+
# @param [Integer] delta
|
42
|
+
def change_priority(delta)
|
43
|
+
new_priority = priority + delta
|
44
|
+
adjacent = self.class.siblings(self).find_by(priority: new_priority)
|
45
|
+
if adjacent.is_a?(self.class) && (adjacent.id != id)
|
46
|
+
adjacent.update!(priority: priority)
|
47
|
+
end
|
48
|
+
update(priority: new_priority)
|
49
|
+
|
50
|
+
self.class.siblings(self).map { |e| [e.id, e.priority] }.to_h
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def set_next_priority
|
56
|
+
if id.nil? && priority == 1
|
57
|
+
self.priority = self.class.siblings(self).maximum(:priority).to_i + 1
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def normalize_priority
|
62
|
+
self.priority = PRIORITY_RANGE.first if priority < PRIORITY_RANGE.first
|
63
|
+
self.priority = PRIORITY_RANGE.last if priority > PRIORITY_RANGE.last
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class PollVote < ApplicationRecord
|
2
|
+
PER_PAGE = 20
|
3
|
+
|
4
|
+
belongs_to :poll_answer, counter_cache: true
|
5
|
+
belongs_to :user
|
6
|
+
belongs_to :agent, optional: true
|
7
|
+
|
8
|
+
before_save { self.footprint = "#{user_id}:#{ip}:#{agent_id}" }
|
9
|
+
|
10
|
+
scope :recent, -> { order('id desc') }
|
11
|
+
|
12
|
+
# @param [Integer] page
|
13
|
+
def self.page_for_administration(page = 1)
|
14
|
+
recent.page(page).per(PER_PAGE)
|
15
|
+
end
|
16
|
+
|
17
|
+
# @param [Integer] page
|
18
|
+
def self.page_for_visitors(page = 1)
|
19
|
+
recent.page(page).per(PER_PAGE)
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class PollImageUploader < CarrierWave::Uploader::Base
|
2
|
+
include CarrierWave::MiniMagick
|
3
|
+
include CarrierWave::BombShelter
|
4
|
+
|
5
|
+
storage :file
|
6
|
+
|
7
|
+
def max_pixel_dimensions
|
8
|
+
[3840, 3840]
|
9
|
+
end
|
10
|
+
|
11
|
+
def store_dir
|
12
|
+
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id/10000.floor}/#{model.id/100.floor}/#{model.id}"
|
13
|
+
end
|
14
|
+
|
15
|
+
process :auto_orient
|
16
|
+
|
17
|
+
def auto_orient
|
18
|
+
manipulate! do |image|
|
19
|
+
image.tap(&:auto_orient)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
version :medium_2x do
|
24
|
+
resize_to_fit 1280, 1280
|
25
|
+
end
|
26
|
+
|
27
|
+
version :medium, from_version: :medium_2x do
|
28
|
+
resize_to_fit 640, 640
|
29
|
+
end
|
30
|
+
|
31
|
+
version :small, from_version: :medium do
|
32
|
+
resize_to_fit 320, 320
|
33
|
+
end
|
34
|
+
|
35
|
+
version :preview_2x, from_version: :small do
|
36
|
+
resize_to_fit 160, 160
|
37
|
+
end
|
38
|
+
|
39
|
+
version :preview, from_version: :preview_2x do
|
40
|
+
resize_to_fit 80, 80
|
41
|
+
end
|
42
|
+
|
43
|
+
def extension_white_list
|
44
|
+
%w(jpg jpeg png)
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<div class="image">
|
2
|
+
<%= poll_image_preview(entity) %>
|
3
|
+
</div>
|
4
|
+
<div class="data">
|
5
|
+
<div><%= admin_poll_question_link(entity) %></div>
|
6
|
+
<% unless entity.comment.blank? %>
|
7
|
+
<div class="info">
|
8
|
+
<%= entity.comment %>
|
9
|
+
</div>
|
10
|
+
<% end %>
|
11
|
+
<% if entity.poll_answers.any? %>
|
12
|
+
<div><%= entity.poll_answers.count %></div>
|
13
|
+
<% end %>
|
14
|
+
|
15
|
+
<%= redner partial: 'admin/polls/toggleable', locals: { entity: entity } %>
|
16
|
+
|
17
|
+
<ul class="actions">
|
18
|
+
<li><%= edit_icon(edit_poll_path(entity)) %></li>
|
19
|
+
</ul>
|
20
|
+
</div>
|
@@ -0,0 +1,58 @@
|
|
1
|
+
<% content_for :meta_title, t('.title', poll: @entity.poll.name, answer: @entity.poll_question.text, question: @entity.text) %>
|
2
|
+
<% content_for :breadcrumbs do %>
|
3
|
+
<%= link_to(t('admin.polls.nav_item.text'), admin_polls_path) %>
|
4
|
+
<%= admin_poll_link(@entity.poll) %>
|
5
|
+
<%= admin_poll_question_link(@entity.poll_question) %>
|
6
|
+
<span><%= @entity.priority %>: <%= @entity.text %></span>
|
7
|
+
<% end %>
|
8
|
+
|
9
|
+
<article>
|
10
|
+
<h1><%= @entity.text %></h1>
|
11
|
+
|
12
|
+
<ul class="actions">
|
13
|
+
<li><%= return_icon(admin_poll_question_path(@entity.poll_question_id)) %></li>
|
14
|
+
<li><%= edit_icon(edit_poll_answer_path(@entity.id)) %></li>
|
15
|
+
</ul>
|
16
|
+
|
17
|
+
<% unless @entity.image.blank? %>
|
18
|
+
<figure>
|
19
|
+
<%= poll_image_medium(@entity) %>
|
20
|
+
<figcaption><%= t('activerecord.attributes.poll_answer.image') %></figcaption>
|
21
|
+
</figure>
|
22
|
+
<% end %>
|
23
|
+
|
24
|
+
<dl>
|
25
|
+
<dt><%= t(:created_at) %></dt>
|
26
|
+
<dd>
|
27
|
+
<%= time_tag(@entity.created_at) %>
|
28
|
+
</dd>
|
29
|
+
|
30
|
+
<dt><%= t(:updated_at) %></dt>
|
31
|
+
<dd>
|
32
|
+
<%= time_tag(@entity.updated_at) %>
|
33
|
+
</dd>
|
34
|
+
|
35
|
+
<dt><%= t('activerecord.attributes.poll_answer.priority') %></dt>
|
36
|
+
<dd>
|
37
|
+
<%= @entity.priority %>
|
38
|
+
</dd>
|
39
|
+
|
40
|
+
<dt><%= t('activerecord.attributes.poll_answer.poll_question_id') %></dt>
|
41
|
+
<dd>
|
42
|
+
<%= admin_poll_question_link(@entity.poll_question) %>
|
43
|
+
</dd>
|
44
|
+
|
45
|
+
<dt><%= t('activerecord.attributes.poll_answer.text') %></dt>
|
46
|
+
<dd>
|
47
|
+
<%= @entity.text %>
|
48
|
+
</dd>
|
49
|
+
</dl>
|
50
|
+
|
51
|
+
<section>
|
52
|
+
<h2><%= t(:poll_vote_count, count: @entity.poll_votes_count) %></h2>
|
53
|
+
|
54
|
+
<%= paginate @collection %>
|
55
|
+
<%= render partial: 'shared/admin/list', locals: { collection: @collection } %>
|
56
|
+
<%= paginate @collection %>
|
57
|
+
</section>
|
58
|
+
</article>
|
@@ -0,0 +1,7 @@
|
|
1
|
+
<div class="toggleable" data-url="<%= toggle_admin_poll_question_path(entity) %>">
|
2
|
+
<% PollQuestion.toggleable_attributes.each do |flag| %>
|
3
|
+
<span class="<%= entity.attributes[flag.to_s] ? 'active' : 'inactive' %>" data-flag="<%= flag %>">
|
4
|
+
<%= t("activerecord.attributes.poll_question.#{flag}") %>
|
5
|
+
</span>
|
6
|
+
<% end %>
|
7
|
+
</div>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<div class="image">
|
2
|
+
<%= poll_image_preview(entity) %>
|
3
|
+
</div>
|
4
|
+
<div class="data">
|
5
|
+
<div><%= admin_poll_question_link(entity) %></div>
|
6
|
+
<% unless entity.comment.blank? %>
|
7
|
+
<div class="info">
|
8
|
+
<%= entity.comment %>
|
9
|
+
</div>
|
10
|
+
<% end %>
|
11
|
+
<% if entity.poll_answers.any? %>
|
12
|
+
<div><%= entity.poll_answers.count %></div>
|
13
|
+
<% end %>
|
14
|
+
|
15
|
+
<%= redner partial: 'admin/polls/toggleable', locals: { entity: entity } %>
|
16
|
+
|
17
|
+
<ul class="actions">
|
18
|
+
<li><%= edit_icon(edit_poll_path(entity)) %></li>
|
19
|
+
</ul>
|
20
|
+
</div>
|
@@ -0,0 +1,72 @@
|
|
1
|
+
<% content_for :meta_title, t('.title', poll: @entity.poll.name, question: @entity.text) %>
|
2
|
+
<% content_for :breadcrumbs do %>
|
3
|
+
<%= link_to(t('admin.polls.nav_item.text'), admin_polls_path) %>
|
4
|
+
<%= admin_poll_link(@entity.poll) %>
|
5
|
+
<span><%= @entity.priority %>: <%= @entity.text %></span>
|
6
|
+
<% end %>
|
7
|
+
|
8
|
+
<article>
|
9
|
+
<h1><%= @entity.text %></h1>
|
10
|
+
|
11
|
+
<ul class="actions">
|
12
|
+
<li><%= return_icon(admin_poll_path(@entity.poll_id)) %></li>
|
13
|
+
<li><%= edit_icon(edit_poll_question_path(@entity.id)) %></li>
|
14
|
+
</ul>
|
15
|
+
|
16
|
+
<% unless @entity.image.blank? %>
|
17
|
+
<figure>
|
18
|
+
<%= poll_image_medium(@entity) %>
|
19
|
+
<figcaption><%= t('activerecord.attributes.poll_question.image') %></figcaption>
|
20
|
+
</figure>
|
21
|
+
<% end %>
|
22
|
+
|
23
|
+
<dl>
|
24
|
+
<dt><%= t(:created_at) %></dt>
|
25
|
+
<dd>
|
26
|
+
<%= time_tag(@entity.created_at) %>
|
27
|
+
</dd>
|
28
|
+
|
29
|
+
<dt><%= t(:updated_at) %></dt>
|
30
|
+
<dd>
|
31
|
+
<%= time_tag(@entity.updated_at) %>
|
32
|
+
</dd>
|
33
|
+
|
34
|
+
<dt><%= t('activerecord.attributes.poll_question.poll_id') %></dt>
|
35
|
+
<dd>
|
36
|
+
<%= admin_poll_link(@entity.poll) %>
|
37
|
+
</dd>
|
38
|
+
|
39
|
+
<dt><%= t('activerecord.attributes.poll_question.priority') %></dt>
|
40
|
+
<dd>
|
41
|
+
<%= @entity.priority %>
|
42
|
+
</dd>
|
43
|
+
|
44
|
+
<dt><%= t('activerecord.attributes.poll_question.text') %></dt>
|
45
|
+
<dd>
|
46
|
+
<%= @entity.text %>
|
47
|
+
</dd>
|
48
|
+
|
49
|
+
<% unless @entity.comment.blank? %>
|
50
|
+
<dt><%= t('activerecord.attributes.poll_question.comment') %></dt>
|
51
|
+
<dd>
|
52
|
+
<%= @entity.comment %>
|
53
|
+
</dd>
|
54
|
+
<% end %>
|
55
|
+
</dl>
|
56
|
+
|
57
|
+
<%= redner partial: 'admin/poll_questions/toggleable', locals: { entity: @entity } %>
|
58
|
+
|
59
|
+
<section>
|
60
|
+
<h2><%= t(:poll_answer_count, count: @entity.poll_answers_count) %></h2>
|
61
|
+
|
62
|
+
<%= render partial: 'shared/admin/list_with_priority', locals: { collection: @entity.poll_answers.ordered_by_priority } %>
|
63
|
+
</section>
|
64
|
+
|
65
|
+
<% if @entity.poll_answers.count < 10 %>
|
66
|
+
<section>
|
67
|
+
<h2><%= t('poll_answers.new.heading') %></h2>
|
68
|
+
|
69
|
+
<%= render partial: 'poll_answers/form', locals: { entity: @entity.poll_answers.new } %>
|
70
|
+
</section>
|
71
|
+
<% end %>
|
72
|
+
</article>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<div class="data">
|
2
|
+
<div><%= admin_user_link(entity.user) %></div>
|
3
|
+
<div class="info">
|
4
|
+
<%= entity.ip %>,
|
5
|
+
<%= time_tag(entity.created_at) %>
|
6
|
+
</div>
|
7
|
+
<div class="secondary info">
|
8
|
+
<%= t('activerecord.attributes.poll_vote.footprint') %>:
|
9
|
+
<%= entity.footprint %>
|
10
|
+
</div>
|
11
|
+
<% unless entity.agent.nil? %>
|
12
|
+
<div class="secondary info">
|
13
|
+
<%= entity.agent.name %>
|
14
|
+
</div>
|
15
|
+
<% end %>
|
16
|
+
</div>
|