biovision-poll 0.0.170908

Sign up to get free protection for your applications and to get access to all the features.
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>