Space Vatican

Ramblings of a curious coder

Conditional RJS Explained

RJS is one of those nifty little things that catches your eye when you first start off using Rails. You very quickly get to add ajax and whizzy effects without having to peer into the somewhat messy world of javascript. Some would argue that it’s a bit of a crutch and you’re better off casting it away and just writing the javascript yourself. To an extent I agree, but I do think it has its place (and also its shortcomings). One of the problems with RJS is that it creates a convenient illusion and it can occasionally catch you out unless you understand that illusion and its limitations.

A question I frequently see is along the lines of ‘I want my rjs to do x only if y is not already on the page’. For example if some element is on the page you want to highlight it, if not you want to create it. People often come up with something like this as their first attempt:

1
2
3
4
5
  if page['some_div'].visible
    page['some_div'].replace :partial => 'some_partial'
  else
    page.insert_html :after, 'something_else', :partial => 'header'
  end

It’s important to understand that this cannot possibly ever work, not in a month of mondays or without destroying the fabric of space time. Until you understand this (and I’ll try to explain) you haven’t understood rjs and you will be confused. More generally if the conditions you are interested are client side (is this div there, is x in the text box etc…) then they can’t be handled by a plain old if in your rjs.

RJS in 5 minutes

RJS allows you to write ruby instead of javascript. Unfortunately browsers don’t support ruby as a scripting language, and so that ruby is transformed into javascript. The way it works (simplifying horribly) is that an instance of JavascriptGenerator (the page object you use in a a rjs file or a render :update block) responds to methods by outputting the appropriate javascript (or another proxy object).

For example page[‘some_div’] creates a proxy for the dom element with that id. If you call a special method on it (like render, visual_effect, insert, [] etc…) then rails will generate the appropriate bit of javascript. If you call anything else, then rails assumes you wanted to generate that javascript function call and does that for you (it converts the arguments for you, so strings are escaped properly, hashes are converted to json objects etc…).

So, some examples:

1
2
3
4
  page.select('.alert').each {|element| element.hide()}
  page['customers'].setStyle(:display => :inline, :padding => '1ex')
  page['header'].replace :partial => 'header'
  page['customers']['style']['display']='none'

these generate the following javascript:

1
2
3
4
$$(".alert").each(function(value, index) { value.hide(); });
$("customers").setStyle({"padding": "1ex", "display": "inline"});
$("header").replace("Contents of the partial");
$("customers").style.display = "none";

However you can’t write

1
page['customers'].select("li:not('.active')").each {|element| page.hide }

(hide all children of $(‘customers’) that are list elements without the class active) because the proxy doesn’t know how to proxy it. Not everything works completely seemlessly, so if something doesn’t work there’s a chance that RJS just doesn’t understand what you’ve trying to do. You can normally work you way out of it, in this case

1
2
page['customers'].select("li:not('.active')").each(
    page.literal 'function(element){element.hide()}')

will do the trick (page.literal dumps a literal bit of javascript), the question is whether it’s worth the effort or whether you’d be better off writing raw javascript instead of trying to bludgeon RJS into doing what you want.

R is for Ruby

Your rjs file is just normal ruby, so you can do things like

1
2
3
4
5
6
  if @divs_to_hide
    page.hide(@divs_to_hide)
  end
  if @element_to_remove
    page[@element_to_remove].remove()
  end

Which brings us to the main point: since rjs is just regular ruby (there’s just that special local variable), the if is a regular ruby if. It’s evaluated server-side, and so it’s just plain not possible for it to refer in any meaningful way to something client side.

A new hope

There is a way out though. What we want is a javascript if – so just generate one!

1
2
3
4
5
  page << "if($('some_div').visible()){"
  page['some_div'].replace :partial => 'some_partial'
  page << '}else{'
  page.insert_html :after, 'something_else', :partial => 'header'
  page << '}'

which generates

1
2
3
4
5
if($('some_div').visible()){
  $("some_div").replace("Contents of your partial here");
}else{
  new Insertion.After("something_else", "Contents of your other partial here");
}

Not very pretty [1], but it gets the job done. page<< inserts into the generated javascript the string you pass, we’ve just wrapped our other bits of javascript with some if statements and associated punctuation. Both partials are rendered and stuffed into the javascript and sent to browser (since we can only choose what to use client side) so this wouldn’t be very efficient if you were deciding which of 2 enormous bits of html to insert.

Another trick

It occured to me recently there’s another trick we can play as long as you can express your condition as the existence of an element specified by a css selector:

1
2
3
4
page.select('#foo').each do |element|
  element.highlight
  page.alert('Foo is on the page')
end

This will highlight the element with id foo and display a message if that element exists (I could have used page[‘foo’] in the block if I had wanted to).

As described above, with select we can iterate over a collection and this is just a special case. The collection of items with id foo should contain precisely 1 or 0 elements, so iterating over it is the same as testing whether #foo exists. You can of course put anything you want in this block. The same caveats about all your partials etc… being rendered whether they are used or not still apply and there is the additional limitation that you can’t express things like “do x if y doesn’t exist”.

Hopefully that’s cleared things up a bit. If you want to know more about the magic behind RJS, take a look at protototype_helper.rb in action_view/helpers, it’s where all this stuff lives.

[1] There’s a plugin that tidies this up.

Confused by sync.rb ?

I needed a reader/writer lock the other day and followed the trail to ruby’s Sync class (why this gets to call itself Sync/Synchronizer as opposed to all the other synchronisation primitives is beyond me). The documentation isn’t exactly enlightening either. I’m sure there’s all sorts of clever stuff to do with upgrading a lock you already hold and other stuff like that, but if you’re just interested in the boring case all you need is

1
2
3
4
5
6
7
8
9
  lock = Sync.new

  lock.synchronize(Sync::EX) do
    #do something that requires a writer (exclusive) lock
  end

  lock.synchronize(Sync::SH) do
    #do something that requires a reader (shared) lock
  end

:with or :without You: Link_to_remote’s Mysterious Parameter

One of the nice things about Rails is that it makes it really easy to get off the ground with Ajax, thanks to the link_to_remote helper (and all the other functions that share the same options like remote_function, form_remote_tag, observe_field etc… [1]). The hard work is actually done by prototype but now is not the time to worry about that.

The question that comes up over and over again is how do you add extra parameters, and this is where the :with option comes into play. This is all in the docs, but it’s a little on the terse side. First things first though: if the parameters are known to you at the time when you call link_to_remote there’s no need to use :with. Just pretend it’s a link_to and do

1
2
  link_to_remote 'Click',
        :url => {:action => 'foo', :some_param => 42, :something_else => 'hello world'}

That of course is the boring case. The interesting case happens when you want to submit some javascript variables or some input elements. Take a step back and look at what remote_function actually generates. It looks a little like this

1
2
  new Ajax.Request('/dummy/foo', {asynchronous:true, evalScripts:true,
           parameters:'authenticity_token=' + encodeURIComponent('snip')})

The interesting thing here is the parameters option. It either takes a javascript object or a query string. By default all you’ll get here is the authenticity token (part of Rails’ CRSF protection), and if you’ve turned off that you’ll get nothing. Everything you stick here gets passed as a parameter to your action, and in a nutshell rails sticks the value of your :with option here.

There’s another easy case before we get stuck in. If you just want to submit the contents of the form then the :submit option is your best friend. Just pass it the id of a form, and rails will set the parameters to Form.serialize(‘some_form’) and all the inputs from that form will get sent over with the ajax request.

:with => ‘fancy pants’

In the most general case, you can pass whatever you want to :with as long as it evaluates to a valid query string (or if you don’t have forgery protection turned on any javascript object will do [2]). Object.toQueryString is your friend here, it will turn any javascript object into a query string and takes care of escaping anything. Your other important friend is Form.Element.serialize. Given a form element or id it chucks back an appropriate piece of query string. It’s even available as a method on extended elements, so instead of Form.Element.serialize(‘some_field’) you can write $(‘some_field’).serialize().

So if you’ve got a form element with id message you can create an ajax link that submits it with

1
  link_to_remote 'Click me', :url => {:action => 'foo'}, :with =>"$('message').serialize()"

If you had 2 elements you can just string them together (remember that all we’re doing here is building up a query string:

1
2
  link_to_remote 'Click me', :url => {:action => 'foo'},
                 :with =>"$('message').serialize() + '&' + $('comment').serialize()"

Personally I hate all that messing around concatenating strings and ampersands, so I would usually just write

1
2
3
  link_to_remote 'Click me', :url => {:action => 'foo'},
     :with =>"$H({message: $F('message'), comment: $F('comment'), 
        fromUser:prompt('Tell me something')}).toQueryString()"

What we’ve done here is build up a prototype hash (that’s the $H({…}) bit) and then called toQueryString on it, which does exactly what it says. $F is another prototype helper that, given an id, returns the value of the associated input element. The last part shows that we can do anything we want really. Here I’m asking the user to provide some text, but it could be some pre-existing javascript variable or anything you want.

If you decide not to us the various prototype serialisation helpers then you do need to be a little careful: it’s up to you to ensure that what needs to be escaped is escaped. A handy function here is encodeURIComponent:

1
2
  link_to_remote 'Click me', :url => {:action => 'foo'},
     :with =>"'thing='+encodeURIComponent('I am a nasty & funky string')"

This ensures the ampersand is encoded rather than messing up everything. You can of course combine that with some of the other tricks:

1
2
  link_to_remote 'Click me', :url => {:action => 'foo'},
     :with =>"'thing='+encodeURIComponent(someFunctionReturningAString())"

observe_field is a naughty boy

Everything I’ve said holds true for observe_field, but observe field allows you to take some short cuts. If what you pass to :with doesn’t look like a valid query string or interesting javascript expression then rails assumes you’re just specifying what name you want the parameter to be submitted as, so

1
  observe_field 'some_field', :with => 'q'

expands to

1
  observe_field 'some_field', :with => '"q="+value'

This is executed in a context where value is the new value of the form element. Can you spot the problem yet? You need to return a valid query string and value is just the value of the field. By some good fortune you get away with this right until you type an ampersand (I’ll file a ticket on thisit was fixed here, so edge and rails 2.1 are ok). It’s not escaped and so your query string is mangled. Until this is fixed you should use something like

1
  observe_field 'some_field', :with => '"q="+encodeURIComponent(value)'

[1]The canonical function is actually remote_function, everyone else just uses its output. For example form_remote_tag is basically just a normal form whose onSubmit is the output of remote_function. As far as the docs go though, all the goodies are under link_to_remote.

[2]Prototype is smart enough to know that if you pass an object as the parameters option it should call Object.toQueryString on it, so for example parameters: {a: 2+2, b: “hello”} would do the right thing. The reason forgery protection makes a difference is that rails assumes that your :with option evaluates to a query string and appends to that authenticity_token=xxx.

When Aptana Messes With Your Gems

Recently a fair few people on rubyonrails-talk have been getting strange error messages about config.time_zone or referring to funny versions of gems (like rails 2.0.2.9216). This rather suggests that they were using edge versions of rails (since config.time_zone is an edge thing, and rails 2.0.2.9216 means ‘the edge gem from revision 9216). These edge gems come from gems.rubyonrails.org. But why were so many people suddenly doing this, all at the same time ?

It turns out that in its infinite wisdom Aptana silently adds gems.rubyonrails.org to your gem sources. If you remove gems.rubyonrails.org, Aptana will put it back. Don’t ask me why, it’s madness as far as I can tell. So you gem edge rails gems installed, thus the rails command now refers to edge rails and generates you an envionment.rb suitable for edge rails (including the config.time_zone line). The gem version specified in environment.rb is still 2.0.2 though, so your app loads 2.0.2 which doesn’t know about the edge features. Failure.

So, to fix this: * Uninstall all the edge gems (the ones with versions like 2.0.2.9216 (the last number may vary) * Remove gems.rubyonrails.org from your gem sources: gem sources -r http://gems.rubyonrails.org * (Optional) get rid of Aptana, since it will keep adding gems.rubyonrails.org (or wait for the version that fixes this)

You might also want to recreate your app as the environment.rb that edge rails created for you will not work with rails 2.0.2

Creating Multiple Associations With the Same Table

Rails makes setting up your associations dead easy and very concise. There’s a whole bunch of options that can be set however as long as you are following Rails’ conventions you very rarely need to use them. If you’re new to rails you might not even know they exists and get away with it 99% of the time. One case where you do is when you’ve got more than one association pointing at the same table.

If you wrote an application tracking sales between individuals (lets call it railsbay) you might have a table called sales and in that table you’d need to track the buyer and the seller. Both buyer and seller should be objects from the users table, and we’ve got two columns that refer to them: buyer_id and seller_id. Before we tackle what we actually want to do, lets take a step back. This would all be really easy if there was only one user associated with a sale. So lets take a look at what happens inside when we write

1
2
3
class Sale < ActiveRecord::Base
  belongs_to :user
end

When you do this [1], ActiveRecord derives the class name by taking :user and camelizing it to get User, the assumed class name (if this was a has_many, it would also singularize, so Users -> User). If you specify a class_name option, that takes priority. The foreign key (the database column containing the id of the associated user, in this case user_id) is derived by taking the name (user) and adding _id [1]. It could be overridden with :foreign_key. The first parameter (:user) is the name of association, so in particular it is the name of the method you call to get the value of the association. All this means is that we could also have written

1
2
3
class Sale < ActiveRecord::Base
  belongs_to :user, :class_name => 'User', :foreign_key => 'user_id'
end

It’s rather verbose, so we don’t. If you didn’t like your users (that’s not really a good idea) you can say

1
2
3
class Sale < ActiveRecord::Base
  belongs_to :nasty_user, :class_name => 'User', :foreign_key => 'user_id'
end

and now you need to access the association as sale.nasty_user instead of sale.user.

We can now return to the original problem. We’ve got 2 users to associate, a buyer and a seller, so the associations might as well use those names. Our foreign keys are buyer_id and seller_id, and in both cases the association will refer to a User, so we need :class_name => ‘User’

1
2
3
4
class Sale < ActiveRecord::Base
  belongs_to :buyer, :class_name => 'User', :foreign_key => 'buyer_id'
  belongs_to :seller, :class_name => 'User', :foreign_key => 'seller_id'
end

You don’t actually need the foreign key in this case (since the default is association name + _id (from rails 2.0) so this simplifies down to

1
2
3
4
class Sale < ActiveRecord::Base
  belongs_to :buyer, :class_name => 'User'
  belongs_to :seller, :class_name => 'User'
end

Easy! Now we just need to do the other side of the association. A user will have many sales as a seller and many sales as a buyer, we’ll call the associations sales and purchases. Like before, the association name determines the method you call to get/set an association. ActiveRecord assumes the defaults in much the same way, so from

1
2
3
class User < ActiveRecord::Base
  has_many :sales
end

ActiveRecord infers that the class is Sale (overridable by the :class_name option). The foreign key inference is different. Since we’re talking creating assocations on the User class, ActiveRecord assumes that database columns pointing at this table are called user_id (underscorize the class name and add id [3]). So the above is equivalent to

1
2
3
class User < ActiveRecord::Base
  has_many :sales, :class_name => 'Sale', :foreign_key => 'user_id'
end

Our associations are called sales and purchases, both refer to things of class Sale and the foreign keys are buyer_id and seller_id, so our User class should look like

1
2
3
4
class User < ActiveRecord::Base
  has_many :purchases, :class_name => 'Sale', :foreign_key => 'buyer_id'
  has_many :sales, :class_name => 'Sale', :foreign_key => 'seller_id'
end

If you want you can drop the :class_name option from the sales association, it’s not needed. There we go, you are now an association master! The same ideas apply in the other cases where the defaults aren’t write. Always make the association name unique (try and come up with a good name, it will really help the readability of your code), and then set foreign_key and class_name options appropriately. I haven’t talked about has_many :through at all, but that’s because Josh Susser already has a lovely writeup of that over here.

[1]This is a slight lie. ActiveRecord does very little when you call belongs_to, has_many etc… all these things are computed when they are needed. That distinction isn’t really important here.

[2]ActiveRecord use to base this on the :class_name option, ie the default key was (basically) the lowercased class name followed by _id. This changed in rails 2.0

[3]There’s a bit more to this in cases where your models are in modules and so on. The gory details are in Inflector#foreign_key