safe_in_place_editing 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +318 -0
- data/app/assets/safe_in_place_editing/safe_in_place_editing.js +78 -0
- data/lib/safe_in_place_editing.rb +15 -0
- data/lib/safe_in_place_editing/controller_methods.rb +123 -0
- data/lib/safe_in_place_editing/helper_methods.rb +287 -0
- data/lib/safe_in_place_editing/version.rb +3 -0
- metadata +68 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 384a023dadf89e878b381e57b2c352ecd7317916
|
4
|
+
data.tar.gz: 7ef531b3a8ee0bbeb7ea6c7ce983f0de1593cf5d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 21f0dedc4d6d5643730e53bb73569e0657b8ec39e05c1d1a7b4e0d61cc17b2e50a9f39e97aba73d33b08b4e8b4ed49f726798dbdce7d12aa36571815caa17149
|
7
|
+
data.tar.gz: 620f9756d1595728dc2e9665e51e99788eaddb8021a615032c9681fab3139a45b2ece720e032e43d1d4f02b86059d960be7a3215e47bb4891e668126195ae7ea
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2013 Hipposoft
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,318 @@
|
|
1
|
+
= Safe In Place Editing v2.00 (2013-10-22)
|
2
|
+
|
3
|
+
Early versions of Ruby On Rails shipped with an in-place editing mechanism
|
4
|
+
which allowed simple edits to be performed on model attribute values without
|
5
|
+
having to render a full 'edit' view for that model.
|
6
|
+
|
7
|
+
Unfortunately this suffered a significant vulnerability which basically meant
|
8
|
+
that any in-place editor field was an open JavaScript console, much to the
|
9
|
+
presumed delight of malicious users. In-place editing features were later moved
|
10
|
+
into a plug-in and the cross-site scripting fault was fixed; this simple plugin
|
11
|
+
is available from GitHub:
|
12
|
+
|
13
|
+
* https://github.com/amerine/in_place_editing
|
14
|
+
|
15
|
+
The original version's vulnerability compelled me to write a replacement. Even
|
16
|
+
after the original was fixed, it still had some shortcomings and the Safe In
|
17
|
+
Place Editing code addresses those. The full rationale for its creation, along
|
18
|
+
with instructions on its use are given below.
|
19
|
+
|
20
|
+
Version 1.03 of this software is designed for Rails 3, which deprecates plug-in
|
21
|
+
code. Installation is therefore a bit more complex than running "script/plugin"
|
22
|
+
as under Rails 2. Such is progress, it seems...!
|
23
|
+
|
24
|
+
This plug-in would not exist without the original in-place editor code,
|
25
|
+
from which it borrows very heavily. My thanks to all involved.
|
26
|
+
|
27
|
+
|
28
|
+
== Upgrading
|
29
|
+
|
30
|
+
If you were using the plug-in version of Safe In Place Editing, you need to
|
31
|
+
delete the old Safe In Place Editing code from inside "vendor/plugins" in your
|
32
|
+
app. You will also need to modify the way you include the support JavaScript
|
33
|
+
code from this:
|
34
|
+
|
35
|
+
<%= javascript_include_tag( "safe_in_place_editing/safe_in_place_editing" ) %>
|
36
|
+
|
37
|
+
...to this:
|
38
|
+
|
39
|
+
<%= javascript_include_tag( "safe_in_place_editing" ) %>
|
40
|
+
|
41
|
+
A global search and replace of "safe_in_place_editing/safe_in_place_editing/"
|
42
|
+
to just "safe_in_place_editing" across your application files may suffice.
|
43
|
+
|
44
|
+
Now follow the steps given in "Installation" below, to install the new Gem
|
45
|
+
version of Safe In Place Editing.
|
46
|
+
|
47
|
+
|
48
|
+
== Installation
|
49
|
+
|
50
|
+
Add this line to your application's Gemfile:
|
51
|
+
|
52
|
+
gem 'safe_in_place_editing'
|
53
|
+
|
54
|
+
You will need to use the Prototype JavaScript library rather than jQuery (the
|
55
|
+
two may coexist peacefully, but I haven't tested it). To use that, add the
|
56
|
+
following to your Gemfile, if you don't already have it:
|
57
|
+
|
58
|
+
gem 'prototype-rails'
|
59
|
+
|
60
|
+
You can find out more about proper usage of the JavaScript files provided by
|
61
|
+
this Gem here:
|
62
|
+
|
63
|
+
* https://github.com/rails/prototype-rails
|
64
|
+
|
65
|
+
Finally, execute:
|
66
|
+
|
67
|
+
bundle
|
68
|
+
|
69
|
+
Please note that the Gem here has only been tested in Rails 3 applications
|
70
|
+
that use the Rails Asset Pipeline, so though it might work on (for example)
|
71
|
+
legacy upgraded Rails 2 applications that don't use the asset pipeline file
|
72
|
+
structure, I can't be sure. If you encounter problems, consider switching
|
73
|
+
over to the pipeline instead.
|
74
|
+
|
75
|
+
* http://guides.rubyonrails.org/asset_pipeline.html
|
76
|
+
|
77
|
+
See "Usage exmaples" later for information on how to use the Gem.
|
78
|
+
|
79
|
+
|
80
|
+
== InPlaceEditing problems and Safe In Place Editing solutions
|
81
|
+
=== 1: InPlaceEditing's XSS fixes prevent other features from working
|
82
|
+
|
83
|
+
At the time of writing the original in-place-editor plugin escapes values
|
84
|
+
written into the view coming from the database and values written into the
|
85
|
+
view after a user makes an edit, avoiding the vulnerability which it used
|
86
|
+
to introduce. This introduces some problems though:
|
87
|
+
|
88
|
+
1. It's no longer possible to deliberately insert HTML at all, as per older
|
89
|
+
documentation examples for Textile and Markaby-based in-place editors.
|
90
|
+
|
91
|
+
2. The in-place editor form, when shown, shows the _literal_ escaped text (so
|
92
|
+
you get double-escape problems, with things like "&" showing up instead
|
93
|
+
of "&") because it uses a "getText" call in JavaScript that reads
|
94
|
+
"innerHTML" of the <span> used to mark up the text to be used for
|
95
|
+
editing. There are two ways around this:
|
96
|
+
|
97
|
+
* Patch getText to a better implementation as described on the
|
98
|
+
Scriptaculous Wiki (why is this not in their core release?!)
|
99
|
+
|
100
|
+
https://madrobby.github.com/scriptaculous/ajax-inplaceeditor/
|
101
|
+
|
102
|
+
Object.extend(Ajax.InPlaceEditor.prototype, {
|
103
|
+
getText: function() {
|
104
|
+
return this.element.childNodes[0] ?
|
105
|
+
this.element.childNodes[0].nodeValue : '';
|
106
|
+
}
|
107
|
+
});
|
108
|
+
|
109
|
+
* Have a custom method to return the unescaped value of the field
|
110
|
+
sitting on the server side (again, as for the Textile and Markaby
|
111
|
+
examples) - but this means one more controller method and a server
|
112
|
+
round-trip each time an editor is shown.
|
113
|
+
|
114
|
+
Safe In Place Editing patches getText as described above, escapes values in
|
115
|
+
the initially created form and its auto-generated helpers, if used, escape
|
116
|
+
updated values when rendering them for the in-place view update. You can
|
117
|
+
defeat escaping in the safe_in_place_editor_field helper method with a
|
118
|
+
special override parameter but this is strongly discouraged.
|
119
|
+
|
120
|
+
You must include the JavaScript code for the getText patch and supporting
|
121
|
+
methods when using the plugin:
|
122
|
+
|
123
|
+
<%= javascript_include_tag( "safe_in_place_editing/safe_in_place_editing" ) %>
|
124
|
+
|
125
|
+
|
126
|
+
=== 2: InPlaceEditing bypasses optimistic locking
|
127
|
+
|
128
|
+
Optimistic locking is a safety feature in Rails which even the core
|
129
|
+
developers seem to forget about sometimes! It associates a version number
|
130
|
+
with models. If two users are viewing an 'edit' page for that model and one of
|
131
|
+
them submits their form first, then when the other user tries to submit their
|
132
|
+
own edits, Rails will detect that the associated version number on their form
|
133
|
+
is too old and raise a locking error. Without this, the second user would be
|
134
|
+
able to just overwrite the edits made by the first user without anybody
|
135
|
+
realising this error had happened.
|
136
|
+
|
137
|
+
Unfortunately the stock InPlaceEditing plugin is written in such a way that
|
138
|
+
locking is bypassed; an in-place editor always succeeds, no matter how out of
|
139
|
+
date the view in which the editor resides happens to be.
|
140
|
+
|
141
|
+
One might take the approach of the anti-forgery request mechanism patch needed
|
142
|
+
to get the in-place edit plug-in working with Rails 2, extending the ":with"
|
143
|
+
key's value in the options hash with a query string passing the lock_version
|
144
|
+
through:
|
145
|
+
|
146
|
+
if ( object.respond_to?( :lock_version ) )
|
147
|
+
in_place_editor_options[ :with ] ||= "Form.serialize(form)"
|
148
|
+
in_place_editor_options[ :with ] += " + '&lock_version=#{ object.lock_version }'"
|
149
|
+
end
|
150
|
+
|
151
|
+
The update code on the server side could manually check this against the
|
152
|
+
object it just found in the database. Unfortunately the client JavaScript
|
153
|
+
code is static, so after a first update the form itself is out of date,
|
154
|
+
passing an old lock version through and all subsequent update attempts
|
155
|
+
fail until the whole view is reloaded.
|
156
|
+
|
157
|
+
lock_version = nil
|
158
|
+
lock_version = @item.lock_version.to_s if ( @item.respond_to?( :lock_version ) )
|
159
|
+
|
160
|
+
if ( lock_version != params[ :lock_version ] )
|
161
|
+
# Somebody else already edited this item. Do "something"
|
162
|
+
# (see later).
|
163
|
+
else
|
164
|
+
@item.update_attribute( attribute, params[ :value ] )
|
165
|
+
end
|
166
|
+
|
167
|
+
We might attempt to write out JS which assigns global variables unique to
|
168
|
+
each form with the initial lock value. An on-complete JS handler could
|
169
|
+
then increment the lock version at the client side. This seems ridiculously
|
170
|
+
over complicated given the task at hand, requires one to override the
|
171
|
+
default on-complete handler, bypass or extend the Rails plug-in (since it
|
172
|
+
offers no interface to change the on-complete handler details) and the
|
173
|
+
client might still get out of sync with the server's lock versions. Since
|
174
|
+
there is little alternative, however, Safe In Place Editing takes this
|
175
|
+
heavyweight approach, using extra JS support methods to try and reduce the
|
176
|
+
inline code baggage.
|
177
|
+
|
178
|
+
|
179
|
+
=== 3: InPlaceEditing's error handling seems to be faulty
|
180
|
+
|
181
|
+
In theory, returning a 500 error should lead to the onFailure handler running
|
182
|
+
in the JS domain, but when used from Rails 2.0.2, just about all properties of
|
183
|
+
the 'transport' object used in the default handler function are undefined with
|
184
|
+
the standard InPlaceEditing plugin. As a result, no alert box can be shown to the user. The onComplete handler is *always* run, regardless of whether the
|
185
|
+
request returns a 2xx or other status code and this leads to numerous problems
|
186
|
+
when trying to elegantly handle errors.
|
187
|
+
|
188
|
+
The JavaScript assistance functions included with SafeInPlaceEditing take
|
189
|
+
care of error handling for you. If the on-failure code seems to be having
|
190
|
+
trouble then the on-complete code will take over. These functions are also
|
191
|
+
used to support optimistic locking as described above.
|
192
|
+
|
193
|
+
|
194
|
+
== Usage examples
|
195
|
+
|
196
|
+
Any view using Safe In Place Editing requires the Prototype JavaScript library
|
197
|
+
to be included. See https://github.com/rails/prototype-rails for details. Most
|
198
|
+
people just do this in the "app/assets/javascripts/application.js" file.
|
199
|
+
|
200
|
+
Either in your main application layout file, add:
|
201
|
+
|
202
|
+
<%= javascript_include_tag( "safe_in_place_editing" ) %>
|
203
|
+
|
204
|
+
...in the document <head> section, or include the script via the asset pipeline
|
205
|
+
by editing "app/assets/javascripts/application.js" and adding:
|
206
|
+
|
207
|
+
//= require safe_in_place_editing
|
208
|
+
|
209
|
+
Whether you include these resources globally or only for the views that require
|
210
|
+
them, don't forget to add both the Prototype JavaScript library files and the
|
211
|
+
Safe In Place Editing support code.
|
212
|
+
|
213
|
+
In your controller, declare the models and model attribute names which are
|
214
|
+
to be available for in-place editing. These declarations cause actions to be
|
215
|
+
defined in your controller on your behalf; the actions handle the XHR requests
|
216
|
+
from the client JavaScript code executing in web browsers when users alter
|
217
|
+
attributes of a model via an in-place editor control.
|
218
|
+
|
219
|
+
safe_in_place_edit_for( :customer, :title )
|
220
|
+
safe_in_place_edit_for( :customer, :code )
|
221
|
+
|
222
|
+
The above sets up a controller so it allows edits to a model called "Customer"
|
223
|
+
for attributes "title" and "code".
|
224
|
+
|
225
|
+
In the view, wherever you want an editor to be available, add a call to the
|
226
|
+
"safe_in_place_editor_field" method. For example, in the case of the controller
|
227
|
+
for the "Customer" model above, we might produce a 'show' view for a model
|
228
|
+
instance stored in "<tt>@customer</tt>" which includes:
|
229
|
+
|
230
|
+
<strong>Title:</strong>
|
231
|
+
<%= safe_in_place_editor_field( @customer, :title ) %>
|
232
|
+
<br />
|
233
|
+
|
234
|
+
<strong>Code:</strong>
|
235
|
+
<%= safe_in_place_editor_field( @customer, :code ) %>
|
236
|
+
<br />
|
237
|
+
|
238
|
+
If you're familiar with the API for the InPlaceEditing plugin, you may be
|
239
|
+
surprised at the use of "<tt>@customer</tt>" rather than ":customer" in the
|
240
|
+
call to "safe_in_place_editor_field". In fact, this call supports _either_
|
241
|
+
form - you can pass a symbol "<tt>:foo</tt>", in which case the plugin assumes
|
242
|
+
that an instance variable "<tt>@foo</tt>" is available - or you can just pass
|
243
|
+
in the variable value directly. Judging by Google searches this quirk of the
|
244
|
+
InPlaceEditing API seemed to trip up quite a few people and I saw no reason to
|
245
|
+
duplicate that quirk with Safe In Place Editing!
|
246
|
+
|
247
|
+
|
248
|
+
=== Boolean values
|
249
|
+
|
250
|
+
As an added bonus, Safe In Place Editing has special support for boolean values
|
251
|
+
in models. If you have a true/false field, an in-place editor will show a small
|
252
|
+
pop-up menu including "Yes" and "No" entries.
|
253
|
+
|
254
|
+
Suppose we have a Task model and the task can be marked as currently active, or
|
255
|
+
inactive. To this end it has an attribute "active" which is a boolean property.
|
256
|
+
We can create an in-place editor for this by first enabling editing in the
|
257
|
+
controller for Tasks:
|
258
|
+
|
259
|
+
safe_in_place_edit_for( :task, :active )
|
260
|
+
|
261
|
+
...then in the view, inserting an in-place editor where we might otherwise have
|
262
|
+
just shown the value of the 'active' attribute as a simple piece of text:
|
263
|
+
|
264
|
+
<strong>Active:</strong>
|
265
|
+
<%= safe_in_place_editor_field( @task, :active ) %>
|
266
|
+
|
267
|
+
It's that simple; the plugin code takes care of the rest. There are numerous
|
268
|
+
additional options which can be passed to the various calls; see the API
|
269
|
+
documentation for InPlaceEditing for the basics, then check the API
|
270
|
+
documentation here for any exceptions or additions.
|
271
|
+
|
272
|
+
|
273
|
+
=== Suggested CSS
|
274
|
+
|
275
|
+
You can style in-place editors however you like; the default Rails scaffold
|
276
|
+
styles may be sufficient. If using scaffolding, though, you might like to try
|
277
|
+
out the following additional styles as I think they give good results:
|
278
|
+
|
279
|
+
form.inplaceeditor-form {
|
280
|
+
position: absolute;
|
281
|
+
background: white;
|
282
|
+
border: 2px solid #888;
|
283
|
+
text-align: center;
|
284
|
+
}
|
285
|
+
|
286
|
+
form.inplaceeditor-form input[ type="text" ] {
|
287
|
+
margin: 5px;
|
288
|
+
width: 90%;
|
289
|
+
}
|
290
|
+
|
291
|
+
form.inplaceeditor-form input[ type="submit" ] {
|
292
|
+
margin: 5px;
|
293
|
+
float: left;
|
294
|
+
}
|
295
|
+
|
296
|
+
form.inplaceeditor-form a {
|
297
|
+
margin: 5px;
|
298
|
+
float: right;
|
299
|
+
}
|
300
|
+
|
301
|
+
form.inplaceeditor-form select {
|
302
|
+
margin: 5px;
|
303
|
+
float: left;
|
304
|
+
}
|
305
|
+
|
306
|
+
|
307
|
+
== Contacts
|
308
|
+
|
309
|
+
Ideally please raise issues, suggestions, pull requests etc. via GitHub:
|
310
|
+
|
311
|
+
* https://github.com/pond/safe_in_place_editing
|
312
|
+
|
313
|
+
Alternatively free to contact me at "ahodgkin@rowing.org.uk".
|
314
|
+
|
315
|
+
|
316
|
+
= Copyright
|
317
|
+
|
318
|
+
Copyright (c) 2008-2013 Hipposoft, released under the MIT license.
|
@@ -0,0 +1,78 @@
|
|
1
|
+
/************************************************************************\
|
2
|
+
* File: safe_in_place_editing.js *
|
3
|
+
* Hipposoft 2008 *
|
4
|
+
* *
|
5
|
+
* Purpose: Safe, lockable in-place editing - client-side code. *
|
6
|
+
* *
|
7
|
+
* History: 24-Jun-2008 (ADH): Created. *
|
8
|
+
\************************************************************************/
|
9
|
+
|
10
|
+
/* Stop "Jack & Jill", written for display purposes into an HTML page,
|
11
|
+
* from being edited as exactly that - "Jack & Jill" - if the in-place
|
12
|
+
* editor is activated due to Prototype's use of "innerHTML" in its internal
|
13
|
+
* "getText" function. See:
|
14
|
+
*
|
15
|
+
* http://github.com/madrobby/scriptaculous/wikis/ajax-inplaceeditor
|
16
|
+
*/
|
17
|
+
|
18
|
+
Object.extend
|
19
|
+
(
|
20
|
+
Ajax.InPlaceEditor.prototype,
|
21
|
+
{
|
22
|
+
getText: function()
|
23
|
+
{
|
24
|
+
return this.element.childNodes[ 0 ] ? this.element.childNodes[ 0 ].nodeValue : '';
|
25
|
+
}
|
26
|
+
}
|
27
|
+
);
|
28
|
+
|
29
|
+
/* Support the "on success" and "on failure" functions */
|
30
|
+
|
31
|
+
var safeInPlaceEditorDoneFailureReport = false;
|
32
|
+
|
33
|
+
/* Custom in-place editor "on failure" function */
|
34
|
+
|
35
|
+
function safeInPlaceEditorOnFailure( transport )
|
36
|
+
{
|
37
|
+
if ( transport.responseText )
|
38
|
+
{
|
39
|
+
safeInPlaceEditorRaiseAlert( transport );
|
40
|
+
safeInPlaceEditorDoneFailureReport = true;
|
41
|
+
}
|
42
|
+
else
|
43
|
+
{
|
44
|
+
safeInPlaceEditorDoneFailureReport = false;
|
45
|
+
}
|
46
|
+
}
|
47
|
+
|
48
|
+
/* Custom in-place editor "on complete" function (call by proxy to set
|
49
|
+
* the value of 'lockVar' with the name of the lock variable, if any,
|
50
|
+
* held in global (i.e. 'window') context).
|
51
|
+
*/
|
52
|
+
|
53
|
+
function safeInPlaceEditorOnComplete( transport, element, lockVar )
|
54
|
+
{
|
55
|
+
if ( transport.status == 200 )
|
56
|
+
{
|
57
|
+
if ( lockVar ) window[ lockVar ] += 1;
|
58
|
+
}
|
59
|
+
else if ( ! safeInPlaceEditorDoneFailureReport )
|
60
|
+
{
|
61
|
+
safeInPlaceEditorRaiseAlert( transport );
|
62
|
+
}
|
63
|
+
|
64
|
+
safeInPlaceEditorDoneFailureReport = false;
|
65
|
+
}
|
66
|
+
|
67
|
+
/* Helper function - raise an alert describing the given transport object's
|
68
|
+
* responseText value.
|
69
|
+
*/
|
70
|
+
|
71
|
+
function safeInPlaceEditorRaiseAlert( transport )
|
72
|
+
{
|
73
|
+
alert
|
74
|
+
(
|
75
|
+
"Error communicating with the server: " +
|
76
|
+
transport.responseText.stripTags()
|
77
|
+
);
|
78
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'safe_in_place_editing/version'
|
2
|
+
require 'safe_in_place_editing/controller_methods'
|
3
|
+
require 'safe_in_place_editing/helper_methods'
|
4
|
+
|
5
|
+
if defined? ActionController
|
6
|
+
ActionController::Base.send :include, SafeInPlaceEditing
|
7
|
+
ActionController::Base.helper SafeInPlaceEditingHelper
|
8
|
+
end
|
9
|
+
|
10
|
+
module SafeInPlaceEditing
|
11
|
+
module Rails
|
12
|
+
class Engine < ::Rails::Engine
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
########################################################################
|
2
|
+
# File:: safe_in_place_editing.rb
|
3
|
+
# (C):: Hipposoft 2008, 2009
|
4
|
+
#
|
5
|
+
# Purpose:: Safe, lockable in-place editing - controller support.
|
6
|
+
# ----------------------------------------------------------------------
|
7
|
+
# 24-Jun-2008 (ADH): Created.
|
8
|
+
# 22-Oct-2013 (ADH): Incorporated into 'gemified' sources.
|
9
|
+
########################################################################
|
10
|
+
|
11
|
+
module SafeInPlaceEditing
|
12
|
+
|
13
|
+
def self.included( base ) # :nodoc:
|
14
|
+
base.extend( ClassMethods )
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
|
19
|
+
# == Overview
|
20
|
+
#
|
21
|
+
# API equivalent of in_place_edit_for circa 2009, except:
|
22
|
+
#
|
23
|
+
# - Runs all user data through "ERB::Util::html_escape" when sending it to
|
24
|
+
# the view to avoid associated vulnerabilities with otherwise-unescaped
|
25
|
+
# user-supplied data; the current InPlaceEditing plugin does this too,
|
26
|
+
# albeit using "CGI::escapeHTML" for some reason.
|
27
|
+
#
|
28
|
+
# - Supports optimistic locking if a lock_version CGI parameter is
|
29
|
+
# supplied, by explicitly checking the version being updated.
|
30
|
+
#
|
31
|
+
# - Explicitly catches errors and returns them as 500 status codes
|
32
|
+
# with a plain text message regardless of Rails environment.
|
33
|
+
#
|
34
|
+
# - You must include some support JavaScript code and Prototype 1.7.x is
|
35
|
+
# assumed - Rails default tends to be 1.6.x - though it _should_ still
|
36
|
+
# work with older Prototype library versions. See the example below for
|
37
|
+
# details.
|
38
|
+
#
|
39
|
+
# See +safe_in_place_editor+ and +safe_in_place_editor_field+ for the
|
40
|
+
# counterpart helper functions.
|
41
|
+
#
|
42
|
+
#
|
43
|
+
# == Simple example
|
44
|
+
#
|
45
|
+
# This is adapted from the repository at:
|
46
|
+
#
|
47
|
+
# * https://github.com/amerine/in_place_editing
|
48
|
+
#
|
49
|
+
# ...many thanks to those involved.
|
50
|
+
#
|
51
|
+
# # Controller
|
52
|
+
# #
|
53
|
+
# class BlogController < ApplicationController
|
54
|
+
# safe_in_place_edit_for( :post, :title )
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# # View
|
58
|
+
# #
|
59
|
+
# <%= safe_in_place_editor_field( :post, 'title' ) %>
|
60
|
+
#
|
61
|
+
# # Application layout file, document <head> section
|
62
|
+
# #
|
63
|
+
# <%= javascript_include_tag( "safe_in_place_editing/safe_in_place_editing" ) %>
|
64
|
+
#
|
65
|
+
def safe_in_place_edit_for( object, attribute, options = {} )
|
66
|
+
define_method( "set_#{ object }_#{ attribute }" ) do
|
67
|
+
safe_in_place_edit_backend( object, attribute, options )
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
# Back-end for "safe_in_place_edit_for" - the actual invoked implementation
|
75
|
+
# of the dynamically created functions.
|
76
|
+
#
|
77
|
+
def safe_in_place_edit_backend( object, attribute, options )
|
78
|
+
@item = object.to_s.camelize.constantize.find( params[ :id ] )
|
79
|
+
|
80
|
+
lock_version = nil
|
81
|
+
lock_version = @item.lock_version.to_s if ( @item.respond_to?( :lock_version ) )
|
82
|
+
|
83
|
+
if ( params.include?( :lock_version ) and lock_version != params[ :lock_version ] )
|
84
|
+
render( { :status => 500, :text => "Somebody else already edited this #{ object.to_s.humanize.downcase }. Reload the page to obtain the updated version." } )
|
85
|
+
return
|
86
|
+
else
|
87
|
+
begin
|
88
|
+
|
89
|
+
# Call "touch" to make sure the item is modified even if the user has
|
90
|
+
# actually just submitted the form with an unchanged variable. This
|
91
|
+
# makes sure that Rails sees the object as 'dirty' and saves it. For
|
92
|
+
# objects with lock versions, that means the lock version always
|
93
|
+
# increments. The JavaScript code has to assume such an increment and
|
94
|
+
# has no clear way to know if it doesn't happen; we could dream up
|
95
|
+
# something complex but simpler just to ensure Rails is in step.
|
96
|
+
#
|
97
|
+
# In the worst possible case, JavaScript and Rails end up out of step
|
98
|
+
# with the lock version and the user gets told there's a mismatch. A
|
99
|
+
# page reload later and everything is sorted out.
|
100
|
+
|
101
|
+
success = @item.update_attribute( attribute, params[ :value ] )
|
102
|
+
success = @item.touch if ( success && ! lock_version.nil? && @item.lock_version.to_s == lock_version )
|
103
|
+
|
104
|
+
raise "Unable to save changes to database" unless ( success )
|
105
|
+
|
106
|
+
rescue => error
|
107
|
+
render( { :status => 500, :text => error.message } )
|
108
|
+
return
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
value = @item.send( attribute )
|
114
|
+
|
115
|
+
if ( ( value.is_a? TrueClass ) || ( value.is_a? FalseClass ) )
|
116
|
+
value = value ? 'Yes' : 'No'
|
117
|
+
else
|
118
|
+
value = ERB::Util::html_escape( value.to_s )
|
119
|
+
end
|
120
|
+
|
121
|
+
render( { :text => value } )
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,287 @@
|
|
1
|
+
########################################################################
|
2
|
+
# File:: safe_in_place_editing_helper.rb
|
3
|
+
# (C):: Hipposoft 2008, 2009
|
4
|
+
#
|
5
|
+
# Purpose:: Safe, lockable in-place editing - helper methods.
|
6
|
+
# ----------------------------------------------------------------------
|
7
|
+
# 24-Jun-2008 (ADH): Created.
|
8
|
+
# 22-Oct-2013 (ADH): Incorporated into 'gemified' sources.
|
9
|
+
########################################################################
|
10
|
+
|
11
|
+
module SafeInPlaceEditingHelper
|
12
|
+
|
13
|
+
# == Overview
|
14
|
+
#
|
15
|
+
# API equivalent of in_place_editor circa 2009, except fixes various bugs
|
16
|
+
# (see README.rdoc for the rationale) and:
|
17
|
+
#
|
18
|
+
# * New option ":lock_var", which is the name of a global variable to be
|
19
|
+
# used in the JS domain to track the lock version at the client side. By
|
20
|
+
# default set to nil, meaning no optimistic locking support. Options
|
21
|
+
# value ":lock_version" MUST be set to the lock version of the object for
|
22
|
+
# which the in-place editor is being created in this case. The variable
|
23
|
+
# is incremented each time the object is successfully updated through the
|
24
|
+
# in-place editor, since the server will have incremented its lock version
|
25
|
+
# so the client must keep in step. If someone else edits the item, the
|
26
|
+
# client and server lock versions will not match and the update will fail,
|
27
|
+
# which is the desired result.
|
28
|
+
#
|
29
|
+
# * The ":save_text" option is set to "OK" by default, since I detest that
|
30
|
+
# nasty lower case "ok" button that's produced by the JS code otherwise.
|
31
|
+
#
|
32
|
+
# * The ":cancel_text" option is set to "Cancel" by default to match the
|
33
|
+
# above change.
|
34
|
+
#
|
35
|
+
# * New option ":is_boolean" indicating a true/false popup should be offered
|
36
|
+
# instead of a text field; if omitted, a text field is assumed. If present
|
37
|
+
# and 'true', additional optional value ":first_value" says whether or not
|
38
|
+
# the pop-up menu should start with True/Yes (if ":first_value"'s value is
|
39
|
+
# itself true), or False/No (if ":first_value"'s value is itself false, or
|
40
|
+
# if the option is omitted).
|
41
|
+
#
|
42
|
+
# Custom on-failure and on-complete functions are used. To try and reduce
|
43
|
+
# the code bulk for each instance of the editor, hard-coded JS function names
|
44
|
+
# are used with the support code placed in 'safe_in_place_editing.js'. See
|
45
|
+
# there for a reference implementation if intending to write customised
|
46
|
+
# equivalents. To override the default names of these functions for any
|
47
|
+
# reason, give the names as strings in options properties :on_complete and
|
48
|
+
# :on_failure, then make sure appropriate JS functions are actually defined.
|
49
|
+
#
|
50
|
+
#
|
51
|
+
# == Original In Place Editor documentation
|
52
|
+
#
|
53
|
+
# This is copied from the repository at:
|
54
|
+
#
|
55
|
+
# * https://github.com/amerine/in_place_editing
|
56
|
+
#
|
57
|
+
# ...many thanks to those involved.
|
58
|
+
#
|
59
|
+
# Makes an HTML element specified by the DOM ID +field_id+ become an in-place
|
60
|
+
# editor of a property.
|
61
|
+
#
|
62
|
+
# A form is automatically created and displayed when the user clicks the element,
|
63
|
+
# something like this:
|
64
|
+
#
|
65
|
+
# <form id="myElement-in-place-edit-form" target="specified url">
|
66
|
+
# <input name="value" text="The content of myElement"/>
|
67
|
+
# <input type="submit" value="ok"/>
|
68
|
+
# <a onclick="javascript to cancel the editing">cancel</a>
|
69
|
+
# </form>
|
70
|
+
#
|
71
|
+
# The form is serialized and sent to the server using an AJAX call, the action on
|
72
|
+
# the server should process the value and return the updated value in the body of
|
73
|
+
# the reponse. The element will automatically be updated with the changed value
|
74
|
+
# (as returned from the server).
|
75
|
+
#
|
76
|
+
# options with (Must be a function) means that you must pass a string like:
|
77
|
+
#
|
78
|
+
# function(transport, element) {what_to_do()}
|
79
|
+
#
|
80
|
+
# Required +options+ are:
|
81
|
+
# <tt>:url</tt>:: Specifies the url where the updated value should
|
82
|
+
# be sent after the user presses "ok".
|
83
|
+
#
|
84
|
+
# Addtional +options+ are:
|
85
|
+
# <tt>:rows</tt>:: Number of rows (more than 1 will use a TEXTAREA)
|
86
|
+
# <tt>:cols</tt>:: Number of characters the text input should span (works for both INPUT and TEXTAREA)
|
87
|
+
# <tt>:size</tt>:: Synonym for :cols when using a single line text input.
|
88
|
+
# <tt>:cancel_text</tt>:: The text on the cancel link. (default: "cancel")
|
89
|
+
# <tt>:save_text</tt>:: The text on the save link. (default: "ok")
|
90
|
+
# <tt>:loading_text</tt>:: The text to display while the data is being loaded from the server (default: "Loading...")
|
91
|
+
# <tt>:saving_text</tt>:: The text to display when submitting to the server (default: "Saving...")
|
92
|
+
# <tt>:external_control</tt>:: The id of an external control used to enter edit mode.
|
93
|
+
# <tt>:load_text_url</tt>:: URL where initial value of editor (content) is retrieved.
|
94
|
+
# <tt>:options</tt>:: Pass through options to the AJAX call (see prototype's Ajax.Updater)
|
95
|
+
# <tt>:with</tt>:: JavaScript snippet that should return what is to be sent
|
96
|
+
# in the AJAX call, +form+ is an implicit parameter
|
97
|
+
# <tt>:script</tt>:: Instructs the in-place editor to evaluate the remote JavaScript response (default: false)
|
98
|
+
# <tt>:click_to_edit_text</tt>:: The text shown during mouseover the editable text (default: "Click to edit")
|
99
|
+
# <tt>:on_complete</tt>:: (Must be a function)Code run if update successful with server. Also if user cancels the form (see https://prototype.lighthouseapp.com/projects/8887/tickets/243).
|
100
|
+
# <tt>:on_failure</tt>:: (Must be a function)Code run if update failed with server
|
101
|
+
#
|
102
|
+
def safe_in_place_editor( field_id, options = {} )
|
103
|
+
|
104
|
+
# Set up some default values
|
105
|
+
|
106
|
+
options[ :with ] ||= "Form.serialize(form).replace(/\\+/g,'%20')"
|
107
|
+
|
108
|
+
if protect_against_forgery?
|
109
|
+
options[ :with ] += " + '&authenticity_token=' + encodeURIComponent('#{ form_authenticity_token }')"
|
110
|
+
end
|
111
|
+
|
112
|
+
options[ :on_complete ] ||= 'safeInPlaceEditorOnComplete'
|
113
|
+
options[ :on_failure ] ||= 'safeInPlaceEditorOnFailure'
|
114
|
+
options[ :save_text ] ||= 'OK'
|
115
|
+
options[ :cancel_text ] ||= 'Cancel'
|
116
|
+
|
117
|
+
# Preliminary script data
|
118
|
+
|
119
|
+
if ( options.include?( :lock_var ) )
|
120
|
+
function = "window['#{ options[ :lock_var ] }']=#{ options[ :lock_version ] };"
|
121
|
+
else
|
122
|
+
function = ''
|
123
|
+
end
|
124
|
+
|
125
|
+
function_name = options[ :is_boolean ] ? 'InPlaceCollectionEditor' : 'InPlaceEditor'
|
126
|
+
|
127
|
+
function << "new Ajax.#{ function_name }("
|
128
|
+
function << "'#{ field_id }', "
|
129
|
+
function << "'#{ url_for( options[ :url ] ) }'"
|
130
|
+
|
131
|
+
# Map Rails in-place editor options to JS in-place editor options - see:
|
132
|
+
#
|
133
|
+
# http://github.com/madrobby/scriptaculous/wikis/ajax-inplaceeditor
|
134
|
+
|
135
|
+
js_options = {}
|
136
|
+
|
137
|
+
js_options[ 'rows' ] = options[ :rows ] if options[ :rows ]
|
138
|
+
js_options[ 'cols' ] = options[ :cols ] if options[ :cols ]
|
139
|
+
js_options[ 'size' ] = options[ :size ] if options[ :size ]
|
140
|
+
js_options[ 'ajaxOptions' ] = options[ :options ] if options[ :options ]
|
141
|
+
js_options[ 'htmlResponse' ] = ! options[ :script ] if options[ :script ]
|
142
|
+
|
143
|
+
js_options[ 'cancelText' ] = %('#{ options[ :cancel_text ] }') if options[ :cancel_text ]
|
144
|
+
js_options[ 'okText' ] = %('#{ options[ :save_text ] }') if options[ :save_text ]
|
145
|
+
js_options[ 'loadingText' ] = %('#{ options[ :loading_text ] }') if options[ :loading_text ]
|
146
|
+
js_options[ 'savingText' ] = %('#{ options[ :saving_text ] }') if options[ :saving_text ]
|
147
|
+
js_options[ 'clickToEditText' ] = %('#{ options[ :click_to_edit_text ] }') if options[ :click_to_edit_text ]
|
148
|
+
js_options[ 'textBetweenControls' ] = %('#{ options[ :text_between_controls ] }') if options[ :text_between_controls ]
|
149
|
+
|
150
|
+
js_options[ 'externalControl' ] = "'#{ options[ :external_control ] }'" if options[ :external_control ]
|
151
|
+
js_options[ 'loadTextURL' ] = "'#{ url_for( options[ :load_text_url ] ) }'" if options[ :load_text_url ]
|
152
|
+
|
153
|
+
js_options[ 'callback' ] = "function(form) { return #{ options[ :with ] }; }" if options[ :with ]
|
154
|
+
|
155
|
+
if options[ :is_boolean ]
|
156
|
+
if options[ :start_value ]
|
157
|
+
js_options[ 'collection' ] = "[['true','Yes'],['false','No']]"
|
158
|
+
else
|
159
|
+
js_options[ 'collection' ] = "[['false','No'],['true','Yes']]"
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Set up the custom on-failure and on-complete handlers
|
164
|
+
|
165
|
+
js_options[ 'onFailure' ] = "#{ options[ :on_failure ] }"
|
166
|
+
|
167
|
+
if ( options.include?( :lock_var ) )
|
168
|
+
js_options['onComplete'] = "function(transport, element) {#{ options[ :on_complete ] }(transport,element,'#{ options[ :lock_var ] }');}"
|
169
|
+
else
|
170
|
+
js_options['onComplete'] = "#{ options[ :on_complete ] }"
|
171
|
+
end
|
172
|
+
|
173
|
+
# Assemble the content
|
174
|
+
|
175
|
+
function << ( ', ' + options_for_javascript( js_options ) ) unless js_options.empty?
|
176
|
+
function << ')'
|
177
|
+
|
178
|
+
return javascript_tag( function )
|
179
|
+
end
|
180
|
+
|
181
|
+
# Close API equivalent of in_place_editor_field, except fixes various bugs
|
182
|
+
# (see the README for rationale). Allows either an object name in the first
|
183
|
+
# parameter (e.g. ":foo", in which case instance variable "@foo" must point
|
184
|
+
# to the object instance of interest) or an object instance (to save messing
|
185
|
+
# around with magic instance variables, but obtains the object name from
|
186
|
+
# "class.name.underscore", so may not be appropriate for unusual object
|
187
|
+
# classes). Anyway, clearer error reporting and the ability to pass in an
|
188
|
+
# object reference directly may help avoid a common error experienced with
|
189
|
+
# the InPlaceEditing plug-in code, as described here at the time of writing:
|
190
|
+
#
|
191
|
+
# http://oldwiki.rubyonrails.org/rails/pages/InPlaceEditing
|
192
|
+
#
|
193
|
+
# Includes the following options:
|
194
|
+
#
|
195
|
+
# * :lock_var is the name of the variable used for optimistic locking, by
|
196
|
+
# default set to a row-unique value. The assumption is that this same
|
197
|
+
# variable gets used throughout the row so that multiple edits on that
|
198
|
+
# row all cause the same variable to be incremented. If your back-end
|
199
|
+
# update function has side effects that might invalidate the value shown
|
200
|
+
# in another column on that row - which would be pretty strange! - you'd
|
201
|
+
# need to override the lock variable name with something that's unique to
|
202
|
+
# both the row and the column. When using a lock variable, additional
|
203
|
+
# option ":lock_version" is always set internally to the lock version of
|
204
|
+
# the object for which the field is being built and cannot be overridden.
|
205
|
+
#
|
206
|
+
# * :with is extended to include a "lock_version" parameter in the query
|
207
|
+
# string so that the client side's idea of the current object's lock
|
208
|
+
# version may be communicated to the server's attribute update action.
|
209
|
+
# This is done internally; there is no need to set the option yourself.
|
210
|
+
#
|
211
|
+
# The editor options also support "is_boolean", which overrides the default
|
212
|
+
# setting of whether or not the column value is considered to be a string
|
213
|
+
# or a boolean quantity. This is provided just-in-case, with no current
|
214
|
+
# known cases where the automatic detection isn't sufficient.
|
215
|
+
#
|
216
|
+
# The Prototype library getText function must be patched as described in
|
217
|
+
# the README rationale; "application.js" is a good place to do this.
|
218
|
+
#
|
219
|
+
# Note an optional fifth parameter which if 'true' will prevent HTML
|
220
|
+
# escaping of the value for values which are really meant to contain HTML
|
221
|
+
# code. Be very, very careful with this.
|
222
|
+
#
|
223
|
+
def safe_in_place_editor_field( object, method, tag_options = {}, editor_options = {}, no_escape = false )
|
224
|
+
|
225
|
+
# Allow a symbol or object instance to be passed. Since the symbol use
|
226
|
+
# case involves accessing a 'magic' related instance variable name and
|
227
|
+
# since there are lots of examples via Google of this confusing people,
|
228
|
+
# raise a helpful error message if the relevant variable is missing.
|
229
|
+
|
230
|
+
if ( object.instance_of?( Symbol ) )
|
231
|
+
object_name = object
|
232
|
+
var_name = "@#{ object_name }"
|
233
|
+
if ( instance_variable_defined?( var_name ) )
|
234
|
+
object = instance_variable_get( var_name )
|
235
|
+
else
|
236
|
+
raise( 'If passing \':foo\' to in_place_editor_field, \'@foo\' must refer to the object for which the field is being built' )
|
237
|
+
end
|
238
|
+
else
|
239
|
+
object_name = object.class.name.underscore
|
240
|
+
end
|
241
|
+
|
242
|
+
# Pass the lock version in for optimistic locking support, should the
|
243
|
+
# object support it. The update callback function must manually compare
|
244
|
+
# the params[ :lock_version ] value against the lock_version.to_s()
|
245
|
+
# value of the object that's being updated.
|
246
|
+
|
247
|
+
if ( object.respond_to?( :lock_version ) )
|
248
|
+
var = "#{ object_name }_#{ object.id }_safeInPlaceEditorLockVersion"
|
249
|
+
|
250
|
+
editor_options[ :lock_version ] = object.lock_version.to_s
|
251
|
+
editor_options[ :lock_var ] ||= var
|
252
|
+
editor_options[ :with ] ||= "Form.serialize(form).replace(/\\+/g,'%20')"
|
253
|
+
editor_options[ :with ] += " + '&lock_version=' + #{ var }"
|
254
|
+
end
|
255
|
+
|
256
|
+
# Escape the value unless told not to and construct the complete in-place
|
257
|
+
# editor assembly. Check for boolean values too, allowing caller-override.
|
258
|
+
|
259
|
+
column_value = object.send( method )
|
260
|
+
|
261
|
+
is_boolean = ( editor_options[ :is_boolean ] || ( column_value.is_a? TrueClass ) || ( column_value.is_a? FalseClass ) )
|
262
|
+
|
263
|
+
if ( is_boolean )
|
264
|
+
editor_options[ :start_value ] = !! column_value
|
265
|
+
column_value = column_value ? 'Yes' : 'No'
|
266
|
+
else
|
267
|
+
column_value = ERB::Util::html_escape( column_value ) unless ( no_escape )
|
268
|
+
end
|
269
|
+
|
270
|
+
tag_options = {
|
271
|
+
:id => "#{ object_name }_#{ method }_#{ object.id }_in_place_editor",
|
272
|
+
:class => "in_place_editor_field"
|
273
|
+
}.merge!( tag_options )
|
274
|
+
|
275
|
+
editor_options[ :url ] ||= url_for( {
|
276
|
+
:action => "set_#{ object_name }_#{ method }",
|
277
|
+
:id => object.id
|
278
|
+
} )
|
279
|
+
|
280
|
+
# Update the boolean value flag, unless the caller had already set one.
|
281
|
+
|
282
|
+
editor_options[ :is_boolean ] = is_boolean unless editor_options.has_key?( :is_boolean )
|
283
|
+
|
284
|
+
return content_tag( :span, column_value.html_safe, tag_options ) +
|
285
|
+
safe_in_place_editor( tag_options[ :id ], editor_options )
|
286
|
+
end
|
287
|
+
end
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: safe_in_place_editing
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 2.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Andrew Hodgkinson
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2013-11-29 00:00:00 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: railties
|
16
|
+
prerelease: false
|
17
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: "3.2"
|
22
|
+
type: :runtime
|
23
|
+
version_requirements: *id001
|
24
|
+
description: Safe In Place Editing Rails extension, providing flexible HTML safe in-place editing with string and boolean type support
|
25
|
+
email:
|
26
|
+
- ahodgkin@rowing.org.uk
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files: []
|
32
|
+
|
33
|
+
files:
|
34
|
+
- lib/safe_in_place_editing/controller_methods.rb
|
35
|
+
- lib/safe_in_place_editing/helper_methods.rb
|
36
|
+
- lib/safe_in_place_editing/version.rb
|
37
|
+
- lib/safe_in_place_editing.rb
|
38
|
+
- app/assets/safe_in_place_editing/safe_in_place_editing.js
|
39
|
+
- MIT-LICENSE
|
40
|
+
- README.rdoc
|
41
|
+
homepage: https://github.com/pond/safe_in_place_editing
|
42
|
+
licenses: []
|
43
|
+
|
44
|
+
metadata: {}
|
45
|
+
|
46
|
+
post_install_message:
|
47
|
+
rdoc_options: []
|
48
|
+
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- &id002
|
54
|
+
- ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- *id002
|
60
|
+
requirements: []
|
61
|
+
|
62
|
+
rubyforge_project:
|
63
|
+
rubygems_version: 2.1.11
|
64
|
+
signing_key:
|
65
|
+
specification_version: 4
|
66
|
+
summary: Safe In Place Editing Rails extension
|
67
|
+
test_files: []
|
68
|
+
|