pairer 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +7 -0
  3. data/README.md +134 -0
  4. data/Rakefile +20 -0
  5. data/app/assets/config/pairer_manifest.js +3 -0
  6. data/app/assets/images/pairer/favicon.ico +0 -0
  7. data/app/assets/images/pairer/sweep.png +0 -0
  8. data/app/assets/javascripts/pairer/application.js +47 -0
  9. data/app/assets/stylesheets/pairer/application.scss +82 -0
  10. data/app/assets/stylesheets/pairer/utility.scss +221 -0
  11. data/app/controllers/pairer/application_controller.rb +31 -0
  12. data/app/controllers/pairer/boards_controller.rb +202 -0
  13. data/app/controllers/pairer/sessions_controller.rb +32 -0
  14. data/app/helpers/pairer/application_helper.rb +4 -0
  15. data/app/jobs/pairer/application_job.rb +5 -0
  16. data/app/jobs/pairer/cleanup_boards_job.rb +9 -0
  17. data/app/mailers/pairer/application_mailer.rb +5 -0
  18. data/app/models/pairer/application_record.rb +19 -0
  19. data/app/models/pairer/board.rb +226 -0
  20. data/app/models/pairer/group.rb +45 -0
  21. data/app/models/pairer/person.rb +7 -0
  22. data/app/views/layouts/pairer/application.html.slim +53 -0
  23. data/app/views/layouts/pairer/application.js.erb +5 -0
  24. data/app/views/pairer/boards/_current_groups.html.slim +11 -0
  25. data/app/views/pairer/boards/_group.html.slim +17 -0
  26. data/app/views/pairer/boards/_group_lock_button.html.slim +4 -0
  27. data/app/views/pairer/boards/_person.html.slim +9 -0
  28. data/app/views/pairer/boards/_recently_accessed_boards.html.slim +26 -0
  29. data/app/views/pairer/boards/_role.html.slim +6 -0
  30. data/app/views/pairer/boards/_stats.html.slim +37 -0
  31. data/app/views/pairer/boards/create_group.js.erb +3 -0
  32. data/app/views/pairer/boards/create_person.js.erb +7 -0
  33. data/app/views/pairer/boards/delete_group.js.erb +7 -0
  34. data/app/views/pairer/boards/index.html.slim +28 -0
  35. data/app/views/pairer/boards/lock_group.js.erb +1 -0
  36. data/app/views/pairer/boards/lock_person.js.erb +1 -0
  37. data/app/views/pairer/boards/show.html.slim +240 -0
  38. data/app/views/pairer/boards/update_group.js.erb +0 -0
  39. data/app/views/pairer/exceptions/show.html.slim +8 -0
  40. data/app/views/pairer/sessions/sign_in.html.slim +10 -0
  41. data/app/views/pairer/shared/_flash.html.slim +6 -0
  42. data/app/views/pairer/shared/svg/_broom.html.slim +2 -0
  43. data/config/locales/en.yml +5 -0
  44. data/config/routes.rb +26 -0
  45. data/db/migrate/20210821001344_add_pairer_tables.rb +34 -0
  46. data/db/seeds.rb +0 -0
  47. data/lib/pairer/config.rb +38 -0
  48. data/lib/pairer/engine.rb +47 -0
  49. data/lib/pairer/version.rb +3 -0
  50. data/lib/pairer.rb +24 -0
  51. data/lib/tasks/pairer_engine_tasks.rake +4 -0
  52. 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,3 @@
1
+ $(".groups-container tbody").append("<%= j render 'pairer/boards/group', group: @group %>");
2
+
3
+ window.init_sortable_lists();
@@ -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,7 @@
1
+ var group_row = $(".group-row[data-group-id=<%= @group.public_id %>]");
2
+
3
+ $('.unassigned-lists-container .person-list').append(group_row.find(".person"));
4
+
5
+ $('.unassigned-lists-container .roles-list').append(group_row.find(".role"));
6
+
7
+ group_row.remove();
@@ -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,8 @@
1
+ div style="text-align: center; padding-top: 25%"
2
+ - case response.status.to_s
3
+ - when "404"
4
+ h1 Page Not Found
5
+ - else
6
+ h1 Sorry, an error occured. The administrator has been notified.
7
+
8
+ .badge.badge-error = "Error #{response.status}"
@@ -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,6 @@
1
+ #flash-container
2
+ - flash.each do |name, msg|
3
+ - if msg.is_a?(String)
4
+ .alert.alert-dismissible class="alert-#{name.to_s == 'notice' ? 'success' : 'danger'}"
5
+ button.close type="button" data-dismiss="alert" aria-hidden="true" ×
6
+ span id="flash_#{name}" = msg
@@ -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"
@@ -0,0 +1,5 @@
1
+ en:
2
+ activerecord:
3
+ attributes:
4
+ 'pairer/board':
5
+ num_iterations_to_track: "Number of Iterations to Track"
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
@@ -0,0 +1,3 @@
1
+ module Pairer
2
+ VERSION = '1.0.0'
3
+ 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
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :pairer do
3
+ # # Task goes here
4
+ # end