sufia 3.7.1 → 3.7.2
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.
- checksums.yaml +4 -4
- data/SUFIA_VERSION +1 -1
- data/app/assets/javascripts/sufia.js +3 -0
- data/app/assets/javascripts/sufia/batch_edit.js +170 -0
- data/app/assets/stylesheets/{video-js.css → video-js.css.erb} +5 -2
- data/app/controllers/concerns/sufia/users_controller_behavior.rb +7 -7
- data/app/views/batch_edits/edit.html.erb +0 -175
- data/app/views/static/help.html.erb +1 -1
- data/app/views/users/edit.html.erb +1 -1
- data/lib/sufia/version.rb +1 -1
- data/spec/controllers/users_controller_spec.rb +29 -22
- data/spec/features/users_spec.rb +1 -0
- data/spec/models/active_fedora_pid_based_job_spec.rb +18 -0
- data/spec/models/characterize_job_spec.rb +1 -0
- data/spec/models/generic_file/reload_on_save_spec.rb +25 -0
- data/spec/models/generic_file_spec.rb +24 -0
- data/spec/models/geo_names_resource_spec.rb +10 -0
- data/spec/spec_helper.rb +2 -0
- data/sufia-models/app/models/geo_names_resource.rb +4 -4
- data/sufia-models/lib/sufia/models/generic_file.rb +2 -0
- data/sufia-models/lib/sufia/models/generic_file/reload_on_save.rb +18 -0
- data/sufia-models/lib/sufia/models/jobs/active_fedora_pid_based_job.rb +1 -1
- data/sufia-models/lib/sufia/models/version.rb +1 -1
- data/sufia-models/sufia-models.gemspec +1 -1
- data/sufia.gemspec +6 -0
- metadata +27 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b1c4c30d06096c8bf96dd31815ac959204aa7ba0
|
4
|
+
data.tar.gz: a5f92e07ff3b432ab8b0fcd9c257b86e7c9b76c0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d09bc93fc96305ffb8f2406611419e9979ba1bc28e7096ca9d04370132d91396b820d44b03d23f09d30153b2b3c5d5792b4cc150e5ea45bd0724e8d96a3fdae5
|
7
|
+
data.tar.gz: 6150293ef7e9b349b62deab24475e3b370af265e2116c065c904943880506d3000a13d0878e746fd47b5fe693c78cd88d96f7d64e1df19da812f87e3a4dcb9b5
|
data/SUFIA_VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.7.
|
1
|
+
3.7.2
|
@@ -49,6 +49,9 @@ limitations under the License.
|
|
49
49
|
//= require hydra/batch_select
|
50
50
|
//= require hydra_collections
|
51
51
|
|
52
|
+
// this needs to be after batch_select so that the form ids get setup correctly
|
53
|
+
//= require sufia/batch_edit
|
54
|
+
|
52
55
|
//over ride the blacklight default to submit
|
53
56
|
//form when sort by or show per page change
|
54
57
|
Blacklight.do_select_submit = function() {
|
@@ -0,0 +1,170 @@
|
|
1
|
+
function batch_edit_init () {
|
2
|
+
|
3
|
+
// initialize popover helpers
|
4
|
+
$("a[rel=popover]").popover({ html: true });
|
5
|
+
|
6
|
+
$("tr.expandable").click(function () {
|
7
|
+
$(this).next("ul").slideToggle();
|
8
|
+
|
9
|
+
$(this).find('i.toggle').toggleClass("icon-chevron-down");
|
10
|
+
});
|
11
|
+
|
12
|
+
$("tr.expandable_new").click(function () {
|
13
|
+
$(this).find('i').toggleClass("icon-chevron-down");
|
14
|
+
});
|
15
|
+
|
16
|
+
|
17
|
+
function deserialize(Params) {
|
18
|
+
var Data = Params.split("&");
|
19
|
+
var i = Data.length;
|
20
|
+
var Result = {};
|
21
|
+
while (i--) {
|
22
|
+
var Pair = decodeURIComponent(Data[i]).split("=");
|
23
|
+
var Key = Pair[0];
|
24
|
+
var Val = Pair[1];
|
25
|
+
Result[Key] = Val;
|
26
|
+
}
|
27
|
+
return Result;
|
28
|
+
}
|
29
|
+
|
30
|
+
var ajaxManager = (function () {
|
31
|
+
var requests = [];
|
32
|
+
var running = false;
|
33
|
+
return {
|
34
|
+
addReq: function (opt) {
|
35
|
+
requests.push(opt);
|
36
|
+
},
|
37
|
+
removeReq: function (opt) {
|
38
|
+
if ($.inArray(opt, requests) > -1)
|
39
|
+
requests.splice($.inArray(opt, requests), 1);
|
40
|
+
},
|
41
|
+
runNow: function () {
|
42
|
+
clearTimeout(self.tid);
|
43
|
+
if (!running) {
|
44
|
+
this.run();
|
45
|
+
}
|
46
|
+
},
|
47
|
+
run: function () {
|
48
|
+
running = true;
|
49
|
+
var self = this,
|
50
|
+
orgSuc;
|
51
|
+
|
52
|
+
if (requests.length) {
|
53
|
+
oriSuc = requests[0].complete;
|
54
|
+
|
55
|
+
// combine data from multiple requests
|
56
|
+
if (requests.length > 1) {
|
57
|
+
var data = deserialize(requests[0].data.replace(/\+/g, " "));
|
58
|
+
form = [requests[0].form]
|
59
|
+
for (var i = requests.length - 1; i > 0; i--) {
|
60
|
+
req = requests.pop();
|
61
|
+
adata = deserialize(req.data.replace(/\+/g, " "));
|
62
|
+
for (key in Object.keys(adata)) {
|
63
|
+
curKey = Object.keys(adata)[key];
|
64
|
+
if (curKey.slice(0, 12) == "generic_file") {
|
65
|
+
data[curKey] = adata[curKey];
|
66
|
+
form.push(req.form);
|
67
|
+
}
|
68
|
+
}
|
69
|
+
}
|
70
|
+
requests[0].data = $.param(data);
|
71
|
+
requests[0].form = form;
|
72
|
+
}
|
73
|
+
|
74
|
+
requests[0].complete = function () {
|
75
|
+
if (typeof oriSuc === 'function') oriSuc();
|
76
|
+
if (typeof requests[0].form === 'object') {
|
77
|
+
for (f in form) {
|
78
|
+
form_id = form[f];
|
79
|
+
after_ajax(form_id);
|
80
|
+
}
|
81
|
+
}
|
82
|
+
requests.shift();
|
83
|
+
self.run.apply(self, []);
|
84
|
+
};
|
85
|
+
|
86
|
+
$.ajax(requests[0]);
|
87
|
+
} else {
|
88
|
+
self.tid = setTimeout(function () {
|
89
|
+
self.run.apply(self, []);
|
90
|
+
}, 500);
|
91
|
+
}
|
92
|
+
running = false;
|
93
|
+
},
|
94
|
+
stop: function () {
|
95
|
+
requests = [];
|
96
|
+
clearTimeout(this.tid);
|
97
|
+
}
|
98
|
+
};
|
99
|
+
}());
|
100
|
+
|
101
|
+
|
102
|
+
ajaxManager.run();
|
103
|
+
|
104
|
+
function after_ajax(form_id) {
|
105
|
+
var key = form_id.replace("form_", "");
|
106
|
+
var save_button = "#" + key + "_save";
|
107
|
+
var outer_div = "#collapse_" + key;
|
108
|
+
$("#status_" + key).html("Changes Saved");
|
109
|
+
$(save_button).removeAttr("disabled");
|
110
|
+
$(outer_div).removeClass("loading");
|
111
|
+
$('#' + form_id).children([".control-group"]).removeClass('hidden')
|
112
|
+
}
|
113
|
+
|
114
|
+
function before_ajax(form_id) {
|
115
|
+
var key = form_id.replace("form_", "");
|
116
|
+
var save_button = "#" + key + "_save";
|
117
|
+
var outer_div = "#collapse_" + key;
|
118
|
+
$(save_button).attr("disabled", "disabled");
|
119
|
+
$(outer_div).addClass("loading");
|
120
|
+
$('#' + form_id).children([".control-group"]).addClass('hidden')
|
121
|
+
}
|
122
|
+
|
123
|
+
|
124
|
+
function runSave(e) {
|
125
|
+
e.preventDefault();
|
126
|
+
var button = $(this);
|
127
|
+
var form = $(button.parent().parent()[0]);
|
128
|
+
var form_id = form[0].id
|
129
|
+
before_ajax(form_id);
|
130
|
+
|
131
|
+
ajaxManager.addReq({
|
132
|
+
form: form_id,
|
133
|
+
queue: "add_doc",
|
134
|
+
url: form.attr("action"),
|
135
|
+
dataType: "json",
|
136
|
+
type: form.attr("method").toUpperCase(),
|
137
|
+
data: form.serialize(),
|
138
|
+
success: function (e) {
|
139
|
+
eval(e.responseText);
|
140
|
+
after_ajax(form_id);
|
141
|
+
},
|
142
|
+
error: function (e) {
|
143
|
+
after_ajax(form_id);
|
144
|
+
if (e.status == 200) {
|
145
|
+
eval(e.responseText);
|
146
|
+
} else {
|
147
|
+
alert("Error! Status: " + e.status);
|
148
|
+
}
|
149
|
+
}
|
150
|
+
});
|
151
|
+
setTimeout(ajaxManager.runNow(), 100);
|
152
|
+
}
|
153
|
+
|
154
|
+
$("#permissions_save").click(runSave);
|
155
|
+
$(".field-save").click(runSave);
|
156
|
+
|
157
|
+
}
|
158
|
+
|
159
|
+
|
160
|
+
|
161
|
+
|
162
|
+
// turbolinks triggers page:load events on page transition
|
163
|
+
// If app isn't using turbolinks, this event will never be triggered, no prob.
|
164
|
+
$(document).on('page:load', function() {
|
165
|
+
batch_edit_init();
|
166
|
+
});
|
167
|
+
|
168
|
+
$(document).ready(function() {
|
169
|
+
batch_edit_init();
|
170
|
+
});
|
@@ -20,8 +20,11 @@ The control icons are from a custom font. Each icon corresponds to a character
|
|
20
20
|
*/
|
21
21
|
@font-face {
|
22
22
|
font-family: 'VideoJS';
|
23
|
-
src:
|
24
|
-
src:
|
23
|
+
src:url(<%= asset_path 'vjs.eot' %>);
|
24
|
+
src:url(<%= asset_path 'vjs.eot?#iefix' %>) format('embedded-opentype'),
|
25
|
+
url(<%= asset_path 'vjs.woff' %>) format('woff'),
|
26
|
+
url(<%= asset_path 'vjs.ttf' %>) format('truetype'),
|
27
|
+
url(<%= asset_path 'vjs.svg#VideoJS' %>) format('svg');
|
25
28
|
font-weight: normal;
|
26
29
|
font-style: normal;
|
27
30
|
}
|
@@ -50,9 +50,9 @@ module Sufia::UsersControllerBehavior
|
|
50
50
|
@user.update_attributes(params.require(:user).permit(*User.permitted_attributes))
|
51
51
|
end
|
52
52
|
@user.populate_attributes if params[:update_directory]
|
53
|
-
@user.
|
53
|
+
@user.remove_avatar = true if params[:delete_avatar]
|
54
54
|
unless @user.save
|
55
|
-
redirect_to sufia.edit_profile_path(
|
55
|
+
redirect_to sufia.edit_profile_path(@user.to_param), alert: @user.errors.full_messages
|
56
56
|
return
|
57
57
|
end
|
58
58
|
params.keys.select {|k, v| k.starts_with? 'remove_trophy_' }.each do |smash_trophy|
|
@@ -60,7 +60,7 @@ module Sufia::UsersControllerBehavior
|
|
60
60
|
current_user.trophies.where(generic_file_id: smash_trophy).destroy_all
|
61
61
|
end
|
62
62
|
Sufia.queue.push(UserEditProfileEventJob.new(@user.user_key))
|
63
|
-
redirect_to sufia.profile_path(
|
63
|
+
redirect_to sufia.profile_path(@user.to_param), notice: "Your profile has been updated"
|
64
64
|
end
|
65
65
|
|
66
66
|
def toggle_trophy
|
@@ -89,7 +89,7 @@ module Sufia::UsersControllerBehavior
|
|
89
89
|
current_user.follow(@user)
|
90
90
|
Sufia.queue.push(UserFollowEventJob.new(current_user.user_key, @user.user_key))
|
91
91
|
end
|
92
|
-
redirect_to sufia.profile_path(
|
92
|
+
redirect_to sufia.profile_path(@user.to_param), notice: "You are following #{@user.to_s}"
|
93
93
|
end
|
94
94
|
|
95
95
|
# Unfollow a user
|
@@ -98,7 +98,7 @@ module Sufia::UsersControllerBehavior
|
|
98
98
|
current_user.stop_following(@user)
|
99
99
|
Sufia.queue.push(UserUnfollowEventJob.new(current_user.user_key, @user.user_key))
|
100
100
|
end
|
101
|
-
redirect_to sufia.profile_path(
|
101
|
+
redirect_to sufia.profile_path(@user.to_param), notice: "You are no longer following #{@user.to_s}"
|
102
102
|
end
|
103
103
|
|
104
104
|
protected
|
@@ -114,11 +114,11 @@ module Sufia::UsersControllerBehavior
|
|
114
114
|
end
|
115
115
|
|
116
116
|
def user_is_current_user
|
117
|
-
redirect_to sufia.profile_path(
|
117
|
+
redirect_to sufia.profile_path(@user.to_param), alert: "Permission denied: cannot access this page." unless @user == current_user
|
118
118
|
end
|
119
119
|
|
120
120
|
def user_not_current_user
|
121
|
-
redirect_to sufia.profile_path(
|
121
|
+
redirect_to sufia.profile_path(@user.to_param), alert: "You cannot follow or unfollow yourself" if @user == current_user
|
122
122
|
end
|
123
123
|
|
124
124
|
def get_sort
|
@@ -1,178 +1,3 @@
|
|
1
|
-
<% content_for :local_js do %>
|
2
|
-
|
3
|
-
window.onbeforeunload = clearBatch;
|
4
|
-
function clearBatch(){
|
5
|
-
// clear the batch edit items (do it inline so it is done before the next page load
|
6
|
-
var clearState = $.ajax({
|
7
|
-
headers: {Accept : "application/javascript"},
|
8
|
-
type: 'POST',
|
9
|
-
url: '<%=sufia.batch_edits_clear_path %>',
|
10
|
-
async: false,
|
11
|
-
});
|
12
|
-
}
|
13
|
-
|
14
|
-
// initialize popover helpers
|
15
|
-
$("a[rel=popover]").popover({ html: true });
|
16
|
-
<% @terms.each do |term| %>
|
17
|
-
<% vals = @show_file.send(term) %>
|
18
|
-
<% next if vals.blank? %>
|
19
|
-
$("#<%='generic_file_'+term.to_s%>").autocomplete({"source":["<%=vals.join(",")%>"]});
|
20
|
-
|
21
|
-
<% end %>
|
22
|
-
|
23
|
-
function after_ajax(form_id){
|
24
|
-
var key = form_id.replace("form_","");
|
25
|
-
var save_button = "#"+key+"_save";
|
26
|
-
var outer_div = "#collapse_"+key;
|
27
|
-
$("#status_"+key).html("Changes Saved");
|
28
|
-
$(save_button).removeAttr("disabled");
|
29
|
-
$(outer_div).removeClass("loading");
|
30
|
-
$('#'+form_id).children([".control-group"]).removeClass('hidden')
|
31
|
-
}
|
32
|
-
|
33
|
-
function before_ajax(form_id){
|
34
|
-
var key = form_id.replace("form_","");
|
35
|
-
var save_button = "#"+key+"_save";
|
36
|
-
var outer_div = "#collapse_"+key;
|
37
|
-
$(save_button).attr("disabled", "disabled");
|
38
|
-
$(outer_div).addClass("loading");
|
39
|
-
$('#'+form_id).children([".control-group"]).addClass('hidden')
|
40
|
-
}
|
41
|
-
|
42
|
-
|
43
|
-
$("tr.expandable").click(function(){
|
44
|
-
$(this).next("ul").slideToggle();
|
45
|
-
|
46
|
-
$(this).find('i.toggle').toggleClass("icon-chevron-down");
|
47
|
-
});
|
48
|
-
|
49
|
-
$("tr.expandable_new").click(function(){
|
50
|
-
$(this).find('i').toggleClass("icon-chevron-down");
|
51
|
-
});
|
52
|
-
|
53
|
-
function deserialize(Params) {
|
54
|
-
var Data = Params.split("&");
|
55
|
-
var i = Data.length;
|
56
|
-
var Result = {};
|
57
|
-
while (i--) {
|
58
|
-
var Pair = decodeURIComponent(Data[i]).split("=");
|
59
|
-
var Key = Pair[0];
|
60
|
-
var Val = Pair[1];
|
61
|
-
Result[Key] = Val;
|
62
|
-
}
|
63
|
-
return Result;
|
64
|
-
}
|
65
|
-
|
66
|
-
var ajaxManager = (function() {
|
67
|
-
var requests = [];
|
68
|
-
var running = false;
|
69
|
-
return {
|
70
|
-
addReq: function(opt) {
|
71
|
-
requests.push(opt);
|
72
|
-
},
|
73
|
-
removeReq: function(opt) {
|
74
|
-
if( $.inArray(opt, requests) > -1 )
|
75
|
-
requests.splice($.inArray(opt, requests), 1);
|
76
|
-
},
|
77
|
-
runNow: function(){
|
78
|
-
clearTimeout(self.tid);
|
79
|
-
if (!running) {
|
80
|
-
run();
|
81
|
-
}
|
82
|
-
},
|
83
|
-
run: function() {
|
84
|
-
running = true;
|
85
|
-
var self = this,
|
86
|
-
orgSuc;
|
87
|
-
|
88
|
-
if( requests.length ) {
|
89
|
-
oriSuc = requests[0].complete;
|
90
|
-
|
91
|
-
// combine data from multiple requests
|
92
|
-
if (requests.length > 1) {
|
93
|
-
var data = deserialize(requests[0].data.replace(/\+/g," "));
|
94
|
-
form = [requests[0].form]
|
95
|
-
for (var i=requests.length-1; i>0 ; i--){
|
96
|
-
req = requests.pop();
|
97
|
-
adata = deserialize(req.data.replace(/\+/g," "));
|
98
|
-
for (key in Object.keys(adata)){
|
99
|
-
curKey = Object.keys(adata)[key] ;
|
100
|
-
if (curKey.slice(0,12) == "generic_file"){
|
101
|
-
data[curKey] = adata[curKey];
|
102
|
-
form.push (req.form);
|
103
|
-
}
|
104
|
-
}
|
105
|
-
}
|
106
|
-
requests[0].data = $.param(data);
|
107
|
-
requests[0].form = form;
|
108
|
-
}
|
109
|
-
|
110
|
-
requests[0].complete = function() {
|
111
|
-
if( typeof oriSuc === 'function' ) oriSuc();
|
112
|
-
if (typeof requests[0].form === 'object'){
|
113
|
-
for (f in form) {
|
114
|
-
form_id = form[f];
|
115
|
-
after_ajax(form_id);
|
116
|
-
}
|
117
|
-
}
|
118
|
-
requests.shift();
|
119
|
-
self.run.apply(self, []);
|
120
|
-
};
|
121
|
-
|
122
|
-
$.ajax(requests[0]);
|
123
|
-
} else {
|
124
|
-
self.tid = setTimeout(function() {
|
125
|
-
self.run.apply(self, []);
|
126
|
-
}, 500);
|
127
|
-
}
|
128
|
-
running = false;
|
129
|
-
},
|
130
|
-
stop: function() {
|
131
|
-
requests = [];
|
132
|
-
clearTimeout(this.tid);
|
133
|
-
}
|
134
|
-
};
|
135
|
-
}());
|
136
|
-
|
137
|
-
ajaxManager.run();
|
138
|
-
|
139
|
-
|
140
|
-
$("#permissions_save").click(runSave);
|
141
|
-
$(".field-save").click(runSave);
|
142
|
-
|
143
|
-
function runSave(e){
|
144
|
-
e.preventDefault();
|
145
|
-
var button = $(this);
|
146
|
-
var form = $(button.parent().parent()[0]);
|
147
|
-
var form_id = form[0].id
|
148
|
-
before_ajax(form_id);
|
149
|
-
|
150
|
-
ajaxManager.addReq({
|
151
|
-
form: form_id,
|
152
|
-
queue: "add_doc",
|
153
|
-
url: form.attr("action"),
|
154
|
-
dataType: "json",
|
155
|
-
type: form.attr("method").toUpperCase(),
|
156
|
-
data: form.serialize(),
|
157
|
-
success: function (e) {
|
158
|
-
eval(e.responseText);
|
159
|
-
after_ajax(form_id);
|
160
|
-
},
|
161
|
-
error: function (e) {
|
162
|
-
after_ajax(form_id);
|
163
|
-
if (e.status == 200) {
|
164
|
-
eval(e.responseText);
|
165
|
-
} else {
|
166
|
-
alert("Error! Status: "+e.status);
|
167
|
-
}
|
168
|
-
},
|
169
|
-
});
|
170
|
-
setTimeout(ajaxManager.runNow(),100);
|
171
|
-
}
|
172
|
-
|
173
|
-
<% end %>
|
174
|
-
|
175
|
-
|
176
1
|
<%= render :partial => 'generic_files/breadcrumbs', :locals => {:include_file=>false} %>
|
177
2
|
<h2 class="non lower">Batch Edit Descriptions <small>Click on labels below to edit file descriptions.</small> </h2>
|
178
3
|
<div class="scrollx scrolly fileHeight"> <!-- original values -->
|
@@ -5,7 +5,7 @@ $("a[rel=popover]").popover({ html: true });
|
|
5
5
|
|
6
6
|
<h1>Edit Profile</h1>
|
7
7
|
<div class="span45 well">
|
8
|
-
<%= form_for @user, :url => sufia.profile_path(
|
8
|
+
<%= form_for @user, :url => sufia.profile_path(@user.to_param), :html => {:multipart => true, :class => 'form-horizontal' } do |f| %>
|
9
9
|
<div class="control-group">
|
10
10
|
<%= f.label :avatar, '<i class="icon-camera-retro"></i> Change picture'.html_safe, :class => "control-label" %>
|
11
11
|
<div class="controls">
|
data/lib/sufia/version.rb
CHANGED
@@ -106,7 +106,7 @@ describe UsersController do
|
|
106
106
|
end
|
107
107
|
it "redirects to show profile when user attempts to edit another profile" do
|
108
108
|
get :edit, id: @another_user.user_key
|
109
|
-
response.should redirect_to(@routes.url_helpers.profile_path(
|
109
|
+
response.should redirect_to(@routes.url_helpers.profile_path(@another_user.to_param))
|
110
110
|
flash[:alert].should include("Permission denied: cannot access this page.")
|
111
111
|
end
|
112
112
|
describe "when the user has trophies" do
|
@@ -129,7 +129,7 @@ describe UsersController do
|
|
129
129
|
describe "#update" do
|
130
130
|
it "should not allow other users to update" do
|
131
131
|
post :update, id: @another_user.user_key, user: { avatar: nil }
|
132
|
-
response.should redirect_to(@routes.url_helpers.profile_path(
|
132
|
+
response.should redirect_to(@routes.url_helpers.profile_path(@another_user.to_param))
|
133
133
|
flash[:alert].should include("Permission denied: cannot access this page.")
|
134
134
|
end
|
135
135
|
it "should set an avatar and redirect to profile" do
|
@@ -139,7 +139,7 @@ describe UsersController do
|
|
139
139
|
Sufia.queue.should_receive(:push).with(s1).once
|
140
140
|
f = fixture_file_upload('/world.png', 'image/png')
|
141
141
|
post :update, id: @user.user_key, user: { avatar: f }
|
142
|
-
response.should redirect_to(@routes.url_helpers.profile_path(
|
142
|
+
response.should redirect_to(@routes.url_helpers.profile_path(@user.to_param))
|
143
143
|
flash[:notice].should include("Your profile has been updated")
|
144
144
|
User.find_by_user_key(@user.user_key).avatar?.should be_true
|
145
145
|
end
|
@@ -147,24 +147,31 @@ describe UsersController do
|
|
147
147
|
Sufia.queue.should_receive(:push).never
|
148
148
|
f = fixture_file_upload('/image.jp2', 'image/jp2')
|
149
149
|
post :update, id: @user.user_key, user: { avatar: f }
|
150
|
-
response.should redirect_to(@routes.url_helpers.edit_profile_path(
|
150
|
+
response.should redirect_to(@routes.url_helpers.edit_profile_path(@user.to_param))
|
151
151
|
flash[:alert].should include("Avatar You are not allowed to upload \"jp2\" files, allowed types: jpg, jpeg, png, gif, bmp, tif, tiff")
|
152
152
|
end
|
153
153
|
it "should validate the size of an avatar" do
|
154
154
|
f = fixture_file_upload('/4-20.png', 'image/png')
|
155
155
|
Sufia.queue.should_receive(:push).never
|
156
156
|
post :update, id: @user.user_key, user: { avatar: f }
|
157
|
-
response.should redirect_to(@routes.url_helpers.edit_profile_path(
|
157
|
+
response.should redirect_to(@routes.url_helpers.edit_profile_path(@user.to_param))
|
158
158
|
flash[:alert].should include("Avatar file size must be less than 2MB")
|
159
159
|
end
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
160
|
+
context "user with existing avatar" do
|
161
|
+
before do
|
162
|
+
f = fixture_file_upload('/world.png', 'image/png')
|
163
|
+
@user.avatar = f
|
164
|
+
@user.save
|
165
|
+
end
|
166
|
+
it "should delete an avatar" do
|
167
|
+
s1 = double('one')
|
168
|
+
UserEditProfileEventJob.should_receive(:new).with(@user.user_key).and_return(s1)
|
169
|
+
Sufia.queue.should_receive(:push).with(s1).once
|
170
|
+
post :update, id: @user.user_key, delete_avatar: true
|
171
|
+
response.should redirect_to(@routes.url_helpers.profile_path(@user.to_param))
|
172
|
+
flash[:notice].should include("Your profile has been updated")
|
173
|
+
User.find_by_user_key(@user.user_key).avatar?.should be_false
|
174
|
+
end
|
168
175
|
end
|
169
176
|
it "should refresh directory attributes" do
|
170
177
|
s1 = double('one')
|
@@ -172,7 +179,7 @@ describe UsersController do
|
|
172
179
|
Sufia.queue.should_receive(:push).with(s1).once
|
173
180
|
User.any_instance.should_receive(:populate_attributes).once
|
174
181
|
post :update, id: @user.user_key, update_directory: true
|
175
|
-
response.should redirect_to(@routes.url_helpers.profile_path(
|
182
|
+
response.should redirect_to(@routes.url_helpers.profile_path(@user.to_param))
|
176
183
|
flash[:notice].should include("Your profile has been updated")
|
177
184
|
end
|
178
185
|
it "should set an social handles" do
|
@@ -181,7 +188,7 @@ describe UsersController do
|
|
181
188
|
@user.googleplus_handle.blank?.should be_true
|
182
189
|
@user.linkedin_handle.blank?.should be_true
|
183
190
|
post :update, id: @user.user_key, user: { twitter_handle: 'twit', facebook_handle: 'face', googleplus_handle: 'goo', linkedin_handle:"link" }
|
184
|
-
response.should redirect_to(@routes.url_helpers.profile_path(
|
191
|
+
response.should redirect_to(@routes.url_helpers.profile_path(@user.to_param))
|
185
192
|
flash[:notice].should include("Your profile has been updated")
|
186
193
|
u = User.find_by_user_key(@user.user_key)
|
187
194
|
u.twitter_handle.should == 'twit'
|
@@ -200,7 +207,7 @@ describe UsersController do
|
|
200
207
|
expect {
|
201
208
|
post :update, id: user.user_key, 'remove_trophy_'+file.noid => 'yes'
|
202
209
|
}.to change { user.trophies.count }.by(-1)
|
203
|
-
response.should redirect_to(@routes.url_helpers.profile_path(
|
210
|
+
response.should redirect_to(@routes.url_helpers.profile_path(user.to_param))
|
204
211
|
flash[:notice].should include("Your profile has been updated")
|
205
212
|
end
|
206
213
|
end
|
@@ -216,7 +223,7 @@ describe UsersController do
|
|
216
223
|
UserFollowEventJob.should_receive(:new).with(@user.user_key, @another_user.user_key).and_return(s1)
|
217
224
|
Sufia.queue.should_receive(:push).with(s1).once
|
218
225
|
post :follow, id: @another_user.user_key
|
219
|
-
response.should redirect_to(@routes.url_helpers.profile_path(
|
226
|
+
response.should redirect_to(@routes.url_helpers.profile_path(@another_user.to_param))
|
220
227
|
flash[:notice].should include("You are following #{@another_user.user_key}")
|
221
228
|
end
|
222
229
|
it "should redirect to profile if already following and not log an event" do
|
@@ -224,14 +231,14 @@ describe UsersController do
|
|
224
231
|
#Resque.should_receive(:enqueue).with(UserFollowEventJob, @user.user_key, @another_user.user_key).never
|
225
232
|
Sufia.queue.should_receive(:push).never
|
226
233
|
post :follow, id: @another_user.user_key
|
227
|
-
response.should redirect_to(@routes.url_helpers.profile_path(
|
234
|
+
response.should redirect_to(@routes.url_helpers.profile_path(@another_user.to_param))
|
228
235
|
flash[:notice].should include("You are following #{@another_user.user_key}")
|
229
236
|
end
|
230
237
|
it "should redirect to profile if user attempts to self-follow and not log an event" do
|
231
238
|
#Resque.should_receive(:enqueue).with(UserFollowEventJob, @user.user_key, @user.user_key).never
|
232
239
|
Sufia.queue.should_receive(:push).never
|
233
240
|
post :follow, id: @user.user_key
|
234
|
-
response.should redirect_to(@routes.url_helpers.profile_path(
|
241
|
+
response.should redirect_to(@routes.url_helpers.profile_path(@user.to_param))
|
235
242
|
flash[:alert].should include("You cannot follow or unfollow yourself")
|
236
243
|
end
|
237
244
|
end
|
@@ -242,7 +249,7 @@ describe UsersController do
|
|
242
249
|
UserUnfollowEventJob.should_receive(:new).with(@user.user_key, @another_user.user_key).and_return(s1)
|
243
250
|
Sufia.queue.should_receive(:push).with(s1).once
|
244
251
|
post :unfollow, id: @another_user.user_key
|
245
|
-
response.should redirect_to(@routes.url_helpers.profile_path(
|
252
|
+
response.should redirect_to(@routes.url_helpers.profile_path(@another_user.to_param))
|
246
253
|
flash[:notice].should include("You are no longer following #{@another_user.user_key}")
|
247
254
|
end
|
248
255
|
it "should redirect to profile if not following and not log an event" do
|
@@ -250,14 +257,14 @@ describe UsersController do
|
|
250
257
|
#Resque.should_receive(:enqueue).with(UserUnfollowEventJob, @user.user_key, @another_user.user_key).never
|
251
258
|
Sufia.queue.should_receive(:push).never
|
252
259
|
post :unfollow, id: @another_user.user_key
|
253
|
-
response.should redirect_to(@routes.url_helpers.profile_path(
|
260
|
+
response.should redirect_to(@routes.url_helpers.profile_path(@another_user.to_param))
|
254
261
|
flash[:notice].should include("You are no longer following #{@another_user.user_key}")
|
255
262
|
end
|
256
263
|
it "should redirect to profile if user attempts to self-follow and not log an event" do
|
257
264
|
#Resque.should_receive(:enqueue).with(UserUnfollowEventJob, @user.user_key, @user.user_key).never
|
258
265
|
Sufia.queue.should_receive(:push).never
|
259
266
|
post :unfollow, id: @user.user_key
|
260
|
-
response.should redirect_to(@routes.url_helpers.profile_path(
|
267
|
+
response.should redirect_to(@routes.url_helpers.profile_path(@user.to_param))
|
261
268
|
flash[:alert].should include("You cannot follow or unfollow yourself")
|
262
269
|
end
|
263
270
|
end
|
data/spec/features/users_spec.rb
CHANGED
@@ -14,6 +14,7 @@ describe "User Profile" do
|
|
14
14
|
it "should be editable" do
|
15
15
|
click_link "curator1@example.com"
|
16
16
|
click_link "Edit Your Profile"
|
17
|
+
page.should have_xpath("//form[@action='/users/curator1@example-dot-com']")
|
17
18
|
fill_in 'user_twitter_handle', with: 'curatorOfData'
|
18
19
|
click_button 'Save Profile'
|
19
20
|
page.should have_content "Your profile has been updated"
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ActiveFedoraPidBasedJob do
|
4
|
+
let (:user) {FactoryGirl.find_or_create(:user)}
|
5
|
+
let (:file) {GenericFile.new.tap do |gf|
|
6
|
+
gf.apply_depositor_metadata(user)
|
7
|
+
gf.save!
|
8
|
+
end}
|
9
|
+
after do
|
10
|
+
file.destroy
|
11
|
+
user.destroy
|
12
|
+
end
|
13
|
+
it "finds object" do
|
14
|
+
job = ActiveFedoraPidBasedJob.new(file.id)
|
15
|
+
expect(job.generic_file).to_not be_nil
|
16
|
+
expect(job.generic_file.reload_on_save?).to be_true
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Sufia::GenericFile::ReloadOnSave do
|
4
|
+
let(:user) { FactoryGirl.find_or_create(:user) }
|
5
|
+
let(:file) { GenericFile.new.tap { |f| f.apply_depositor_metadata(user); f.save! } }
|
6
|
+
|
7
|
+
it 'defaults to not call reload' do
|
8
|
+
file.should_not_receive(:reload)
|
9
|
+
file.save
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'can be set to call reload' do
|
13
|
+
file.reload_on_save = true
|
14
|
+
file.should_receive(:reload)
|
15
|
+
file.save
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'allows reload to be turned off and on' do
|
19
|
+
file.reload_on_save = true
|
20
|
+
file.should_receive(:reload).once
|
21
|
+
file.save
|
22
|
+
file.reload_on_save = false
|
23
|
+
file.save
|
24
|
+
end
|
25
|
+
end
|
@@ -1020,4 +1020,28 @@ describe GenericFile do
|
|
1020
1020
|
end
|
1021
1021
|
end
|
1022
1022
|
end
|
1023
|
+
|
1024
|
+
describe "should create a full to_solr record" do
|
1025
|
+
before do
|
1026
|
+
subject.save
|
1027
|
+
end
|
1028
|
+
after do
|
1029
|
+
subject.destroy
|
1030
|
+
end
|
1031
|
+
it "gets both sets of data into solr" do
|
1032
|
+
f1= GenericFile.find(subject.id)
|
1033
|
+
f2 = GenericFile.find(subject.id)
|
1034
|
+
f2.reload_on_save = true
|
1035
|
+
f1.mime_type = "video/abc123"
|
1036
|
+
f2.title = "abc123"
|
1037
|
+
f1.save
|
1038
|
+
mime_type_key = Solrizer.solr_name("mime_type")
|
1039
|
+
title_key = Solrizer.solr_name("desc_metadata__title", :stored_searchable, type: :string)
|
1040
|
+
expect(f1.to_solr[mime_type_key]).to eq([f1.mime_type])
|
1041
|
+
expect(f1.to_solr[title_key]).to_not eq(f2.title)
|
1042
|
+
f2.save
|
1043
|
+
expect(f2.to_solr[mime_type_key]).to eq([f1.mime_type])
|
1044
|
+
expect(f2.to_solr[title_key]).to eq(f2.title)
|
1045
|
+
end
|
1046
|
+
end
|
1023
1047
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -6,6 +6,7 @@ require 'rspec/rails'
|
|
6
6
|
require 'rspec/autorun'
|
7
7
|
require 'capybara/rspec'
|
8
8
|
require 'capybara/rails'
|
9
|
+
require 'equivalent-xml/rspec_matchers'
|
9
10
|
|
10
11
|
require File.expand_path('../support/features', __FILE__)
|
11
12
|
|
@@ -77,6 +78,7 @@ RSpec.configure do |config|
|
|
77
78
|
|
78
79
|
config.include Devise::TestHelpers, :type => :controller
|
79
80
|
config.include EngineRoutes, :type => :controller
|
81
|
+
config.include EquivalentXml::RSpecMatchers
|
80
82
|
end
|
81
83
|
|
82
84
|
module FactoryGirl
|
@@ -5,10 +5,10 @@ class GeoNamesResource < ActiveResource::Base
|
|
5
5
|
|
6
6
|
def self.collection_path(prefix_options = {}, query_options = nil)
|
7
7
|
super(prefix_options, query_options).gsub(/\.json|\.xml/, "")
|
8
|
-
end
|
9
|
-
|
10
|
-
def self.instantiate_collection(collection, prefix_options = {})
|
11
|
-
col = super(collection["geonames"],prefix_options)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.instantiate_collection(collection, original_params = {}, prefix_options = {})
|
11
|
+
col = super(collection["geonames"], original_params, prefix_options)
|
12
12
|
col.map! {|item| { label: item.name+ (item.adminName1 ? ", " + item.adminName1 : "")+", " + item.countryName, value: item.name+ (item.adminName1 ? ", " + item.adminName1 : "")+", " + item.countryName} }
|
13
13
|
return col
|
14
14
|
end
|
@@ -11,6 +11,7 @@ module Sufia
|
|
11
11
|
autoload :Metadata, 'sufia/models/generic_file/metadata'
|
12
12
|
autoload :Versions, 'sufia/models/generic_file/versions'
|
13
13
|
autoload :VirusCheck, 'sufia/models/generic_file/virus_check'
|
14
|
+
autoload :ReloadOnSave, 'sufia/models/generic_file/reload_on_save'
|
14
15
|
include Sufia::ModelMethods
|
15
16
|
include Sufia::Noid
|
16
17
|
include Sufia::GenericFile::MimeTypes
|
@@ -25,6 +26,7 @@ module Sufia
|
|
25
26
|
include Sufia::GenericFile::Metadata
|
26
27
|
include Sufia::GenericFile::Versions
|
27
28
|
include Sufia::GenericFile::VirusCheck
|
29
|
+
include Sufia::GenericFile::ReloadOnSave
|
28
30
|
|
29
31
|
included do
|
30
32
|
belongs_to :batch, :property => :is_part_of
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# add this in here until we can use a version of Active Fedora that contains this ability
|
2
|
+
module Sufia
|
3
|
+
module GenericFile
|
4
|
+
module ReloadOnSave
|
5
|
+
|
6
|
+
attr_writer :reload_on_save
|
7
|
+
|
8
|
+
def reload_on_save?
|
9
|
+
!!@reload_on_save
|
10
|
+
end
|
11
|
+
|
12
|
+
def refresh
|
13
|
+
self.reload if reload_on_save?
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -8,7 +8,7 @@ class ActiveFedoraPidBasedJob
|
|
8
8
|
self.pid = pid
|
9
9
|
end
|
10
10
|
def object
|
11
|
-
@object ||= ActiveFedora::Base.find(pid, cast:true)
|
11
|
+
@object ||= ActiveFedora::Base.find(pid, cast:true).tap{|f| f.reload_on_save = true}
|
12
12
|
end
|
13
13
|
alias_method :generic_file, :object
|
14
14
|
alias_method :generic_file_id, :pid
|
@@ -27,7 +27,7 @@ Gem::Specification.new do |spec|
|
|
27
27
|
spec.add_development_dependency "rake"
|
28
28
|
|
29
29
|
spec.add_dependency 'rails', '> 4.0.0', '< 5.0'
|
30
|
-
spec.add_dependency 'activeresource' # No longer a dependency of rails 4.0
|
30
|
+
spec.add_dependency 'activeresource', "~>4.0.0" # No longer a dependency of rails 4.0
|
31
31
|
|
32
32
|
spec.add_dependency "blacklight", "~> 4.7.0"
|
33
33
|
spec.add_dependency "hydra-head", "~> 6.5.0"
|
data/sufia.gemspec
CHANGED
@@ -21,6 +21,12 @@ Gem::Specification.new do |gem|
|
|
21
21
|
gem.add_dependency 'blacklight_advanced_search', '~> 2.1.0'
|
22
22
|
gem.add_dependency 'blacklight', '>= 4.5'
|
23
23
|
|
24
|
+
# sass-rails is typically generated into the app's gemfile by `rails new`
|
25
|
+
# In rails 3 it's put into the "assets" group and thus not available to the
|
26
|
+
# app. Blacklight 5.2 requires bootstrap-sass which requires (but does not
|
27
|
+
# declare a dependency on) sass-rails
|
28
|
+
gem.add_dependency 'sass-rails'
|
29
|
+
|
24
30
|
gem.add_dependency 'hydra-batch-edit', '>= 1.1.1', '< 2.0.0'
|
25
31
|
|
26
32
|
gem.add_dependency 'daemons', '1.1.9'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sufia
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.7.
|
4
|
+
version: 3.7.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Coyne
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-03-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sufia-models
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - '='
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 3.7.
|
19
|
+
version: 3.7.2
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - '='
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 3.7.
|
26
|
+
version: 3.7.2
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: blacklight_advanced_search
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - '>='
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '4.5'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: sass-rails
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: hydra-batch-edit
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -223,6 +237,7 @@ files:
|
|
223
237
|
- app/assets/javascripts/contact_form.js
|
224
238
|
- app/assets/javascripts/sufia.js
|
225
239
|
- app/assets/javascripts/sufia/audio.js
|
240
|
+
- app/assets/javascripts/sufia/batch_edit.js
|
226
241
|
- app/assets/javascripts/sufia/batch_select_all.js
|
227
242
|
- app/assets/javascripts/sufia/edit_metadata.js
|
228
243
|
- app/assets/javascripts/sufia/fileupload.js
|
@@ -249,7 +264,7 @@ files:
|
|
249
264
|
- app/assets/stylesheets/sufia-audio-overrides.css
|
250
265
|
- app/assets/stylesheets/sufia.css.scss
|
251
266
|
- app/assets/stylesheets/trophy.css
|
252
|
-
- app/assets/stylesheets/video-js.css
|
267
|
+
- app/assets/stylesheets/video-js.css.erb
|
253
268
|
- app/controllers/authorities_controller.rb
|
254
269
|
- app/controllers/batch_controller.rb
|
255
270
|
- app/controllers/batch_edits_controller.rb
|
@@ -560,6 +575,7 @@ files:
|
|
560
575
|
- spec/jobs/ingest_local_file_job_spec.rb
|
561
576
|
- spec/lib/sufia/id_service_spec.rb
|
562
577
|
- spec/lib/sufia/upload_complete_behavior_spec.rb
|
578
|
+
- spec/models/active_fedora_pid_based_job_spec.rb
|
563
579
|
- spec/models/audit_job_spec.rb
|
564
580
|
- spec/models/batch_spec.rb
|
565
581
|
- spec/models/batch_update_job_spec.rb
|
@@ -569,9 +585,11 @@ files:
|
|
569
585
|
- spec/models/file_content_datastream_spec.rb
|
570
586
|
- spec/models/fits_datastream_spec.rb
|
571
587
|
- spec/models/generic_file/actions_spec.rb
|
588
|
+
- spec/models/generic_file/reload_on_save_spec.rb
|
572
589
|
- spec/models/generic_file/visibility_spec.rb
|
573
590
|
- spec/models/generic_file/web_form_spec.rb
|
574
591
|
- spec/models/generic_file_spec.rb
|
592
|
+
- spec/models/geo_names_resource_spec.rb
|
575
593
|
- spec/models/local_authority_spec.rb
|
576
594
|
- spec/models/properties_datastream_spec.rb
|
577
595
|
- spec/models/single_use_link_spec.rb
|
@@ -659,6 +677,7 @@ files:
|
|
659
677
|
- sufia-models/lib/sufia/models/generic_file/metadata.rb
|
660
678
|
- sufia-models/lib/sufia/models/generic_file/mime_types.rb
|
661
679
|
- sufia-models/lib/sufia/models/generic_file/permissions.rb
|
680
|
+
- sufia-models/lib/sufia/models/generic_file/reload_on_save.rb
|
662
681
|
- sufia-models/lib/sufia/models/generic_file/thumbnail.rb
|
663
682
|
- sufia-models/lib/sufia/models/generic_file/trophies.rb
|
664
683
|
- sufia-models/lib/sufia/models/generic_file/versions.rb
|
@@ -801,6 +820,7 @@ test_files:
|
|
801
820
|
- spec/jobs/ingest_local_file_job_spec.rb
|
802
821
|
- spec/lib/sufia/id_service_spec.rb
|
803
822
|
- spec/lib/sufia/upload_complete_behavior_spec.rb
|
823
|
+
- spec/models/active_fedora_pid_based_job_spec.rb
|
804
824
|
- spec/models/audit_job_spec.rb
|
805
825
|
- spec/models/batch_spec.rb
|
806
826
|
- spec/models/batch_update_job_spec.rb
|
@@ -810,9 +830,11 @@ test_files:
|
|
810
830
|
- spec/models/file_content_datastream_spec.rb
|
811
831
|
- spec/models/fits_datastream_spec.rb
|
812
832
|
- spec/models/generic_file/actions_spec.rb
|
833
|
+
- spec/models/generic_file/reload_on_save_spec.rb
|
813
834
|
- spec/models/generic_file/visibility_spec.rb
|
814
835
|
- spec/models/generic_file/web_form_spec.rb
|
815
836
|
- spec/models/generic_file_spec.rb
|
837
|
+
- spec/models/geo_names_resource_spec.rb
|
816
838
|
- spec/models/local_authority_spec.rb
|
817
839
|
- spec/models/properties_datastream_spec.rb
|
818
840
|
- spec/models/single_use_link_spec.rb
|