biovision-poll 0.0.170908

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +28 -0
  4. data/Rakefile +26 -0
  5. data/app/assets/config/biovision_poll_manifest.js +2 -0
  6. data/app/assets/javascripts/biovision/poll/application.js +13 -0
  7. data/app/assets/stylesheets/biovision/poll/polls.scss +0 -0
  8. data/app/controllers/admin/poll_answers_controller.rb +23 -0
  9. data/app/controllers/admin/poll_questions_controller.rb +22 -0
  10. data/app/controllers/admin/polls_controller.rb +27 -0
  11. data/app/controllers/biovision/poll/application_controller.rb +7 -0
  12. data/app/controllers/poll_answers_controller.rb +55 -0
  13. data/app/controllers/poll_questions_controller.rb +55 -0
  14. data/app/controllers/polls_controller.rb +59 -0
  15. data/app/helpers/biovision/poll/application_helper.rb +6 -0
  16. data/app/helpers/polls_helper.rb +37 -0
  17. data/app/jobs/biovision/poll/application_job.rb +6 -0
  18. data/app/mailers/biovision/poll/application_mailer.rb +8 -0
  19. data/app/models/application_record.rb +3 -0
  20. data/app/models/biovision/poll/application_record.rb +7 -0
  21. data/app/models/poll.rb +56 -0
  22. data/app/models/poll_answer.rb +62 -0
  23. data/app/models/poll_question.rb +65 -0
  24. data/app/models/poll_vote.rb +21 -0
  25. data/app/uploaders/poll_image_uploader.rb +46 -0
  26. data/app/views/admin/poll_answers/entity/_in_list.html.erb +20 -0
  27. data/app/views/admin/poll_answers/show.html.erb +58 -0
  28. data/app/views/admin/poll_questions/_toggleable.html.erb +7 -0
  29. data/app/views/admin/poll_questions/entity/_in_list.html.erb +20 -0
  30. data/app/views/admin/poll_questions/show.html.erb +72 -0
  31. data/app/views/admin/poll_votes/entity/_in_list.html.erb +16 -0
  32. data/app/views/admin/polls/_nav_item.html.erb +6 -0
  33. data/app/views/admin/polls/_toggleable.html.erb +7 -0
  34. data/app/views/admin/polls/entity/_in_list.html.erb +20 -0
  35. data/app/views/admin/polls/index.html.erb +16 -0
  36. data/app/views/admin/polls/show.html.erb +61 -0
  37. data/app/views/layouts/biovision/poll/application.html.erb +14 -0
  38. data/app/views/poll_answers/_form.html.erb +42 -0
  39. data/app/views/poll_answers/edit.html.erb +19 -0
  40. data/app/views/poll_answers/new.html.erb +19 -0
  41. data/app/views/poll_questions/_form.html.erb +54 -0
  42. data/app/views/poll_questions/edit.html.erb +18 -0
  43. data/app/views/poll_questions/new.html.erb +18 -0
  44. data/app/views/polls/_form.html.erb +46 -0
  45. data/app/views/polls/edit.html.erb +17 -0
  46. data/app/views/polls/new.html.erb +15 -0
  47. data/config/locales/polls-ru.yml +131 -0
  48. data/config/routes.rb +18 -0
  49. data/db/migrate/20170906000001_create_polls.rb +57 -0
  50. data/db/migrate/20170906000002_create_poll_questions.rb +22 -0
  51. data/db/migrate/20170906000003_create_poll_answers.rb +20 -0
  52. data/db/migrate/20170906000004_create_poll_votes.rb +20 -0
  53. data/lib/biovision/poll.rb +7 -0
  54. data/lib/biovision/poll/engine.rb +14 -0
  55. data/lib/biovision/poll/version.rb +5 -0
  56. data/lib/tasks/biovision/poll_tasks.rake +4 -0
  57. metadata +253 -0
@@ -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>