biovision-poll 0.1.180917.0 → 0.2.200324.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/admin/poll_answers_controller.rb +6 -5
  3. data/app/controllers/admin/poll_questions_controller.rb +6 -5
  4. data/app/controllers/admin/polls_controller.rb +7 -6
  5. data/app/controllers/poll_answers_controller.rb +15 -9
  6. data/app/controllers/poll_questions_controller.rb +15 -9
  7. data/app/controllers/polls_controller.rb +25 -16
  8. data/app/helpers/polls_helper.rb +19 -29
  9. data/app/models/poll.rb +50 -18
  10. data/app/models/poll_answer.rb +29 -36
  11. data/app/models/poll_question.rb +33 -39
  12. data/app/models/poll_user.rb +10 -1
  13. data/app/models/poll_vote.rb +20 -5
  14. data/app/services/biovision/components/polls_component.rb +21 -0
  15. data/app/views/admin/components/links/_polls.html.erb +1 -0
  16. data/app/views/admin/poll_answers/entity/_data.html.erb +0 -0
  17. data/app/views/admin/poll_answers/entity/_in_list.html.erb +0 -3
  18. data/app/views/admin/poll_answers/show.html.erb +25 -35
  19. data/app/views/admin/poll_questions/entity/_data.html.erb +0 -0
  20. data/app/views/admin/poll_questions/entity/_in_list.html.erb +6 -5
  21. data/app/views/admin/poll_questions/show.html.erb +29 -37
  22. data/app/views/admin/poll_votes/entity/_data.html.erb +0 -0
  23. data/app/views/admin/poll_votes/entity/_in_list.html.erb +6 -5
  24. data/app/views/admin/polls/entity/_data.html.erb +0 -0
  25. data/app/views/admin/polls/entity/_in_list.html.erb +1 -3
  26. data/app/views/admin/polls/index.html.erb +8 -2
  27. data/app/views/admin/polls/show.html.erb +32 -30
  28. data/app/views/admin/polls/users.html.erb +2 -1
  29. data/app/views/poll_answers/_form.html.erb +36 -30
  30. data/app/views/poll_answers/_poll_answer.html.erb +22 -2
  31. data/app/views/poll_answers/edit.html.erb +6 -5
  32. data/app/views/poll_answers/new.html.erb +8 -7
  33. data/app/views/poll_questions/_form.html.erb +53 -43
  34. data/app/views/poll_questions/edit.html.erb +6 -5
  35. data/app/views/poll_questions/new.html.erb +7 -6
  36. data/app/views/polls/_form.html.erb +64 -41
  37. data/app/views/polls/_poll.html.erb +11 -6
  38. data/app/views/polls/edit.html.erb +5 -4
  39. data/app/views/polls/new.html.erb +3 -2
  40. data/app/views/polls/results.html.erb +22 -0
  41. data/config/locales/polls-ru.yml +28 -26
  42. data/config/routes.rb +19 -15
  43. data/db/migrate/20200323000001_create_biovision_polls.rb +121 -0
  44. data/lib/biovision/poll/version.rb +3 -1
  45. metadata +10 -12
  46. data/app/uploaders/poll_image_uploader.rb +0 -46
  47. data/app/views/admin/index/dashboard/_biovision_poll.html.erb +0 -10
  48. data/db/migrate/20170906000001_create_polls.rb +0 -70
  49. data/db/migrate/20170906000002_create_poll_questions.rb +0 -22
  50. data/db/migrate/20170906000003_create_poll_answers.rb +0 -20
  51. data/db/migrate/20170906000004_create_poll_votes.rb +0 -20
  52. data/db/migrate/20180412000010_create_poll_users.rb +0 -21
  53. data/db/migrate/20180903111111_rename_vote_footprint.rb +0 -11
@@ -1,12 +1,13 @@
1
1
  <% content_for :meta_title, t('.title') %>
2
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) %>
6
- <span><%= t(:edit) %></span>
3
+ <%= admin_biovision_component_link(component_handler.component) %>
4
+ <%= link_to(t('admin.polls.index.nav_text'), admin_polls_path) %>
5
+ <%= admin_poll_link(@entity.poll) %>
6
+ <%= admin_poll_question_link(@entity) %>
7
+ <span><%= t('.nav_text') %></span>
7
8
  <% end %>
8
9
 
9
- <article class="entity-page">
10
+ <article>
10
11
  <h1><%= t('.heading') %></h1>
11
12
 
12
13
  <ul class="actions">
@@ -1,17 +1,18 @@
1
1
  <% content_for :meta_title, t('.title') %>
2
2
  <% content_for :breadcrumbs do %>
3
- <%= link_to(t('admin.polls.nav_item.text'), admin_polls_path) %>
4
- <%= admin_poll_link(@entity.poll) unless @entity.poll.nil? %>
5
- <span><%= t('.heading') %></span>
3
+ <%= admin_biovision_component_link(component_handler.component) %>
4
+ <%= link_to(t('admin.polls.index.nav_text'), admin_polls_path) %>
5
+ <%= admin_poll_link(@entity.poll) unless @entity.poll.nil? %>
6
+ <span><%= t('.nav_text') %></span>
6
7
  <% end %>
7
8
 
8
9
  <article>
9
10
  <h1><%= t('.title') %></h1>
10
11
 
11
12
  <% unless @entity.poll_id.nil? %>
12
- <ul class="actions">
13
- <li><%= return_icon(admin_poll_path(id: @entity.poll_id)) %></li>
14
- </ul>
13
+ <ul class="actions">
14
+ <li><%= return_icon(admin_poll_path(id: @entity.poll_id)) %></li>
15
+ </ul>
15
16
  <% end %>
16
17
 
17
18
  <%= render partial: 'form', locals: { entity: @entity } %>
@@ -1,49 +1,71 @@
1
1
  <% model_name = entity.class.to_s.underscore %>
2
- <%= form_with model: entity, html: { id: "#{model_name}-form" } do |f| %>
2
+ <%=
3
+ form_with(
4
+ model: entity,
5
+ html: {
6
+ id: "#{model_name}-form",
7
+ data: { check_url: check_polls_path }
8
+ }
9
+ ) do |f|
10
+ %>
3
11
  <%= render partial: 'shared/list_of_errors', locals: { entity: entity } %>
4
12
 
5
- <dl>
6
- <dt><%= f.label :name %></dt>
7
- <dd>
8
- <%=
9
- f.text_field(
10
- :name,
11
- id: "#{model_name}_name",
12
- required: true,
13
- size: nil,
14
- maxlength: Poll::NAME_LIMIT,
15
- placeholder: t('.placeholders.name')
16
- )
17
- %>
18
- <div class="guideline"><%= t('.guidelines.name') %></div>
19
- </dd>
13
+ <dl class="fields">
14
+ <div>
15
+ <dt><%= f.label :name %></dt>
16
+ <dd>
17
+ <%=
18
+ f.text_field(
19
+ :name,
20
+ class: 'input-text',
21
+ data: {
22
+ check: :name
23
+ },
24
+ maxlength: Poll::NAME_LIMIT,
25
+ placeholder: t('.placeholders.name'),
26
+ required: true,
27
+ size: nil
28
+ )
29
+ %>
30
+ <div class="check-result-error" data-field="name"></div>
31
+ <div class="guideline"><%= t('.guidelines.name') %></div>
32
+ </dd>
33
+ </div>
20
34
 
21
- <!--
22
- <dt><%= f.label :description %></dt>
23
- <dd>
24
- <%=
25
- f.text_field(
26
- :description,
27
- id: "#{model_name}_description",
28
- size: nil,
29
- maxlength: Poll::DESCRIPTION_LIMIT,
30
- placeholder: t('.placeholders.description')
31
- )
32
- %>
33
- <div class="guideline"><%= t('.guidelines.description') %></div>
34
- </dd>
35
+ <!--
36
+ <div>
37
+ <dt><%= f.label :description %></dt>
38
+ <dd>
39
+ <%=
40
+ f.text_field(
41
+ :description,
42
+ class: 'input-text',
43
+ data: {
44
+ check: :description
45
+ },
46
+ maxlength: Poll::DESCRIPTION_LIMIT,
47
+ placeholder: t('.placeholders.description'),
48
+ size: nil,
49
+ )
50
+ %>
51
+ <div class="check-result-error" data-field="description"></div>
52
+ <div class="guideline"><%= t('.guidelines.description') %></div>
53
+ </dd>
54
+ </div>
35
55
  -->
36
56
 
37
- <dt><%= f.label :end_date %></dt>
38
- <dd>
39
- <%=
40
- f.date_field(
41
- :end_date,
42
- id: "#{model_name}_end_date",
43
- size: 10
44
- )
45
- %>
46
- </dd>
57
+ <div>
58
+ <dt><%= f.label :end_date %></dt>
59
+ <dd>
60
+ <%=
61
+ f.date_field(
62
+ :end_date,
63
+ class: 'input-text',
64
+ size: 10
65
+ )
66
+ %>
67
+ </dd>
68
+ </div>
47
69
 
48
70
  <%= render(partial: 'shared/forms/entity_flags', locals: { f: f }) %>
49
71
  </dl>
@@ -51,6 +73,7 @@
51
73
  <%= render 'shared/forms/state_container' %>
52
74
 
53
75
  <div class="buttons">
54
- <%= f.button t(:save), type: :submit, class: 'button-save' %>
76
+ <%= hidden_field_tag :entity_id, entity.id %>
77
+ <%= f.button t(:save), class: 'button-save' %>
55
78
  </div>
56
79
  <% end %>
@@ -2,21 +2,26 @@
2
2
  <h1><%= poll.name %></h1>
3
3
 
4
4
  <% if poll.editable_by?(current_user) %>
5
- <ul class="actions">
6
- <li><%= gear_icon(admin_poll_path(id: poll.id)) %></li>
7
- </ul>
5
+ <ul class="actions">
6
+ <li><%= gear_icon(admin_poll_path(id: poll.id)) %></li>
7
+ </ul>
8
8
  <% end %>
9
9
 
10
10
  <div class="description"><%= poll.description %></div>
11
11
 
12
- <%= form_tag results_poll_path(id: poll.id), method: :post do %>
12
+ <% if @entity.voted?(current_user) %>
13
+ <div class="message-box-notice"><%= t('.you_have_voted') %></div>
14
+ <nav><%= link_to(t('.see_results'), results_poll_path(id: poll.id), class: 'button-nav') %></nav>
15
+ <% else %>
16
+ <%= form_tag results_poll_path(id: poll.id), method: :post do %>
13
17
  <%= render poll.poll_questions.ordered_by_priority %>
14
18
 
15
19
  <div class="buttons">
16
- <%= button_tag t('.answer'), type: :submit %>
20
+ <%= button_tag t('.answer'), type: :submit, class: 'button-primary' %>
17
21
  <% if poll.open_results? %>
18
- <%= link_to('Результаты', results_poll_path(id: poll.id), class: 'nav-button') %>
22
+ <%= link_to('Результаты', results_poll_path(id: poll.id), class: 'button-nav') %>
19
23
  <% end %>
20
24
  </div>
25
+ <% end %>
21
26
  <% end %>
22
27
  </article>
@@ -1,11 +1,12 @@
1
1
  <% content_for :meta_title, t('.title') %>
2
2
  <% content_for :breadcrumbs do %>
3
- <%= link_to(t('admin.polls.nav_item.text'), admin_polls_path) %>
4
- <%= admin_poll_link(@entity) %>
5
- <span><%= t(:edit) %></span>
3
+ <%= admin_biovision_component_link(component_handler.component) %>
4
+ <%= link_to(t('admin.polls.index.nav_text'), admin_polls_path) %>
5
+ <%= admin_poll_link(@entity) %>
6
+ <span><%= t('.nav_text') %></span>
6
7
  <% end %>
7
8
 
8
- <article class="entity-page">
9
+ <article>
9
10
  <h1><%= t('.heading') %></h1>
10
11
 
11
12
  <ul class="actions">
@@ -1,7 +1,8 @@
1
1
  <% content_for :meta_title, t('.title') %>
2
2
  <% content_for :breadcrumbs do %>
3
- <%= link_to(t('admin.polls.nav_item.text'), admin_polls_path) %>
4
- <span><%= t(:create) %></span>
3
+ <%= admin_biovision_component_link(component_handler.component) %>
4
+ <%= link_to(t('admin.polls.index.nav_text'), admin_polls_path) %>
5
+ <span><%= t('.nav_text') %></span>
5
6
  <% end %>
6
7
 
7
8
  <article>
@@ -3,4 +3,26 @@
3
3
  <article>
4
4
  <h1><%= @entity.name %></h1>
5
5
  <h2><%= t('.heading') %></h2>
6
+
7
+ <% if component_handler.allow? %>
8
+ <ul class="actions">
9
+ <li><%= gear_icon(admin_poll_path(id: @entity.id)) %></li>
10
+ </ul>
11
+ <% end %>
12
+
13
+ <div class="biovision-poll-results">
14
+ <% @entity.poll_questions.list_for_visitors.each do |question| %>
15
+ <table>
16
+ <caption><%= question.text %></caption>
17
+ <tbody>
18
+ <% question.poll_answers.each do |answer| %>
19
+ <tr>
20
+ <th><%= answer.text %></th>
21
+ <td><%= answer.vote_percent %>%</td>
22
+ </tr>
23
+ <% end %>
24
+ </tbody>
25
+ </table>
26
+ <% end %>
27
+ </div>
6
28
  </article>
@@ -1,16 +1,16 @@
1
1
  ru:
2
- poll_question_count:
3
- zero: "вопросов нет"
4
- one: "%{count} вопрос"
5
- few: "%{count} вопроса"
6
- many: "%{count} вопросов"
7
- other: "%{count} вопросов"
8
2
  poll_answer_count:
9
3
  zero: "ответов нет"
10
4
  one: "%{count} ответ"
11
5
  few: "%{count} ответа"
12
6
  many: "%{count} ответов"
13
7
  other: "%{count} ответов"
8
+ poll_question_count:
9
+ zero: "вопросов нет"
10
+ one: "%{count} вопрос"
11
+ few: "%{count} вопроса"
12
+ many: "%{count} вопросов"
13
+ other: "%{count} вопросов"
14
14
  poll_vote_count:
15
15
  zero: "голосов нет"
16
16
  one: "%{count} голос"
@@ -27,51 +27,45 @@ ru:
27
27
  attributes:
28
28
  poll:
29
29
  active: "Можно голосовать"
30
+ allow_comments: "Разрешить комментарии"
30
31
  anonymous_votes: "Разрешить анонимные голоса"
31
32
  description: "Описание"
32
33
  end_date: "Дата окончания"
33
34
  exclusive: "Эксклюзивный"
34
- image: "Картинка"
35
35
  name: "Название"
36
36
  open_results: "Результаты видны всем"
37
37
  pollable_id: "Связанный объект"
38
38
  pollable_type: "Тип связанного объекта"
39
- region: "Регион"
40
- region_id: "Регион"
41
39
  show_on_homepage: "Показывать на главной"
42
- user: "Пользователь"
43
- user_id: "Пользователь"
44
- visible: "Показывать"
45
40
  poll_answer:
46
- image: "Картинка"
47
41
  poll_question: "Вопрос"
48
42
  poll_question_id: "Вопрос"
49
- priority: "Порядок сортировки"
50
43
  text: "Текст"
51
44
  poll_question:
52
45
  comment: "Комментарий"
53
- image: "Картинка"
54
46
  multiple_choice: "Множественный выбор"
55
- poll: "Опрос"
56
- poll_id: "Опрос"
57
- priority: "Порядок сортировки"
58
47
  text: "Текст"
59
- poll_user:
60
- poll: "Опрос"
61
- user: "Пользователь"
62
- user_id: "Пользователь"
63
48
  poll_vote:
64
- slug: "Идентификатор"
65
49
  poll_answer: "Ответ"
66
- user: "Пользователь"
50
+ attributes:
51
+ poll: "Опрос"
52
+ poll_id: "Опрос"
53
+ biovision:
54
+ components:
55
+ polls:
56
+ name: "Опросы"
57
+ settings:
58
+ answer_limit: "Лимит ответов"
59
+ question_limit: "Лимит вопросов"
67
60
  admin:
68
61
  polls:
69
62
  nav_item:
70
63
  description: "Управление опросами"
71
- text: "Опросы"
64
+ text: "Список опросов"
72
65
  index:
73
- heading: "Опросы"
66
+ heading: "Список опросов"
74
67
  title: "Опросы, страница %{page}"
68
+ nav_text: "Список опросов"
75
69
  show:
76
70
  title: "Опрос «%{name}»"
77
71
  allowed_users: "Допущенные пользователи"
@@ -96,9 +90,11 @@ ru:
96
90
  new:
97
91
  heading: "Новый опрос"
98
92
  title: "Новый опрос"
93
+ nav_text: "Создать опрос"
99
94
  edit:
100
95
  heading: "Редактирование опроса"
101
96
  title: "Редактирование опроса"
97
+ nav_text: "Редактировать"
102
98
  update:
103
99
  success: "Опрос успешно изменён"
104
100
  destroy:
@@ -124,6 +120,8 @@ ru:
124
120
  title: "Опрос «%{name}»"
125
121
  poll:
126
122
  answer: "Ответить"
123
+ you_have_voted: "Вы уже проголосовали"
124
+ see_results: "Посмотреть результаты"
127
125
  results:
128
126
  title: "Результаты опроса «%{name}»"
129
127
  heading: "Результаты опроса"
@@ -131,9 +129,11 @@ ru:
131
129
  new:
132
130
  heading: "Новый вопрос"
133
131
  title: "Новый вопрос"
132
+ nav_text: "Добавить вопрос"
134
133
  edit:
135
134
  heading: "Редактирование вопроса"
136
135
  title: "Редактирование вопроса"
136
+ nav_text: "Редактировать вопрос"
137
137
  update:
138
138
  success: "Вопрос успешно изменён"
139
139
  destroy:
@@ -150,9 +150,11 @@ ru:
150
150
  new:
151
151
  heading: "Новый ответ на вопрос"
152
152
  title: "Новый ответ на вопрос"
153
+ nav_text: "Добавить ответ"
153
154
  edit:
154
155
  heading: "Редактирование ответа на вопрос"
155
156
  title: "Редактирование ответа на вопрос"
157
+ nav_text: "Редактировать ответ"
156
158
  update:
157
159
  success: "Ответ на вопрос успешно изменён"
158
160
  destroy:
data/config/routes.rb CHANGED
@@ -1,35 +1,39 @@
1
+ # frozen_string_literal: true
2
+
1
3
  Rails.application.routes.draw do
4
+ concern :check do
5
+ post :check, on: :collection, defaults: { format: :json }
6
+ end
7
+
8
+ concern :toggle do
9
+ post :toggle, on: :member, defaults: { format: :json }
10
+ end
11
+
12
+ concern :priority do
13
+ post :priority, on: :member, defaults: { format: :json }
14
+ end
15
+
2
16
  resources :polls, :poll_questions, :poll_answers, only: %i[update destroy]
3
17
 
4
18
  scope '(:locale)', constraints: { locale: /ru|en|sv/ } do
5
- resources :polls, except: %i[update destroy] do
19
+ resources :polls, except: %i[update destroy], concerns: :check do
6
20
  member do
7
21
  post 'results' => :answer
8
22
  get 'results'
9
23
  end
10
24
  end
11
- resources :poll_questions, :poll_answers, only: %i[create edit]
25
+ resources :poll_questions, :poll_answers, only: %i[create edit], concerns: :check
12
26
 
13
27
  namespace :admin do
14
- resources :polls, only: %i[index show] do
28
+ resources :polls, only: %i[index show], concerns: :toggle do
15
29
  member do
16
- post 'toggle', defaults: { format: :json }
17
30
  get 'users'
18
31
  post 'users' => :add_user, defaults: { format: :json }
19
32
  delete 'users/:user_id' => :remove_user, as: :user, defaults: { format: :json }
20
33
  end
21
34
  end
22
- resources :poll_questions, only: :show do
23
- member do
24
- post 'toggle', defaults: { format: :json }
25
- post 'priority', defaults: { format: :json }
26
- end
27
- end
28
- resources :poll_answers, only: :show do
29
- member do
30
- post 'priority', defaults: { format: :json }
31
- end
32
- end
35
+ resources :poll_questions, only: :show, concerns: %i[priority toggle]
36
+ resources :poll_answers, only: :show, concerns: :priority
33
37
  end
34
38
  end
35
39
  end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Create component entry and tables for polls component
4
+ class CreateBiovisionPolls < ActiveRecord::Migration[5.1]
5
+ def up
6
+ create_component
7
+ create_polls unless Poll.table_exists?
8
+ create_poll_questions unless PollQuestion.table_exists?
9
+ create_poll_answers unless PollAnswer.table_exists?
10
+ create_poll_votes unless PollVote.table_exists?
11
+ create_poll_users unless PollUser.table_exists?
12
+ end
13
+
14
+ def down
15
+ drop_table :poll_users if PollUser.table_exists?
16
+ drop_table :poll_votes if PollVote.table_exists?
17
+ drop_table :poll_answers if PollAnswer.table_exists?
18
+ drop_table :poll_questions if PollQuestion.table_exists?
19
+ drop_table :polls if Poll.table_exists?
20
+ end
21
+
22
+ private
23
+
24
+ def create_component
25
+ attributes = {
26
+ slug: Biovision::Components::PollsComponent.slug,
27
+ settings: {
28
+ answer_limit: 10,
29
+ question_limit: 10
30
+ }
31
+ }
32
+ BiovisionComponent.create(attributes)
33
+ end
34
+
35
+ def create_polls
36
+ create_table :polls, comment: 'Polls' do |t|
37
+ t.uuid :uuid, null: false
38
+ t.references :language, foreign_key: { on_update: :cascade, on_delete: :cascade }
39
+ t.references :user, null: false, foreign_key: { on_update: :cascade, on_delete: :cascade }
40
+ t.references :simple_image, foreign_key: { on_update: :cascade, on_delete: :nullify }
41
+ t.references :agent, foreign_key: { on_update: :cascade, on_delete: :nullify }
42
+ t.inet :ip
43
+ t.integer :pollable_id
44
+ t.string :pollable_type
45
+ t.timestamps
46
+ t.boolean :visible, default: true, null: false
47
+ t.boolean :active, default: true, null: false
48
+ t.boolean :show_on_homepage, default: true, null: false
49
+ t.boolean :anonymous_votes, default: true, null: false
50
+ t.boolean :open_results, default: true, null: false
51
+ t.boolean :exclusive, default: false, null: false
52
+ t.boolean :allow_comments, default: true, null: false
53
+ t.date :end_date
54
+ t.integer :poll_questions_count, default: 0, null: false
55
+ t.integer :comments_count, default: 0, null: false
56
+ t.string :name
57
+ t.string :description
58
+ t.jsonb :data, default: {}, null: false
59
+ end
60
+
61
+ add_index :polls, :uuid, unique: true
62
+ add_index :polls, :data, using: :gin
63
+ end
64
+
65
+ def create_poll_questions
66
+ create_table :poll_questions, comment: 'Poll questions' do |t|
67
+ t.uuid :uuid, null: false
68
+ t.references :poll, null: false, foreign_key: { on_update: :cascade, on_delete: :cascade }
69
+ t.references :simple_image, foreign_key: { on_update: :cascade, on_delete: :nullify }
70
+ t.integer :priority, limit: 2, default: 1, null: false
71
+ t.timestamps
72
+ t.integer :poll_answers_count, default: 0, null: false
73
+ t.boolean :multiple_choice, default: false, null: false
74
+ t.string :text, null: false
75
+ t.string :comment
76
+ t.jsonb :data, default: {}, null: false
77
+ end
78
+
79
+ add_index :poll_questions, :uuid, unique: true
80
+ add_index :poll_questions, :data, using: :gin
81
+ end
82
+
83
+ def create_poll_answers
84
+ create_table :poll_answers, comment: 'Answers for poll questions' do |t|
85
+ t.uuid :uuid, null: false
86
+ t.references :poll_question, null: false, foreign_key: { on_update: :cascade, on_delete: :cascade }
87
+ t.references :simple_image, foreign_key: { on_update: :cascade, on_delete: :nullify }
88
+ t.integer :priority, limit: 2, default: 1, null: false
89
+ t.timestamps
90
+ t.integer :poll_votes_count, default: 0, null: false
91
+ t.string :text, null: false
92
+ t.jsonb :data, default: {}, null: false
93
+ end
94
+
95
+ add_index :poll_answers, :uuid, unique: true
96
+ add_index :poll_answers, :data, using: :gin
97
+ end
98
+
99
+ def create_poll_votes
100
+ create_table :poll_votes, comment: 'Votes for poll answers' do |t|
101
+ t.uuid :uuid, null: false
102
+ t.references :poll_answer, null: false, foreign_key: { on_update: :cascade, on_delete: :cascade }
103
+ t.references :user, foreign_key: true, on_update: :cascade, on_delete: :cascade
104
+ t.references :agent, foreign_key: true, on_update: :cascade, on_delete: :nullify
105
+ t.inet :ip
106
+ t.timestamps
107
+ t.string :slug
108
+ t.jsonb :data, default: {}, null: false
109
+ end
110
+
111
+ add_index :poll_votes, :uuid, unique: true
112
+ end
113
+
114
+ def create_poll_users
115
+ create_table :poll_users, comment: 'Allowed users in exclusive polls' do |t|
116
+ t.timestamps
117
+ t.references :poll, null: false, foreign_key: { on_update: :cascade, on_delete: :cascade }
118
+ t.references :user, null: false, foreign_key: { on_update: :cascade, on_delete: :cascade }
119
+ end
120
+ end
121
+ end