biovision-poll 0.1.180917.0 → 0.2.200324.1

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 (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