In-place editing of content is a very cool feature and for the most part dead-simple to do in Rails. Unfortunately all the examples around, including the ones in Rails Recipes, assume you'll be implementing this functionality in only the simplest of cases, usually when displaying just that editable data on the page.
In my case I have much more complex requirements - an instance of an event can have many notes attached to the event. If the author of the note is viewing the event they should be able to edit any notes they've written, and edit them in the page using in_place_editor_field.
In this instance the controller for the notes is different than the controller for the page.
Here's how I finally managed to get it to work, using the editing of a note title as an example.
First, add the following line to the note controller:
RUBY:
-
class ActivityItemNoteController <ApplicationController
-
in_place_edit_for :activity_item_note, :title
This automagically sets up a function in the controller called set_activity_item_note_title for us.
Second, in the view, which is app/views/activity/show.rhtml and thus controlled by the ActivityController I display all notes via:
RUBY:
-
<% activity_item.activity_item_notes.each do |note| %>
-
...
-
<% end %>
At this point every other example of in_place_editor_field would tell you to add the following to your page to create an in-place editor:
RUBY:
-
<%= in_place_editor_for :note, :title %>
or some variant of that (or something even worse if the author is building their example off of Rail's scaffolding). But that won't work in this case for many, many reasons which you have probably already discovered for yourself if you're reading this.
So, third, I customized the in_place_editor_for like so:
RUBY:
-
<%= @activity_item_note = note; in_place_editor_field :activity_item_note, :title, {}, :url => { :controller => 'activity_item_note', :action => 'set_activity_item_note_title', :id => note.id } %>
@activity_item_note = note; - set our iteration as an instance variable instead, since in_place_editor_for requires one of these to work (sigh).
:title - define the property to be edited.
:url => { :controller => 'activity_item_note', :action => 'set_activity_item_note_title', :id => note.id } - take back control of the controller and point it to the one that should handle the edit. Add the name of the method and make sure the id of the note being edited is in there as well.
And that does it.
Warning: Because we're passing the id in the URL there's pretty much nothing in this to stop someone from hijacking the URL, injecting their own id's and running rampant through the database, mucking with titles as they see fit. The default set_activity_item_note_title created when I defined in_place_edit_for :activity_item_note, :title has no provision to manage this sort of authentication. As such it is probably a good idea to wrap the auto-generated functions with your own code to manage authentication, something like:
RUBY:
-
note = ActivityItemNote.find( params[:id] )
-
if @user.id == note.user_id
-
# pass along to built-in 'set_activity_item_note_title' here
-
else
-
# scold the user for trying to edit someone else's note
-
end
In my case a simple version of this is:
RUBY:
-
def set_note_title()
-
begin
-
note = ActivityItemNote.find( params[:id] )
-
if nil != note
-
note.user_id == @user.id ? set_activity_item_note_title : render( :text => note.title )
-
end
-
rescue
-
render :text => ' --- '
-
end
-
end
chris on April 13th 2007 in /dev/rails