HellOnline

Icon

Eran's blog

Sessions on Rails without Cookies

Developing for the mobile web feels like being back in 1998. Standardization is lacking although with XHTML-MP we’re doing much better than back in the bad ole days. Still, browsers are many and varied and have different levels of support for different features. Even something so basic like cookies, which we’ve all been taking for granted for so long is no longer guaranteed.

Some phone browsers support cookies. Some get cookie support handled by the gateway. Some have persistent cookies, some do not. How do we get around that? Just like we did before – URL rewriting.
http://example.com/?_session_id=2b34560e1cfd62a265b243accc493c68 much?

Rails already has some support for sessions without cookies. Check out the CGI library (where Rails’ sessions come from) and you’ll see that it knows how to get the session_id from the request parameters. All that means is we have to get them in there. I found a basic URL rewriting idea here. which works by overriding #url_for. I plugged the new method into ApplicationHelper and it almost worked. The only thing missing was support for named route URLs (e.g. foo_url). On my quest to get those working I stumbled on default_url_options and this, much simpler, solution:


def default_url_options(options)
{‘_session_id’ => session.session_id}
end

Really, that’s all you need. Almost. I was having some problems with forms that looked like:

For some reason, the combination of a POST and passing _session_id as a query parameter did not go over well. Time to override #form_for:


def form_for(object_name, *args, &proc)
raise ArgumentError, "Missing block" unless block_given?
key_name = get_session_key_name

# see if a cookie with this key has been set.
if request.cookies.has_key?(key_name) then
return super
end

options = args.last.is_a?(Hash) ? args.pop : {}
concat(form_tag(options.delete(:url) || {}, options.delete(:html) || {}), proc.binding)
# This is what you’re looking for right here:
concat(hidden_field_tag(key_name, session.session_id), proc.binding)
fields_for(object_name, *(args << options), &proc)
concat('', proc.binding)
end

That hidden field will get your session_id across. It feels like an ugly hack and I bet there’s a better way to do this but for now, it’s what works.

Filed under: Ruby on Rails

Rails, meet Java

Most of the backend at work is done using a behemoth of a Java server so it’s pretty hard to avoid cooperating with that no matter what language or what application I’m writing. Right now, it so happens, I’m working on a Rails application that acts as a frontend to an app managed by said java server. My first few weeks on this project consisted mostly of getting java and ruby to play well together.

I started out by adding an XML-RPC layer to our existing services. This wasn’t too hard, there’s a selection of XML-RPC libs for Java with pretty solid implementations. Creating a Rails XML-RPC client was even easier but when my API class started to grow over to 20 methods I decided that a different solution is in order.

Not too long ago, Assaf mentioned ActiveRevver in one of his posts and it seemed like that might be a good place to start. ActiveRevver creates an ActiveRecord-like layer on top of the revver’s web services and that’s exactly what I wanted for myself. A clean interface, similar to ActiveRecord’s that plays well with Rails.

Following ActiveRevver’s example, my models are based on OpenStruct which makes it easy to define objects on the fly (unless, of course, you want to have a method name type). method_missing takes care of delegating member access to the underlying @attributes Ostruct object.


class ActiveFoo::Base
def initialize(attrs = {}, opts = {})
@new_record = opts[:record_from_api] ? false : true
initialize_attributes!(attrs)
end

def initialize_attributes!(attrs)
@attributes = OpenStruct.new(attrs.stringify_keys!)
end
end


class ActiveFoo::Account '',
'password' => '',
'email' => '',
'validated' => false
})
super(schema.merge(attrs), opts)
end
end

A very cool feature in ActiveRevver is their implementation of Associations. Using has_many you can add association just like you would with ActiveRecord. This uses the CollectionProxy class which wraps an array of hashes and presents it as a collection of items of the related class. A sprinkling of find and find_by_foo methods later and this feels just like using ActiveRecord.

One thing I needed to do that I’m not sure ActiveRevver supports was store my objects in a session. To do that, classes must be marshallable. This is easily achieved by adding the marshal_load and marshal_dump methods.


def marshal_dump
[@attributes, new_record?]
end

def marshal_load(obj)
@attributes = obj[0]
@new_record = obj[1]
end

I can now almost completely ignore my Java backend and pretend my application is Ruby and Rails all the way.

Filed under: Ruby on Rails