pairer 1.0.0

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