It’s just data

Rest In Place

Edit in place is a handy feature where clicking on an area of a web page replaces that portion of the page with a form which will enable the user to update that information.  At one time, this was a part of Rails, but in 2007 it moved out to a plugin, and improved upon.  The one that looked like it most closely matched my needs was REST in Place.  It comes in three flavors, I picked JQuery.

URLencoding

The first issue I had was that I would not be able to authenticate, despite carefully following the instructions for setting a rails_authenticity_token.  Looking at it from the server logs, it appeared that the token received had a space in it.  Looking at it from the client, I found a plus in the same place.  Looks like request wasn’t properly x-www-form-urlencoded. Looking at the source (line 18) verifies this to be the case.  A call to encodeURIComponent would solve this.  So would a call to CGI.escape in the assignment to rails_authenticity_token.  The former seems like the “right” way, but instead of modifying the gem, I decided to take the latter approach for now.

It is not clear to me why this problem wouldn’t affect others enough to have been noticed and fixed.

JSON

Once this was fixed, I could edit information in place.  The trouble is that the formatting was lost after the edit.  The information I was editing at the time was a dollar amount, something that I format with number_to_currency.  It seems that under the covers, after an update is made, the information is re-fetched using JSON and that’s the information that is displayed.  That information is “raw” and essentially straight from the database.  My first attempt to address this was to create new attributes which were not backed by the database; but by default such attributes are not included in the JSON so instead of simply losing the formatting, now I lost the result entirely.  This could be addressed, but I decided to go back to the original attributes, and centralize the formatting.

class ActiveRecord::Base
  def self.dollarize *attrs
    attrs.each do |name|
      define_method name do
        clone = SimpleDelegator.new(self[name])
        clone.extend(ApplicationHelper)
        def clone.to_s
          number_to_currency self, :unit=>''
        end
        def clone.to_json(opts)
          to_s.inspect
        end
        clone
      end

      define_method "#{name}=" do |value|
        self[name.to_sym]=value.gsub(/[^0-9.]/, '').to_f
      end
    end
  end
end

This allows fields to be declared as dollarized, after which point all formatting is taken care of automatically, and all other basic numeric functionality is delegated back to the original Float.

Kinda odd to have ApplicationHelper included within a model, but the amount of lines of code went down, processing became more consistent, so this was totally a net plus.

Caching

Now that the basic functionality worked, I noticed something odd.  If after doing an in-page-edit I clicked on a link and subsequently clicked on the back button, I was presented with JSON instead of the HTML page I was expecting.

I’m not totally sure what is going on here, as the server was properly sending private, max-age=0, must-revalidate as the cache control header.  The only thing I can figure is some oddity with Firefox 3.0.11 with XMLHttpRequeset and https sessions.  I’m not a big fan of conneg for this reason.  When it works, it is very useful.  When it doesn’t, it is often difficult to diagnose and debug.

But I have a known workaround.  By appending a question mark to the URI used for XHR, the client sees it as a different resource and the server processes it the same.

Helper

Wanting to apply the same workaround consistently across my application was the tipping point.  The markup rest_in_place wasn’t all that complicated, but was repetitive.  Time for a helper function:

# Usage:
#   <% rest_in_place(obj) do |rip| -%>
#     ... markup ...
#     <%= rip.editable :field %>
#     ... markup ...
#   <% end -%>

def rest_in_place(obj)
  yield RIP_Builder.new(url_for(obj), obj)
end

class RIP_Builder
  def initialize(url, obj)
    @url = url + "?"
    @obj = obj
    @name = ActionController::RecordIdentifier.singular_class_name(obj)
  end

  def editable(field)
    "<span class='rest_in_place' object='#{@name}' attribute='#{field}' url='#{@url}'>#{@obj.send field}</span>" 
  end
end

Attributes

One final note, only of interest to pedants.  The markup above is not conformant in that it invents new attributes.  In order to satisfy the markup orthodoxy, I’d suggest modifying RIP to accept synonyms for each attribute that happen to start with data-.  I would certainly use it on all public facing websites that made use of this feature.  As to what I use on my own private network, that’s between me and my server.


Hey Sam,

thanks for your suggestions. I’ll try to implement them as soon as I find the time
(finishing my diploma thesis currently). If you already made the changes, I’d be very
glad for patches (or fork and send a pull request).

kind regards

Jan

Posted by Jan Varwig at

Wow: that was quick!

Not all of these were suggestions, and the topic of formatting requires more thought; but I will fork, make changes, and send a pull request when complete.  At the very least, some of these items can be added to the documentation.

Posted by Sam Ruby at

submitted by ossreleasefeed [link] [0 comments]...

Excerpt from programming at

[link]...

Excerpt from Delicious/Vathek at

WRT caching - is Vary being sent?

Posted by Mark Nottingham at

Doesn’t look like it.  This is via FireBug:

Date: Sun, 14 Jun 2009 22:13:04 GMT
Server: Apache/2.2.8 (Ubuntu) PHP/5.2.4-2ubuntu5.6 with Suhosin-Patch mod_ssl/2.2.8 OpenSSL/0.9.8g Phusion_Passenger/2.2.2
X-Powered-By: Phusion Passenger (mod_rails/mod_rack) 2.2.2
X-Runtime: 119
Etag: "4b048a6b713b83272cd80b9e1a266c95"
Cache-Control: private, max-age=0, must-revalidate
Content-Length: 153
Status: 200
Keep-Alive: timeout=15, max=98
Connection: Keep-Alive
Content-Type: text/javascript; charset=utf-8
Posted by Sam Ruby at

Sam, re. caching issue, see bug 388141. No attention in the 2 years since I filed it. Not even confirmed.

Mark: yes. Firefox ignores it.

If I look at that list of issues, I’d suggest you try entering some non-US-ASCII characters next (e.g. a € euro sign), it seems to be the next thing to expect not to work. Too often people pay too little attention to escaping and encoding :(.

Posted by Laurens Holst at

So to make things clear, as I see in this particular case a Vary header is not sent: adding a Vary header, although it of course should be there, will make no difference to Firefox’s cache and the issue will still be there.

Posted by Laurens Holst at

It is my belief that if one says “DO NOT CACHE!!!”, there is no requirement to additionally say “oh by the way, the content varies by value the accept header”.

Posted by Sam Ruby at

Doesn’t have to be a belief: [link] (I.e. it’s a simple fact, though you can still do it to inform the user agent.)

Posted by Anne van Kesteren at

Sam Ruby: Rest In Place

Sam Ruby: Rest In Place...

Excerpt from Anarchogeek Tumblr at

Use custom data attributes as suggested by Sam Ruby

m README.markdown m javascripts/jquery.rest_in_place.js m javascripts/mootools.rest_in_place.js m javascripts/rest_in_place.js m testapp/app/views/users/show.html.erb Use custom data attributes as suggested by Sam Ruby See...

Excerpt from Recent Commits to rest_in_place:33615b0f96fd3f9c61db67c9023d59ed3f357ee4 at

Use custom data attributes as suggested by Sam Ruby

m README.markdown m javascripts/jquery.rest_in_place.js m javascripts/mootools.rest_in_place.js m javascripts/rest_in_place.js m testapp/app/views/users/show.html.erb Use custom data attributes as suggested by Sam Ruby See...

Excerpt from Recent Commits to rest_in_place:master at

Sam Ruby: Rest In Place

[link]...

Excerpt from Delicious/tag/rails at

Add your comment