pairer 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +7 -0
- data/README.md +134 -0
- data/Rakefile +20 -0
- data/app/assets/config/pairer_manifest.js +3 -0
- data/app/assets/images/pairer/favicon.ico +0 -0
- data/app/assets/images/pairer/sweep.png +0 -0
- data/app/assets/javascripts/pairer/application.js +47 -0
- data/app/assets/stylesheets/pairer/application.scss +82 -0
- data/app/assets/stylesheets/pairer/utility.scss +221 -0
- data/app/controllers/pairer/application_controller.rb +31 -0
- data/app/controllers/pairer/boards_controller.rb +202 -0
- data/app/controllers/pairer/sessions_controller.rb +32 -0
- data/app/helpers/pairer/application_helper.rb +4 -0
- data/app/jobs/pairer/application_job.rb +5 -0
- data/app/jobs/pairer/cleanup_boards_job.rb +9 -0
- data/app/mailers/pairer/application_mailer.rb +5 -0
- data/app/models/pairer/application_record.rb +19 -0
- data/app/models/pairer/board.rb +226 -0
- data/app/models/pairer/group.rb +45 -0
- data/app/models/pairer/person.rb +7 -0
- data/app/views/layouts/pairer/application.html.slim +53 -0
- data/app/views/layouts/pairer/application.js.erb +5 -0
- data/app/views/pairer/boards/_current_groups.html.slim +11 -0
- data/app/views/pairer/boards/_group.html.slim +17 -0
- data/app/views/pairer/boards/_group_lock_button.html.slim +4 -0
- data/app/views/pairer/boards/_person.html.slim +9 -0
- data/app/views/pairer/boards/_recently_accessed_boards.html.slim +26 -0
- data/app/views/pairer/boards/_role.html.slim +6 -0
- data/app/views/pairer/boards/_stats.html.slim +37 -0
- data/app/views/pairer/boards/create_group.js.erb +3 -0
- data/app/views/pairer/boards/create_person.js.erb +7 -0
- data/app/views/pairer/boards/delete_group.js.erb +7 -0
- data/app/views/pairer/boards/index.html.slim +28 -0
- data/app/views/pairer/boards/lock_group.js.erb +1 -0
- data/app/views/pairer/boards/lock_person.js.erb +1 -0
- data/app/views/pairer/boards/show.html.slim +240 -0
- data/app/views/pairer/boards/update_group.js.erb +0 -0
- data/app/views/pairer/exceptions/show.html.slim +8 -0
- data/app/views/pairer/sessions/sign_in.html.slim +10 -0
- data/app/views/pairer/shared/_flash.html.slim +6 -0
- data/app/views/pairer/shared/svg/_broom.html.slim +2 -0
- data/config/locales/en.yml +5 -0
- data/config/routes.rb +26 -0
- data/db/migrate/20210821001344_add_pairer_tables.rb +34 -0
- data/db/seeds.rb +0 -0
- data/lib/pairer/config.rb +38 -0
- data/lib/pairer/engine.rb +47 -0
- data/lib/pairer/version.rb +3 -0
- data/lib/pairer.rb +24 -0
- data/lib/tasks/pairer_engine_tasks.rake +4 -0
- metadata +261 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
- session_key = :pairer_board_access_list
|
2
|
+
|
3
|
+
- if session[session_key]
|
4
|
+
- begin
|
5
|
+
- access_list_by_time = session[session_key].to_a.sort_by{|_id,time| Time.parse(time)}.reverse.to_h
|
6
|
+
|
7
|
+
- if access_list_by_time.any?
|
8
|
+
- access_list_by_time.each do |id, time|
|
9
|
+
- if Time.parse(time) < 30.days.ago
|
10
|
+
- session[session_key] = access_list_by_time = access_list_by_time.except(id)
|
11
|
+
|
12
|
+
- boards = Pairer::Board.where(org_id: session[:pairer_current_org_id], public_id: access_list_by_time.keys).map{|x| [x.public_id, x]}.to_h
|
13
|
+
|
14
|
+
- if boards.any?
|
15
|
+
.recently-accessed-boards.well.well-sm.space-above5
|
16
|
+
h1.space-above Recently Accessed Boards
|
17
|
+
|
18
|
+
ul style="list-style: none; padding-left: 0"
|
19
|
+
- access_list_by_time.each do |public_id, _time|
|
20
|
+
- board = boards[public_id]
|
21
|
+
|
22
|
+
- if board
|
23
|
+
li style="margin-top: 10px;" = link_to "#{board.name}", pairer.board_path(public_id), style: "color: #333; text-decoration: underline;"
|
24
|
+
- rescue => e
|
25
|
+
- session.delete(session_key)
|
26
|
+
- raise(e)
|
@@ -0,0 +1,6 @@
|
|
1
|
+
span.role data-role-name=role
|
2
|
+
= role
|
3
|
+
|
4
|
+
span.space-left2.delete
|
5
|
+
= link_to board_path(@board, remove_role_name: role), data: {method: :patch, confirm: "Are you sure you want to remove '#{role}' role ?"}, title: "Delete Role", class: 'btn btn-xs btn-danger' do
|
6
|
+
i.icon-trash
|
@@ -0,0 +1,37 @@
|
|
1
|
+
.stats
|
2
|
+
h3.space-above5
|
3
|
+
.pull-left
|
4
|
+
| Stats
|
5
|
+
= link_to "Reset Stats and Groups", board_path(@board, clear_board: true), class: 'btn btn-danger btn-sm space-left2', data: {method: :patch}
|
6
|
+
|
7
|
+
p
|
8
|
+
= form_tag board_path(@board), method: :patch, class: 'form-inline space-above3' do
|
9
|
+
small
|
10
|
+
span.space-right Statistics will only consider last
|
11
|
+
input.form-control.change-submit name="board[num_iterations_to_track]" value=@board.num_iterations_to_track style="width: 40px; height: 30px; padding: 0 7px; text-align: center;"
|
12
|
+
span.space-left iterations
|
13
|
+
|
14
|
+
- stats = @board.stats
|
15
|
+
|
16
|
+
- if stats.empty?
|
17
|
+
p Cannot show stats with only 1 person on the board
|
18
|
+
- else
|
19
|
+
table.table.table-condensed.table-bordered#stats style="max-width: 400px;"
|
20
|
+
thead
|
21
|
+
th Pair
|
22
|
+
th style="width: 90px;" Count
|
23
|
+
tbody
|
24
|
+
- people_by_id = @board.people.map{|x| [x.to_param, x] }.to_h
|
25
|
+
|
26
|
+
- stats.each do |person_ids, count|
|
27
|
+
tr
|
28
|
+
td = person_ids.map{|person_id| people_by_id[person_id]&.name || "<Person Removed>" }.sort.join(", ")
|
29
|
+
td.text-center = count
|
30
|
+
|
31
|
+
- if Rails.env.development?
|
32
|
+
.well.space-above5
|
33
|
+
h4 Debug Info
|
34
|
+
|
35
|
+
= "Number of Tracked Groups: #{@board.tracked_groups.size}"
|
36
|
+
br
|
37
|
+
= "Tracked Groups Iteration Numbers: #{@board.tracked_groups.collect(&:board_iteration_number).uniq.join(", ")}"
|
@@ -0,0 +1,7 @@
|
|
1
|
+
if(<%= raw @person.persisted? %>){
|
2
|
+
$(".unassigned-lists-container .person-list").append("<%= j render 'pairer/boards/person', person: @person %>")
|
3
|
+
|
4
|
+
$("form.new-person-form").find("input","select","textarea").val('')
|
5
|
+
}else if(<%= raw @person.errors[:name].present? %>){
|
6
|
+
alert("Name <%= @person.errors[:name].first %>");
|
7
|
+
}
|
@@ -0,0 +1,28 @@
|
|
1
|
+
.text-center style="max-width: 600px; margin: 0 auto;"
|
2
|
+
.well.well-sm
|
3
|
+
h1.space-above Find Existing Board
|
4
|
+
|
5
|
+
= form_tag nil, method: :get, class: 'form-inline' do
|
6
|
+
.form-group
|
7
|
+
label Board Password
|
8
|
+
input.form-control name="password" type="password" autofocus=true
|
9
|
+
|
10
|
+
button.btn.btn-sm.btn-success.space-left3 type="submit" Find Board
|
11
|
+
|
12
|
+
.well.well-sm.space-above5
|
13
|
+
h1.space-above Create New Board
|
14
|
+
|
15
|
+
.hint.small.space-below2 You can give out this password to give others access to your new board
|
16
|
+
|
17
|
+
= form_tag boards_path, method: :post, class: 'form-inline' do
|
18
|
+
.form-group
|
19
|
+
label Board Password
|
20
|
+
|
21
|
+
input.form-control.password name="password" type="password"
|
22
|
+
i.icon-eye-open.space-left title="Toggle Password Visibility" onclick="$('input.password').attr('type', ($('input.password').attr('type') == 'text' ? 'password' : 'text'))"
|
23
|
+
|
24
|
+
button.btn.btn-sm.btn-default.space-left3 type="submit"
|
25
|
+
i.icon-plus.space-right
|
26
|
+
span Create Board
|
27
|
+
|
28
|
+
= render "recently_accessed_boards"
|
@@ -0,0 +1 @@
|
|
1
|
+
$(".group-row[data-group-id=<%= @group.public_id %>] .group-lock-btn").replaceWith("<%= j render "group_lock_button", group: @group %>");
|
@@ -0,0 +1 @@
|
|
1
|
+
$(".person[data-person-id=<%= @person.public_id %>]").replaceWith("<%= j render "person", person: @person %>")
|
@@ -0,0 +1,240 @@
|
|
1
|
+
.pull-right
|
2
|
+
.text-right
|
3
|
+
button.btn.btn-default.btn-sm.space-right2 type="button" onclick=("$('.password-form').toggle()") Change Password
|
4
|
+
= link_to "Delete Board", board_path(@board), class: "btn btn-danger btn-sm", data: {method: :delete, confirm: "Are you sure you want to delete this board?"}
|
5
|
+
|
6
|
+
= form_tag board_path(@board), method: :patch, class: 'form-inline password-form', style: 'display:none' do
|
7
|
+
h3 Change Board Password
|
8
|
+
|
9
|
+
label Board Password
|
10
|
+
input.form-control.password name="board[password]" type="password" value=@board.password style="width: 100px;"
|
11
|
+
i.icon-eye-open.space-left onclick="$('input.password').attr('type', ($('input.password').attr('type') == 'text' ? 'password' : 'text'))"
|
12
|
+
|
13
|
+
span.space-left2
|
14
|
+
button.btn.btn-xs.btn-success type="submit" Update
|
15
|
+
|
16
|
+
= form_tag board_path(@board), method: :patch, class: 'form-inline' do
|
17
|
+
- if @board.errors.any?
|
18
|
+
.well.well-sm
|
19
|
+
div.bold Errors:
|
20
|
+
= @board.errors.full_messages.join("<br>").html_safe
|
21
|
+
|
22
|
+
div
|
23
|
+
.form-group
|
24
|
+
label Board Name
|
25
|
+
input.form-control.change-submit name="board[name]" value=@board.name style="width: 180px;"
|
26
|
+
|
27
|
+
br.visible-xs
|
28
|
+
span.space-left3.hidden-xs
|
29
|
+
|
30
|
+
.form-group
|
31
|
+
label Group Size
|
32
|
+
input.form-control.change-submit name="board[group_size]" value=@board.group_size style="width: 50px;"
|
33
|
+
|
34
|
+
.row
|
35
|
+
.col-sm-9.groups-container
|
36
|
+
h3
|
37
|
+
.pull-left.space-right2 Groups
|
38
|
+
= link_to create_group_board_path(@board), data: {method: :post}, remote: true, class: 'btn btn-sm btn-default' do
|
39
|
+
i.icon-plus.space-right
|
40
|
+
span Add Group
|
41
|
+
|
42
|
+
.text-center.space-below3 style="margin-top: -53px;"
|
43
|
+
= link_to shuffle_board_path(@board), class: "btn btn-success", data: {method: :post} do
|
44
|
+
i.icon-refresh.space-right
|
45
|
+
span Shuffle
|
46
|
+
|
47
|
+
= render "current_groups"
|
48
|
+
|
49
|
+
div
|
50
|
+
= link_to create_group_board_path(@board), data: {method: :post}, remote: true, class: 'btn btn-sm btn-default' do
|
51
|
+
i.icon-plus.space-right
|
52
|
+
span Add Group
|
53
|
+
|
54
|
+
.hidden-xs = render 'stats'
|
55
|
+
|
56
|
+
.col-sm-3.unassigned-lists-container
|
57
|
+
.well.space-above3.board-roles-container
|
58
|
+
h3.space-above
|
59
|
+
.pull-left.space-right3 Roles
|
60
|
+
|
61
|
+
= form_tag board_path(@board), method: :patch, class: 'form-inline space-below space-left3' do
|
62
|
+
= text_field_tag :add_role_name, nil, placeholder: "Role Name", style: "width: 150px;", class: 'form-control'
|
63
|
+
|
64
|
+
span.space-left
|
65
|
+
button.btn.btn-sm.btn-default type="submit" Add
|
66
|
+
|
67
|
+
- group_roles = @board.current_groups.flat_map(&:roles_array)
|
68
|
+
- unassigned_roles = @board.roles_array - group_roles
|
69
|
+
.roles-list data-prev-roles=unassigned_roles style="min-height: 25px;"
|
70
|
+
- unassigned_roles.each do |role|
|
71
|
+
= render 'role', role: role
|
72
|
+
|
73
|
+
.board-people-container
|
74
|
+
h3.space-above5
|
75
|
+
.pull-left.space-right3 People
|
76
|
+
|
77
|
+
= form_tag create_person_board_path(@board), remote: true, class: 'form-inline new-person-form space-below space-left3' do
|
78
|
+
= text_field_tag :name, nil, placeholder: "Name", style: "width: 150px;", class: 'form-control'
|
79
|
+
|
80
|
+
span.space-left
|
81
|
+
button.btn.btn-sm.btn-default type="submit" Add
|
82
|
+
|
83
|
+
- group_person_ids = @board.current_groups.flat_map(&:person_ids_array)
|
84
|
+
- unassigned_people = @board.people.select{|x| group_person_ids.exclude?(x.public_id) }.sort_by{|x| x.name }
|
85
|
+
.person-list data-prev-person-ids=unassigned_people.map(&:public_id) style="min-height: 40px;"
|
86
|
+
- unassigned_people.each do |person|
|
87
|
+
= render 'person', person: person
|
88
|
+
|
89
|
+
.visible-xs = render 'stats'
|
90
|
+
|
91
|
+
css:
|
92
|
+
.role,
|
93
|
+
.person{
|
94
|
+
display: inline-block;
|
95
|
+
border: 1px solid black;
|
96
|
+
border-radius: 4px;
|
97
|
+
padding: 0px 10px 3px 10px;
|
98
|
+
margin: 5px 5px;
|
99
|
+
line-height: 40px;
|
100
|
+
cursor: grab;
|
101
|
+
user-select: none;
|
102
|
+
|
103
|
+
box-shadow: 4px 4px rgba(0,0,0, 0.2);
|
104
|
+
}
|
105
|
+
|
106
|
+
.groups-container .person .delete,
|
107
|
+
.groups-container .role .delete{
|
108
|
+
display: none;
|
109
|
+
}
|
110
|
+
|
111
|
+
.group-sweep-btn{
|
112
|
+
background-color: none;
|
113
|
+
color: #333;
|
114
|
+
padding: 3px 5px;
|
115
|
+
line-height: 1;
|
116
|
+
font-size: 20px;
|
117
|
+
border: 1px solid #999;
|
118
|
+
}
|
119
|
+
.group-sweep-btn:hover{
|
120
|
+
background-color: #ede175;
|
121
|
+
color: #333;
|
122
|
+
}
|
123
|
+
.group-lock-btn{
|
124
|
+
padding: 5px 9px;
|
125
|
+
}
|
126
|
+
|
127
|
+
.btn-highlight{
|
128
|
+
background-color: orange;
|
129
|
+
color: white;
|
130
|
+
}
|
131
|
+
.btn-highlight:hover{
|
132
|
+
background-color: darkorange;
|
133
|
+
}
|
134
|
+
|
135
|
+
@media (min-width: 768px){
|
136
|
+
.unassigned-lists-container{
|
137
|
+
min-width: 360px;
|
138
|
+
}
|
139
|
+
.groups-container{
|
140
|
+
max-width: calc(100% - 360px);
|
141
|
+
}
|
142
|
+
}
|
143
|
+
|
144
|
+
javascript:
|
145
|
+
$(function(){
|
146
|
+
|
147
|
+
$('.change-submit').on('change', function(){
|
148
|
+
var item = $(this);
|
149
|
+
setTimeout(function(){
|
150
|
+
if(item.val() == "" && item.is('select[multiple]')){
|
151
|
+
item.attr('disabled', true);
|
152
|
+
$("<input name='"+item.attr('name')+"' value='"+item.val()+"' />").insertBefore(item);
|
153
|
+
}
|
154
|
+
item.closest("form").submit();
|
155
|
+
}, 1);
|
156
|
+
});
|
157
|
+
|
158
|
+
window.init_sortable_lists = function(){
|
159
|
+
$(".person-list").sortable({
|
160
|
+
connectWith: ".person-list",
|
161
|
+
items: "> .person",
|
162
|
+
revert: true,
|
163
|
+
update: function(){
|
164
|
+
var group_id = $(this).data('group-id');
|
165
|
+
var prev_person_ids = $(this).data('prev-person-ids');
|
166
|
+
|
167
|
+
var $el = $(this);
|
168
|
+
|
169
|
+
if(!group_id){
|
170
|
+
// When on the unassigned list
|
171
|
+
return true; // true only skips ajax update, false cancels the entire item drag/move
|
172
|
+
}
|
173
|
+
|
174
|
+
var person_ids = [];
|
175
|
+
|
176
|
+
$el.find('.person').each(function(i, item){
|
177
|
+
person_ids.push($(item).data('person-id'));
|
178
|
+
});
|
179
|
+
|
180
|
+
if(equals(prev_person_ids.sort(), person_ids.sort())){
|
181
|
+
return true; // true only skips ajax update, false cancels the entire item drag/move
|
182
|
+
}
|
183
|
+
|
184
|
+
if(person_ids.length === 0){
|
185
|
+
person_ids.push(""); // to ensure empty array value make the params
|
186
|
+
}
|
187
|
+
|
188
|
+
$.ajax({
|
189
|
+
url: "#{update_group_board_path(@board, format: :js)}",
|
190
|
+
method: "POST",
|
191
|
+
data: {group_id: group_id, person_ids: person_ids},
|
192
|
+
}).done(function(){
|
193
|
+
$el.data('prev-person-ids', person_ids);
|
194
|
+
});
|
195
|
+
},
|
196
|
+
});
|
197
|
+
|
198
|
+
$(".roles-list").sortable({
|
199
|
+
connectWith: ".roles-list",
|
200
|
+
items: "> .role",
|
201
|
+
revert: true,
|
202
|
+
update: function(){
|
203
|
+
var group_id = $(this).data('group-id');
|
204
|
+
var prev_roles = $(this).data('prev-roles');
|
205
|
+
|
206
|
+
$el = $(this);
|
207
|
+
|
208
|
+
if(!group_id){
|
209
|
+
// When on the unassigned list
|
210
|
+
return true; // true only skips ajax update, false cancels the entire item drag/move
|
211
|
+
}
|
212
|
+
|
213
|
+
var roles = [];
|
214
|
+
|
215
|
+
$el.find('.role').each(function(i, item){
|
216
|
+
roles.push($(item).data('role-name'));
|
217
|
+
});
|
218
|
+
|
219
|
+
if(equals(prev_roles.sort(), roles.sort())){
|
220
|
+
return true; // true only skips ajax update, false cancels the entire item drag/move
|
221
|
+
}
|
222
|
+
|
223
|
+
if(roles.length === 0){
|
224
|
+
roles.push(""); // to ensure empty array value make the params
|
225
|
+
}
|
226
|
+
|
227
|
+
$.ajax({
|
228
|
+
url: "#{update_group_board_path(@board, format: :js)}",
|
229
|
+
method: "POST",
|
230
|
+
data: {group_id: group_id, roles: roles},
|
231
|
+
}).done(function(){
|
232
|
+
$el.data('prev-roles', roles);
|
233
|
+
});
|
234
|
+
},
|
235
|
+
});
|
236
|
+
};
|
237
|
+
|
238
|
+
window.init_sortable_lists();
|
239
|
+
|
240
|
+
});
|
File without changes
|
@@ -0,0 +1,10 @@
|
|
1
|
+
.text-center style="max-width: 600px; margin: 0 auto;"
|
2
|
+
.well.well-sm
|
3
|
+
h1.space-above Sign In
|
4
|
+
|
5
|
+
= form_tag nil, method: :post, class: 'form-inline' do
|
6
|
+
.form-group
|
7
|
+
label Organization ID
|
8
|
+
input.form-control type="password" name="org_id" autofocus=true
|
9
|
+
|
10
|
+
button.btn.btn-sm.btn-success.space-left2 type="submit" Submit
|
@@ -0,0 +1,2 @@
|
|
1
|
+
svg width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32" class="iconify iconify--la"
|
2
|
+
path fill="currentColor" d="m28.281 2.281l-10 10L17 11v-.031l-.031-.031c-.64-.57-1.477-.844-2.282-.844c-.804 0-1.582.3-2.187.906l-.156.125l-.5.5l-.344.281L2.375 19l-.875.719L12.281 30.5l.719-.875l7.063-9.063l.03.032l1-1h.032l.031-.032c1.14-1.285 1.149-3.257-.062-4.468l-1.375-1.375l10-10zm-13.593 9.813a1.39 1.39 0 0 1 .906.312c.011.008.02.024.031.031l4.063 4.063c.375.375.41 1.172 0 1.688c-.016.019-.016.042-.032.062l-.312.281l-5.782-5.781l.344-.344c.192-.191.473-.304.781-.312zM12.03 14.03l5.938 5.938l-5.875 7.5l-1.438-1.438l2.156-2.25l-1.437-1.375l-2.125 2.219l-1.313-1.313l3.875-3.906L10.406 18L6.5 21.875l-1.969-1.969z"
|
data/config/routes.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
Pairer::Engine.routes.draw do
|
2
|
+
get :sign_in, to: "sessions#sign_in"
|
3
|
+
post :sign_in, to: "sessions#sign_in"
|
4
|
+
get :sign_out, to: "sessions#sign_out"
|
5
|
+
|
6
|
+
resources :boards, controller: :boards, except: [:new, :edit] do
|
7
|
+
member do
|
8
|
+
post :shuffle
|
9
|
+
post :create_person
|
10
|
+
post :lock_person
|
11
|
+
delete :delete_person
|
12
|
+
post :create_group
|
13
|
+
post :lock_group
|
14
|
+
delete :delete_group
|
15
|
+
post :update_group
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
get '/robots', to: 'application#robots', constraints: ->(req){ req.format == :text }
|
20
|
+
|
21
|
+
match '*a', to: 'application#render_404', via: :get
|
22
|
+
|
23
|
+
get "/", to: "boards#index"
|
24
|
+
|
25
|
+
root "boards#index"
|
26
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class AddPairerTables < ActiveRecord::Migration[6.0]
|
2
|
+
def change
|
3
|
+
|
4
|
+
create_table :pairer_boards do |t|
|
5
|
+
t.string :name, :password
|
6
|
+
t.text :roles
|
7
|
+
t.integer :current_iteration_number, null: false
|
8
|
+
t.integer :group_size
|
9
|
+
t.integer :num_iterations_to_track, null: false
|
10
|
+
t.timestamps
|
11
|
+
t.string :public_id, index: true
|
12
|
+
t.string :org_id
|
13
|
+
end
|
14
|
+
|
15
|
+
create_table :pairer_people do |t|
|
16
|
+
t.references :board
|
17
|
+
t.string :name
|
18
|
+
t.timestamps
|
19
|
+
t.boolean :locked, default: false, null: false
|
20
|
+
t.string :public_id, index: true
|
21
|
+
end
|
22
|
+
|
23
|
+
create_table :pairer_groups do |t|
|
24
|
+
t.references :board
|
25
|
+
t.integer :board_iteration_number
|
26
|
+
t.timestamps
|
27
|
+
t.boolean :locked, default: false, null: false
|
28
|
+
t.text :person_ids
|
29
|
+
t.text :roles
|
30
|
+
t.string :public_id, index: true
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
data/db/seeds.rb
ADDED
File without changes
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Pairer
|
2
|
+
class Config
|
3
|
+
|
4
|
+
@@allowed_org_ids = []
|
5
|
+
mattr_reader :allowed_org_ids
|
6
|
+
|
7
|
+
def self.allowed_org_ids=(val)
|
8
|
+
if val.is_a?(Array)
|
9
|
+
@@allowed_org_ids = val.collect(&:presence).compact
|
10
|
+
else
|
11
|
+
raise "Must be an array"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
@@hash_id_salt = true
|
16
|
+
mattr_reader :hash_id_salt
|
17
|
+
|
18
|
+
def self.hash_id_salt=(val)
|
19
|
+
if val.is_a?(String)
|
20
|
+
@@hash_id_salt = val
|
21
|
+
else
|
22
|
+
raise ArgumentError.new("hash_id_salt must be String")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
@@max_iterations_to_track = 100
|
27
|
+
mattr_reader :max_iterations_to_track
|
28
|
+
|
29
|
+
def self.max_iterations_to_track=(val)
|
30
|
+
if val.is_a?(Integer) && val >= 1
|
31
|
+
@@max_iterations_to_track = val
|
32
|
+
else
|
33
|
+
raise "Must be a positive integer"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'slim'
|
2
|
+
require 'sassc-rails'
|
3
|
+
require 'bootstrap-sass'
|
4
|
+
require 'bootswatch-rails'
|
5
|
+
require 'hashids'
|
6
|
+
|
7
|
+
module Pairer
|
8
|
+
class Engine < ::Rails::Engine
|
9
|
+
isolate_namespace Pairer
|
10
|
+
|
11
|
+
initializer "pairer.assets.precompile" do |app|
|
12
|
+
app.config.assets.precompile << "pairer_manifest.js" ### manifest file required
|
13
|
+
app.config.assets.precompile << "pairer/favicon.ico"
|
14
|
+
|
15
|
+
### Automatically precompile assets in specified folders
|
16
|
+
["app/assets/images/"].each do |folder|
|
17
|
+
dir = app.root.join(folder)
|
18
|
+
|
19
|
+
if Dir.exist?(dir)
|
20
|
+
Dir.glob(File.join(dir, "**/*")).each do |f|
|
21
|
+
asset_name = f.to_s
|
22
|
+
.split(folder).last # Remove fullpath
|
23
|
+
.sub(/^\/*/, '') ### Remove leading '/'
|
24
|
+
|
25
|
+
app.config.assets.precompile << asset_name
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
initializer "pairer.append_migrations" do |app|
|
32
|
+
### Automatically load all migrations into main rails app
|
33
|
+
|
34
|
+
if !app.root.to_s.match?(root.to_s)
|
35
|
+
config.paths["db/migrate"].expanded.each do |expanded_path|
|
36
|
+
app.config.paths["db/migrate"] << expanded_path
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
initializer "pairer.load_static_assets" do |app|
|
42
|
+
### Expose static assets
|
43
|
+
app.middleware.use ::ActionDispatch::Static, "#{root}/public"
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
data/lib/pairer.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require "pairer/engine"
|
2
|
+
require "pairer/config"
|
3
|
+
|
4
|
+
module Pairer
|
5
|
+
|
6
|
+
def self.config(&block)
|
7
|
+
c = Pairer::Config
|
8
|
+
|
9
|
+
if block_given?
|
10
|
+
block.call(c)
|
11
|
+
else
|
12
|
+
return c
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
if RUBY_VERSION.to_f <= 2.6 && !Array.new.respond_to?(:intersection)
|
19
|
+
Array.class_eval do
|
20
|
+
def intersection(other)
|
21
|
+
self & other
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|