Unobtrusive, yet explicit

Posted 8 days back at the { buckblogs :here } - Home

A few weeks ago I started a new side project (a string-figure catalog, not yet ready for an audience, sadly), and I figured it would be a good opportunity to dabble in the new goodies in Rails 3. It’s been a fun experience, for the most part, but I’ll save my “wins and fails” for a separate post.

For now, I want to focus on one particular frustration: Unobtrusive Javascript (UJS). In any project of even moderate complexity, I’ve found that Javascript plays a role, and in Rails 2 the primary way to play that game was by inlining your Javascript. (This is where you put Javascript directly into your tags, for instance in “onchange” or “onclick” handlers.)

Apparently this is a Bad Thing, although the only arguments I’ve found against inline Javascript sound suspiciously like “purity for purity’s sake”. At any rate, Rails 3 is embracing RJS, and you’ll find that helper methods like “link_to_function” don’t even exist in Rails 3.

This raises the question: what do you do instead? Well, you have to use UJS. Only, UJS in Rails isn’t super mature yet; there’s a lot of manual labor involved simply trying to work around the absence of “link_to_function”.

So, I set to work. Initially, I tried to copy what rails.js was doing (for Ajax operations, etc.): I installed a handler, and examined the triggering element to see what operations match:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
document.observe("dom:loaded", function() {
  $(document.body).observe("click", function(event) {
    var element = event.findElement("a[data-toggle]");
    if(element) {
      var action = element.readAttribute("data-toggle");
      element.hide();
      element.next().show();
      event.stop();
    } else {
      var element = event.element();
      if(!element.readAttribute("data-tab")) element = element.up("a[data-tab]")
      if(element) {
        selectTab(element.readAttribute("data-tab"));
      }
    }
  });
});

I quickly realized that this does not scale, for two reasons. The first is that you quickly wind up with a massive branch statement inside each of your observer functions, with finicky conditions that (hopefully) map to actual elements in your views. The second is that the relationship between your markup and your Javascript is tenuous at best; even coming back to my code just a few days later, I found it was challenging to discover what code was executed when a link was clicked.

This is, I believe, one of the greatest strengths of inline Javascript: the relationship between markup and code is immediately obvious, and it requires very little hunting to follow the path of execution from the inception of an event.

So I went looking at what other options exist. Low Pro, an extension to Prototype for aiding with UJS, looked promising; I liked how each behavior was registered separately, which seemed like it would give a stronger relationship between markup and execution. However, Low Pro uses CSS selectors to identify which markup gets associated with which callbacks, and while this sounds like it ought to be a great idea, it falls down for one really big reason: CSS selectors depend on styling attributes (classes and ids), and trying to tie functionality to those means you are still left staring at markup and wondering where all the events go. Sometimes a class is stylistic, and sometimes it is logical, and there is not generally any clear way to determine which is which.

Now, you could resort to naming conventions: if a class name is prefixed with “behavior-” (or similar), then it refers to a behavior that is defined in Javascript. That’s closer to what I was looking for, so I played with that.

But what I soon discovered was that you wind up with a bunch of CSS classes that are not used for styling at all, because they specifically refer to dynamic behaviors. What I really wanted was an altogether different attribute for specifying behaviors, like what “onchange” and “onclick” gave me before. Only I had to beware upsetting the Manifold Avatars of UJS Purity by embedding actual Javascript.

What I finally ended up doing (I’ll say I “stumbled on it”, rather than “invented it”, since I’m positive it’s been done before) was defining a “data-behaviors” attribute on every element that needed one:


<%= link_to "Add an alias", "#", "data-behaviors" => "add-alias" %>

Then, in my Javascript driver, I registered callbacks for those named behaviors:

1
2
3
4
5
document.observe("dom:loaded", function() {
  Behaviors.add("click", "add-alias", function(element) {
    // ...
  });
});

The result is UJS that clearly reveals the relationship between the markup and the code; you can easily search for all elements in the views that behave like “add-alias”, for instance, and given a behavior name (like “add-alias”), you can quickly find the code that gets executed for it. Elements can have multiple behaviors, too: just give a comma-delimited list of behavior names in the “data-behaviors” attribute.

It’s not perfect, though: the current implementation doesn’t deal well with elements that want to behave like X on “click”, but Y on “change”. That’s not a scenario I’ve needed to deal with yet, though, so I’m sure when (if?) it comes up, a solution can be found. In the meantime, I’m quite pleased with this. It “clicks”, whereas other UJS solutions just felt obscure and heavy-weight.

Below is the code for behaviors.js. Please feel free to fork the gist on Github and hack away; I’m sure it can be improved upon in lots of ways.

Enjoy!

Dead simple JavaScript Unit Testing in Rails

Posted 3 months back at Dr Nic

Skills Matter : Rails Underground 2009: on Dead simple JavaScript Testing

Formats: Video/Screencast (410 Mb, torrent) | Video only (vimeo)

Start downloading the torrent now, read this article 37 times and the video might be ready to watch.

Writing tests are great for helping you design and think out your code, and the bonus is you end up with a test suite to aide in fighting against regressions. Why? Its embarrassing when your JavaScript doesnt work in production.

But how do you get started with testing JavaScript? How do you make it easy? I mean, so easy that youd feel stupid to not write tests?

And how do you know if your designer/HTML-chopper has broken your JavaScript? How do you find out if JavaScript is broken in CI builds? And what is the appropriate punishment for designers who break JavaScript?

Finally, it is now uber easy to get started: the blue-ridge plugin for Rails. (I previously discussed it near the bottom)

To spread the word, I travelled to London, England for the Rails Underground conference a few months ago. The presentation is now online (recorded and published by SkillsMatter).

I was also recording the screen during the presentation and weve composited the two together (a la confreaks) and its available via BitTorrent. If you can seed for the next few days, that would be greatly appreciated too.

The talk is 45 minutes and questions are 6 mins. (I sadly dont repeat into the microphone some of the questions because the room acoustics were good and everyone could hear everyone elses questions. Sorry.)

Go get it now.

Why Blue Ridge?

This recording was done in July 2009, a few months ago. Is Blue Ridge still the bees-knees? I think so. It has issues, edge cases and bugs, but I dont think there is a similar nor better Rails extension that includes (out of the box) a headless test runner, a bundle of test libraries (Screw.Unit, Smoke, etc), Rails generators, and automated discovery of the designer broke our JavaScript! lynch-mobbing (see my branch below).

These are the things I want. If theres a better testing environment (say on HTMLUnit instead of env.js), then I think the killer packaging is to bundle it all up, with the features above, so it is drop-in, dead simple to use.

My history with JavaScript testing

In the introduction I talk about my life with JavaScript and testing. Here is the extended summary if its interesting to you at all:

  • 2005:
    • ASP.NET + Ajax == crapola
    • Rails promo: easier to do Ajax than not to
    • Inline JavaScript helpers
  • 2006:
    • RJS to generate JavaScript
  • 2007:
    • JavaScript only in its own files
    • Unobtrusive JavaScript
    • Got myself into terrible mess with MyConfPlan
    • How to test JavaScript?
  • 2008:
    • Figured out how to test it
    • Write a PeepCode but never published it
    • Wrote newjs and jsunittest
  • 2009:

Miscellaneous

I mention a couple of miscellaneous things. Heres a summary.

My fork of blue-ridge has the feature to render sample HTML from your templates. It wasnt accepted into the primary blue-ridge library because it was rspec only. Perhaps someone can make it work for test/unit etc.

I alias the script/generate command:

alias gen="script/generate"

Im extending TextMate with Ciarn Walshs ProjectPlus plug-in (source). Its sweet.

Thanks

Thanks to Mark Coleman for organising Rails Underground, inviting me over, and having the sessions recorded. And to SkillsMatter for recording and publishing the raw footage.

Thanks to Bo Jeanes for helping to get Final Cut Pro to mash the screencast and the SkillsMatter video into one video.

Thanks to Jack Chen for hacking some code to push the video up to s3 (when Transmit and BaconDrop were failing me)

Related posts:

  1. First look at rails 3.0.pre This article is out of date in some aspects....
  2. Rails themes can remember things I was getting annoyed at having to remember all the...
  3. Install any HTML theme/template into your Rails app Have you ever even bothered to Google for rails...

Simple AJAX and Inline Javascript With Rails

Posted 6 months back at Jamie van Dyke

Ive noticed a lot of people asking questions about ajax, rjs, scriptaculous and other whizzy cool effects lately. So I thought Id document a couple here so theres less confusion about some of the simpler effects. Ive included an element toggle on a hyper link, a simple...

And the Pending Tests They Shall Pass!

Posted 8 months back at Katz Got Your Tongue?

While weve been working on ActionController::Base, there were a few tests for fixes in Rails 2.3 that were mostly hacked in and we were waiting for a cleaner underlying implementation to build them on top of. Today, we finally got all the pending tests passing!

There were basically two categories of pending tests:

Layout Selection

Rails 2 had a feature called :exempt_from_layout, which allowed users to specify that a particular kind of layout (defaulting to RJS) should be exempt from layout. The reason for this feature was that people were noticing that their RJS layouts were being wrapped in their HTML layouts.

When I first saw this feature, I had a simple question: Why were RJS layouts being wrapped in HTML templates in the first place? And also, if you want an RJS layout (application.js.erb), why shouldnt you be allowed to.

As it turns out, the reason for this was that any number of parts of Rails render templates, and the layout lookup is completely separate. So when it came time to look up a layout, Rails didnt realize that it had just rendered an RJS template (as far as it was concerned, since the available formats allow for HTML and JS, either would do).

The solution: Have template and layout lookup go through a deterministic process, and limit layout lookup to the mime type for the template that was actually rendered (not templates that might have been rendered).

The upshot: exempt_from_layout isnt needed. If you dont want a layout around your RJS templates, dont make an application.js.*. If you do, do. Rails will do the right thing.

An aside: the hardest part of Rails layouts involves when to raise an exception for a missing layout. Since Rails allows layouts to be missing in certain circumstances, making sure an exception is raised in other circumstances is quite tricky. In master, we raise an exception if you explicitly provided a layout (layout "foo"), and none exist for any MIME type. Implicit layouts or explicit layouts that exist for another MIME are permitted to be absent. The exception to that rule is render :layout => true, which converts an implicit layout to a required, explicit one.

RJS and HTML

Rails 2 has a bunch of hardcoded rules that allow RJS templates to render HTML. This allows page[:foo].replace_html :partial => "some_partial" to render some_partial.html.erb.

Effectively, when you got into an RJS template, the acceptable formats list was hardcoded to [:html]. Again, this blocks the use of RJS partials, and if you supply only an RJS partial, it will not be used (a missing template error would be raised).

When we thought about it, we realized that what is desired is to look for templates matching the mime type of the current template first, and if such a template could not be found, to allow templates matching any mime (with HTML leading the list). If youre in an RJS template and you call render :partial => "foo", and only a foo.xml.erb exists, we can assume that you mean to render that template.

This handles all of the cases supported by Rails 2, with one small change. If you have both a js and html template, the js template will win inside of RJS. If you didnt want the js template to win, why did you create it?

That rule now applies to any template. Partials matching the existing mime will be rendered if they exist, but any other mime will work fine as a fallback. So no special rules required for RJS anymore. The same rules apply for other templates (:file etc.) rendered from the context of another template.

One last thing. If you do page[:customer].replace_html :partial => "world", an html template will be required. When we noticed that we could separate out the rules for replace_html from other partials, we realized that we could hardcode more restrictive rules for that specific API than the general API.

And with that, the remaining pending tests pass. I really enjoy being able to solve problems that required hacks in the past by reorganizing the code to produce conceptually nicer solutions.

<script type="text/javascript"> addthis_url = 'http%3A%2F%2Fyehudakatz.com%2F2009%2F06%2F18%2Fand-the-pending-tests-they-shall-pass%2F'; addthis_title = 'And+the+Pending+Tests+%26%238212%3B+They+Shall+Pass%21'; addthis_pub = ''; </script><script type="text/javascript" src="http://s7.addthis.com/js/addthis_widget.php?v=12"></script>

And the Pending Tests They Shall Pass!

Posted 8 months back at Katz Got Your Tongue?

While weve been working on ActionController::Base, there were a few tests for fixes in Rails 2.3 that were mostly hacked in and we were waiting for a cleaner underlying implementation to build them on top of. Today, we finally got all the pending tests passing!

There were basically two categories of pending tests:

Layout Selection

Rails 2 had a feature called :exempt_from_layout, which allowed users to specify that a particular kind of layout (defaulting to RJS) should be exempt from layout. The reason for this feature was that people were noticing that their RJS layouts were being wrapped in their HTML layouts.

When I first saw this feature, I had a simple question: Why were RJS layouts being wrapped in HTML templates in the first place? And also, if you want an RJS layout (application.js.erb), why shouldnt you be allowed to.

As it turns out, the reason for this was that any number of parts of Rails render templates, and the layout lookup is completely separate. So when it came time to look up a layout, Rails didnt realize that it had just rendered an RJS template (as far as it was concerned, since the available formats allow for HTML and JS, either would do).

The solution: Have template and layout lookup go through a deterministic process, and limit layout lookup to the mime type for the template that was actually rendered (not templates that might have been rendered).

The upshot: exempt_from_layout isnt needed. If you dont want a layout around your RJS templates, dont make an application.js.*. If you do, do. Rails will do the right thing.

An aside: the hardest part of Rails layouts involves when to raise an exception for a missing layout. Since Rails allows layouts to be missing in certain circumstances, making sure an exception is raised in other circumstances is quite tricky. In master, we raise an exception if you explicitly provided a layout (layout "foo"), and none exist for any MIME type. Implicit layouts or explicit layouts that exist for another MIME are permitted to be absent. The exception to that rule is render :layout => true, which converts an implicit layout to a required, explicit one.

RJS and HTML

Rails 2 has a bunch of hardcoded rules that allow RJS templates to render HTML. This allows page[:foo].replace_html :partial => "some_partial" to render some_partial.html.erb.

Effectively, when you got into an RJS template, the acceptable formats list was hardcoded to [:html]. Again, this blocks the use of RJS partials, and if you supply only an RJS partial, it will not be used (a missing template error would be raised).

When we thought about it, we realized that what is desired is to look for templates matching the mime type of the current template first, and if such a template could not be found, to allow templates matching any mime (with HTML leading the list). If youre in an RJS template and you call render :partial => "foo", and only a foo.xml.erb exists, we can assume that you mean to render that template.

This handles all of the cases supported by Rails 2, with one small change. If you have both a js and html template, the js template will win inside of RJS. If you didnt want the js template to win, why did you create it?

That rule now applies to any template. Partials matching the existing mime will be rendered if they exist, but any other mime will work fine as a fallback. So no special rules required for RJS anymore. The same rules apply for other templates (:file etc.) rendered from the context of another template.

One last thing. If you do page[:customer].replace_html :partial => "world", an html template will be required. When we noticed that we could separate out the rules for replace_html from other partials, we realized that we could hardcode more restrictive rules for that specific API than the general API.

And with that, the remaining pending tests pass. I really enjoy being able to solve problems that required hacks in the past by reorganizing the code to produce conceptually nicer solutions.

<script type="text/javascript"> addthis_url = 'http%3A%2F%2Fyehudakatz.com%2F2009%2F06%2F18%2Fand-the-pending-tests-they-shall-pass%2F'; addthis_title = 'And+the+Pending+Tests+%26%238212%3B+They+Shall+Pass%21'; addthis_pub = ''; </script><script type="text/javascript" src="http://s7.addthis.com/js/addthis_widget.php?v=12"></script>

Calculating Line Item Extensions

Posted 9 months back at Sterling Rose Design Blog

In my project, I have orders, and each order can have an unlimited number of line_items. Line_items are created by the user clicking on a button, which appends (via RJS) a new row to the line_items tabled form. So far, so good.

But I needed the extended price (quantity * price_per) of each line_item to be calculated every time the user tabbed out or clicked away from the price_per field. Further, I needed the subtotal, tax, total, and balance fields to be automatically re-calculated.

I messed around with it for several hours, trying Javascript, Prototype, and even jQuery, before I finally settled on a Prototype approach that worked. The real struggle was that using Rails 2.3s nested forms functionality meant that each line_item would have an index key embedded in the middle of the text fields name and id, and I could not come up with a good way to extract it, to pass it to the Javascript function.

Luckily, the numbering appears to be a 0-based index, so I cheated and used an incrementer.

I know its not a perfect solution (its obtrusive, its probably much more verbose than it needs to be) but it functions.

So, on to the code. Obviously, Ive snipped out a lot of stuff that doesnt have anything to do with this example.

/controllers/orders_controller.rb

def new
  @order = Order.new
  @order.line_items.build
  @order.zero_set_amounts
  @colors = Color.unremoved
end

def edit
  @order = Order.new
  @colors = Color.unremoved
end

/models/order.rb

def zero_set_amounts
  self.subtotal = 0
  self.tax = 0
  self.shipping = 0
  self.artwork = 0
  self.setup = 0
  self.printing_charge = 0
  self.misc_charge = 0
  self.total = 0
  self.balance = 0
  self.reorder ||= false
end

/views/layouts/application.html.erb

<head>
  <%= javascript_include_tag :defaults, 'main' %>
</head>

/views/orders/new.html.erb and /views/orders/edit.html.erb

<% form_for(@order) do |f| %>
  <%= render :partial => "form", :locals => {:f => f} %>
<% end %>

/views/orders/_form.html.erb

<div id="order_line_items">
  <%= render :partial => 'line_items', :locals => {:f => f, :order => @order} %>
  <div id="order_summary_fields">
    <div class="left"><%= f.label :subtotal %></div>
    <div class="right"><%= text_field_tag :subtotal, 0, :size => 10, 
      :disabled => true %></div>
    <div class="left"><%= f.label :artwork %></div>
    <div class="right"><%= f.text_field :artwork, :size => 10, :onblur => 
      'recalculateTotals();' %></div>
    <div class="left"><%= f.label :setup %></div>
    <div class="right"><%= f.text_field :setup, :size => 10, :onblur => 
      'recalculateTotals();' %></div>
    <div class="left"><%= f.label :printing_charge, "Printing" %></div>
    <div class="right"><%= f.text_field :printing_charge, :size => 10, :onblur => 
      'recalculateTotals();' %></div>
    <div class="left"><%= f.label :misc_charge, "Miscellaneous" %></div>
    <div class="right"><%= f.text_field :misc_charge, :size => 10, :onblur => 
      'recalculateTotals();' %></div>
    <div class="left"><%= f.label :shipping %></div>
    <div class="right"><%= f.text_field :shipping, :size => 10, :onblur => 
      'recalculateTotals();' %></div>
    <div class="left"><%= f.label :tax %></div>
    <div class="right"><%= text_field_tag :tax, 0, :size => 10, :disabled => true %></div>
    <div class="left"><strong><%= f.label :total %></strong></div>
    <div class="right"><%= text_field_tag :total, 0, :size => 10, 
      :disabled => true %></div>
    <div class="left"><%= f.label :balance %></div>
    <div class="right"><%= text_field_tag :balance, 0, :size => 10, 
      :disabled => true %></div>
    <%= render :partial => "shared/create_or_update", :locals => {:f => f} %>
    <%= f.hidden_field :subtotal %>
    <%= f.hidden_field :tax %>
    <%= f.hidden_field :total %>
    <%= f.hidden_field :balance%>
    <%= f.hidden_field :reorder %>
  </div>  
</div>

/views/order/_line_items.html.erb

<%= button_to_remote 'Add a New Line Item', {:url => new_line_item_path, :method => :get} %>
...
<% i = 0 %>
<% f.fields_for :line_items do |line_items_forms| %>
  <tr>
    <td><%= line_items_forms.text_field :quantity, :size => 5 %></td>
    <td><%= line_items_forms.text_field :description, :size => 30 %></td>
    <td><%= line_items_forms.select :color_id, @colors.map {|c| [c.name, c.id]} %></td>
    <td><%= line_items_forms.text_field :price_per, :size => 10, :onblur => 
      order.new_record? ? 'updateExtendedPrice("order_line_items_attributes_0_quantity", 
      this.id, "order_line_items_attributes_0_price_extended");' : 
      "updateExtendedPrice('order_line_items_attributes_#{i}_quantity', this.id, 
      'order_line_items_attributes_#{i}_price_extended');" %></td>
    <td><%= line_items_forms.text_field :price_extended, :size => 10, :class => 
      'fluffy_bunny' %></td>
   <% i += 1 %>
  </tr>
<% end %>

Yeah, I called my class fluffy_bunny. Its not a class name I was likely to use anywhere else, and fluffy bunnies are cute. :)
/controllers/line_items_controller.rb

def new    
  begin
    @order = Order.find(params[:order_id])
    @line_item = @order.line_items.build
  rescue ActiveRecord::RecordNotFound
    @line_item = LineItem.new
  ensure
    @item_index = @order.nil? ? Time.now.to_i : (@order.line_items.length - 1)
  end

  respond_to do |format|
    format.html
    format.js do        
      @colors = Color.unremoved.collect {|c| [c.name, c.id]}
      @line_id = "order_line_items_attributes_#{@item_index}"
      @line_name = "order[line_items_attributes][#{@item_index}]"
    end
  end
end

/views/line_items/new.js.erb

$('line_item_table').insert(<%=js render(:partial => "new_line_item") %>, 'bottom');

/views/line_items/_new_line_item.html.erb

<tr> 
  <td><%= text_field_tag "#{@line_id}_quantity", '', :name => "#{@line_name}[quantity]", 
    :size => 5 -%></td> 
  <td><%= text_field_tag "#{@line_id}_description", '', :name => 
    "#{@line_name}[description]", :size => 30 -%></td> 
  <td><%= select_tag "#{@line_id}_color_id", options_for_select(@colors), :name => 
    "#{@line_name}[color_id]" -%></td>
  <td><%= text_field_tag "#{@line_id}_price_per", '', :name => 
    "#{@line_name}[price_per]", :size => 10, :onblur => 
    "updateExtendedPrice('order_line_items_attributes_#{@item_index}_quantity', this.id, 
    'order_line_items_attributes_#{@item_index}_price_extended');" -%></td>
  <td><%= text_field_tag "#{@line_id}_price_extended", '', :name => 
    "#{@line_name}[price_extended]", :size => 10, :class => 'fluffy_bunny' -%></td>
</tr>

/public/javascripts/main.js

function updateExtendedPrice(x,y,z) {
  $(z).value=parseFloat($(x).value) * parseFloat($(y).value);
  updateSubtotal();
  recalculateTotals();
}

function updateSubtotal() {
  var subtotal=0;
  var li;
  var line_item_extensions = $$('.fluffy_bunny');
  line_item_extensions.each(function(li) {subtotal += parseFloat(li.value);})
  $('order_subtotal').value=subtotal;
  $('subtotal').value=subtotal;
}

function updateTax() {
  /* Tax rate is 7% */
  var tax = ((parseFloat($('order_subtotal').value) + 
    parseFloat($('order_artwork').value) + parseFloat($('order_setup').value) + 
    parseFloat($('order_shipping').value) + parseFloat($('order_printing_charge').value) 
    + parseFloat($('order_misc_charge').value)) * 0.07).toFixed(2);
  $('order_tax').value=tax;
  $('tax').value=tax;
}

function updateTotal() {
  var total = (parseFloat($('order_subtotal').value) + 
  parseFloat($('order_artwork').value) + parseFloat($('order_setup').value) + 
  parseFloat($('order_tax').value) + parseFloat($('order_shipping').value) + 
  parseFloat($('order_printing_charge').value) + 
    parseFloat($('order_misc_charge').value)).toFixed(2);
  $('order_total').value=total;
  $('total').value=total;
}

function updateBalance() {
  balance = $('order_total').value;
  $('order_balance').value = balance;
  $('balance').value = balance;
}

function recalculateTotals() {
  updateTax();
  updateTotal();
  updateBalance();
}

Dear Railsists, Please Dont be Obtrusive

Posted 9 months back at Ruby, Rails, Web2.0

obtrusive_or_not.png Update: thanks to Jon Wood aka jellybob, a prototype demonstration has been added, which is even better than my original jQuery btw as it degrades gracefully. Check it out in the prototype-unobtrusive directory.

I am guessing 9 out of 10 of you reading the title is prepared for yet-another Rails drama on some obtrusive community members, and because everyone is tired of Rails dramas, I am risking that some of you wont care to read the article - but I couldnt resist :-). Actually Id like to talk about usage of (un)obtrusive Javascript - why is it a bad idea to be obtrusive, especially given that (as you will learn from the article) writing unobtrusive Javascript is not harder, and you get the warm, fuzzy feeling of writing nice and clean code!

The Drill

To demonstrate the differences, Ill lead you through the creation of a quick AJAXy shout wall both the default/standard (and obtrusive) way, then do the same with unobtrusive Javascript to show you that contrary to the popular belief, you dont need to memorize the Tome of Javascript Black Magick Tricks by heart, use obscure libraries or special coding techniques to achieve clean, unobtrusive code. The shout wall is simply a form for posting a new message, and a list of messages below it, like so:

shout_wall.png

(You can check out the code used in this post from its github repository).

The Standard Way

Note: If youd like to follow along, please use the provided pastie links - do not try to cut & paste multiple lines from the page (single lines are OK), as it will be b0rk3d.

  1. Creating a new Rails application
    1. rails obtrusive-shout-wall
  2. Get into the Rails dir
    1. cd obtrusive-shout-wall
  3. Generate the resource message
    1. script/generate resource message
  4. Add this the following to the generated migration (some_timestamp_create_messages (Get it from pastie):
    1. t.string :author
    2. t.text :message
  5. Run the migrations:
    1. rake db:migrate
  6. Because we want to view the messages in reverse order (newest one first), we add a default scope to the Message model (in message.rb):
    1. default_scope :order => created_at DESC
  7. Create the application layout - create a new file in app/views/layouts called application.html.erb, and fill it with the following content (Get it from pastie):
    1. <html>
    2. <head>
    3. <%= stylesheet_link_tag "application" %>
    4. <%= javascript_include_tag :defaults %>
    5. </head>
    6. <body>
    7. <%= yield %>
    8. </body>
    9. </html>
  8. Create a file application.css and drop it into public/stylesheets. Add the following content (Get it from pastie):
    1. body {
    2. background-color:#FFFFFF;
    3. color:#333333;
    4. font-family:"Lucida Grande",verdana,arial,helvetica,sans-serif;
    5. margin:0 auto;
    6. padding:0;
    7. text-align:center;
    8. width:960px;
    9. }
    10. #messages {
    11. text-align: left;
    12. margin-left: 80px;
    13. margin-top: 50px;
    14. }
    15. #message-form {
    16. text-align: left;
    17. }
    18. #message-form dl {
    19. margin:10px 0 0 80px;
    20. }
    21. #message-form dd {
    22. color:#666666;
    23. font-size:11px;
    24. line-height:24px;
    25. margin:0 0 5px 80px;
    26. }
    27. #message-form dt {
    28. float:left;
    29. font-size:14px;
    30. line-height:24px;
    31. width:80px;
    32. text-align: left;
    33. }
    34. #author {
    35. margin-right: 640px;
    36. }
    37. #message {
    38. width: 600px;
    39. height: 200px;
    40. margin-right: 194px;
    41. }
    42. .message {
    43. margin-bottom: 20px;
    44. }
    45. .first_row {
    46. padding-bottom: 10px;
    47. }
    48. .message-meta {
    49. font-size: 12px;
    50. }
    51. .author {
    52. color: #FF5050;
    53. font-weight: bold;
    54. }
    55. .new-message-label {
    56. text-align: left;
    57. padding-top: 30px;
    58. margin-left: 80px;
    59. }
    60. #submit-button {
    61. float : right;
    62. margin-right: 195px;
    63. margin-top: 10px;
    64. }
  9. Create a new action, index in MessagesController (Get it from pastie):
    1. def index
    2. @messages = Message.all
    3. end
  10. This goes into app/views/messages/index.html.erb (Get it from pastie):
    1. <h3 class="new-message-label">Enter new message!</h3>
    2. <% remote_form_for :message, :html => {:id => "message-form"} do |form| %>
    3. <dl>
    4. <dt>Author:</dt>
    5. <dd><%= text_field_tag author %></dd>
    6. <dt>Message:</dt>
    7. <dd><%= text_area_tag message %></dd>
    8. </dl>
    9. <%= submit_tag "Submit!", :id => "submit-button"%>
    10. <% end %>
    11. <div id="messages">
    12. <%= render :partial => message, :collection => @messages %>
    13. </div>
    We are showing the form for the messages and list the already exiting messages below the list. Note that we are using the _remote_form_for_ Rails helper to create an AJAXy form. This is already obtrusive, since if you observe the generated HTML, you will see that the form has an onsubmit parameter with some horribly looking code attached to it.:

    Obtrusive helper.png

    Sure, you can go meh all the way, but slinging Javascript code all over the place is just as bad idea as writing inline CSS (or even worse, using HTML code for styling) or putting Rails code into views. It will work without any problems - but its just not the right way of doing things, especially if your code is going to hit a certain size.
  11. You probably noticed that we are rendering a message as a partial - so create a partial file app/views/messages/_message.html.erb with the following content (Get it from pastie):
    1. <div class="message" id="message-<%=message.id%>">
    2. <div class="message-meta">on
    3. <%= message.created_at.to_formatted_s(:long_ordinal) %>,
    4. <span class="author"><%= message.author %></span>
    5. said:
    6. </div>
    7. <div><%= message.message %></div>
    8. </div>
  12. We need a create action in MessagesController in order to process the form submission (Get it from pastie):
    1. def create
    2. @message = Message.create(:author => params[:author], :message => params[:message])
    3. end
  13. And obviously well need to render something to respond to the create action. Using the standard Rails way, RJS, we might come up with something like this (in app/views/messages/create.js.rjs - Get it from pastie):
    1. page.insert_html :top, "messages", :partial => message, :object => @message
    2. page.visual_effect :highlight, "message-#{@message.id}"
    Here we insert the messages partial, using the just created @message, and throw a splash of yellow fade into the mix for good measure. Easy peasy.
  14. We are done! Fire up script/server, hit localhost:3000/messages and voila!

The Good Way

Here I am presenting only the steps that are different from the above - i.e. if step 3 is skipped, use the one from above.

  1. Creating a new Rails application
    1. rails unobtrusive-shout-wall
  2. Get into the Rails dir
    1. cd unobtrusive-shout-wall
  3. Same as above
  4. Same as above
  5. Same as above
  6. Same as above
  7. Since we are going to use jQuery (unobtrusiveness is *not* a property of jQuery, you can be just as unobtrusive with Prorotype - but I switched to jQuery just before learning how, and now I am lazy to go back check out how in the prototype unobtrusive directory in the github repository), you have to download jQuery with some basic effects, as well as an AJAX form handling library (still from the directory unobtrusive-shout-wall - Get it from pastie):
    1. curl http://jqueryjs.googlecode.com/files/jquery-1.3.1.min.js > public/javascripts/jquery.js
    2. curl http://www.malsup.com/jquery/form/jquery.form.js?2.28 > public/javascripts/jquery-form.js
    3. curl http://view.jquery.com/tags/ui/latest/ui/effects.core.js > public/javascripts/effects.core.js
    4. curl http://view.jquery.com/tags/ui/latest/ui/effects.highlight.js > public/javascripts/effects.highlight.js
    and replace
    1. <%= javascript_include_tag :defaults %>
    with
    1. <%= javascript_include_tag jquery %>
    2. <%= javascript_include_tag jquery-form %>
    3. <%= javascript_include_tag application %>
    4. <%= javascript_include_tag effects.core %>
    5. <%= javascript_include_tag effects.highlight %>
    in the layout file.
  8. Same as above
  9. Same as above
  10. Same as above - just delete remote from the name of the helper, i.e. use a standard Rails view helper, form_for
  11. Same as above
  12. Since we are not relying on Rails to do the rendering for as via a template file, we return the html chunk that we will render from Javascipt. So your create action should look like (Get it from pastie):
    1. def create
    2. @message = Message.create(:author => params[:author], :message => params[:message])
    3. render :partial => message, :object => @message
    4. end
  13. Now comes the fundamentally different part - instead of using RJS to respond to the create action, we move all our code to application.js (Get if from pastie):
    1. $(document).ready(function() {
    2. $("#message-form").ajaxForm({success: handleNewMessage});
    3. function handleNewMessage(response, statusText) {
    4. $("#messages").prepend(response).effect("highlight", {}, 1500);
    5. }
    6. });
    I dont think so that this code is particularly more complicated or hard to understand that the RJS one. Everything is inside the ready() function, which means that its only run once the document is properly loaded. Then we declare that #message-form is an AJAX form, and that upon successful submission, the handleNewMessage() method should be called. And if that happens, we add the response (which is the return value of the create action) to the #messages div, just as we did in RJS. Then we apply the yellow fade! w00t!
  14. Same as above

(You can check out the code used in this post from its github repository).

Conclusion

As you can see, the only real difference between the obtrusive and non-obtrusive version is in the last 2 points (downloading and including the jQuery header files can be easily solved with Rails templates): instead of leaving the rendering part to Rails, we return the response as a string and dynamically insert it from jQuery. With about the same effort, we kept all the Javascript code in application.js, which is much cleaner this way (you can open up 1 file and check out all the JS/AJAX behavior in one place), especially after introducing a lot of Javascript functionality into your code - in other words, for the same amount of work we got something much better. Please try to keep this in mind when you are working with Javascript and Rails the next time - believe me, it can save you from a lot of pain!

RailsConf Speaker Interview: Mike Subelsky

Posted 10 months back at ChadFowler.com

Next up in my series of RailsConf speaker interviews is Mike Subelsky, co-founder of OtherInbox and avid SproutCore developer. (SproutCore was used, among other things, to develop Apples MobileMe UI and presents a Cocoa-like environment for developing Rich browser-based user interfaces.)

Mike will be presenting both a tutorial on SproutCore development and a session on cloud computing gotchas at RailsConf in a couple of weeks.

How did OtherInbox get started? Was this your first startup? What were the hurdles in moving from consulting to product development?

We wanted to solve our own email overload problemmany people receive heavy volumes of gray mail or bacn and increasing volumes of more important email like receipts, social networking notifications, confirmations, etc. The other founder of the company, Joshua Baer, had the idea to build a product that would elegantly organize and make sense of this kind of mail, something we would use ourselves that would also help the average person.

This was my first startup; I knew Josh from a project class we had taken together at Carnegie Mellon and was lucky that he invited me to be the cofounder. I had been dabbling and freelancing as a Rails developer for the previous 18 months, and had always been hacking on side projects for my whole career.

Coming from a consulting background, there have been two big hurdles:

  1. I didnt expect product development to have such a different tempo than consulting projects. You live with your code much longer (so you end up maintaining your own legacy code, and dont have the next greenfield to look forward to), you have to deal more directly with the consequences of mistakes (theres no one to bail you out), and the risks are higher (because you dont know when or if your labors will pay off). Thus there are many peaks and valleys of happiness, and its not always easy to tell if you are being successful.
  1. When time and resources are so constrained, the development team has to become disciplined without sacrificing too much agility. You cant rely on individual programming heroics to carry the day otherwise youre guaranteed to burn out and get sloppy. I havent completely overcome this hurdle yet, but Im reading everything about software development processes that I can get my hands on. Ive become a huge fan of Eric Ries lean startup blog Lessons Learned.

What makes SproutCore interesting and special to you?

SproutCore lets you build real client-server applications, delegating a lot of GUI work to the client that servers currently perform (like rendering HTML views or serving up cached HTML pages). The framework takes concepts that desktop developers have used for a long time and reimagines them for use in web applications. For example, key-value observation and bindings help you eliminate a lot of buggy, ugly glue code, saving a lot of time and aggravation).

Its special to me because I think this technology has the potential to dramatically change application development in general. The core team has a strong point of view about how Internet clients should be built, and how to make JavaScript and web browsers perform. Writing SproutCore code reminds me of the feeling I got the first time I picked up a book about Railsusing these tools is making me a better programmer.

Also, Ive never liked the feel of Flash, and I love how SproutCore uses only web-native technologies to do amazing things in the browser. PaperCube and MobileMe are examples of SproutCore apps that feel like desktop apps, but dont require any plugins whatsoever.

Are SproutCore and Rails particularly well suited to one another? If so, how?

I think soyou can point a SproutCore client at any kind of server you want, but I think theres probably a sweet spot for these two frameworks. Rails makes it easy to write a robust, easily extensible REST API, which makes it really easy to write web clients. SproutCore makes it easy to write robust, easily extensible web clients.

In OtherInbox, our Rails app has to worry about doing is sending back JSON as quickly as possible. The SproutCore app handles most of the user interaction (except for things like the signin page, which were keeping server-based for now) and focuses on making the browser experience as fast and fluid as possible. We dont need to run as many web servers to keep up with our recent growth, because the servers arent bogged down generating HTML or RJS updates.

As a happy side-effect, since we built our app this way, someone has reverse engineered our JSON API to make an Android app.

Rails developers will also find many cultural similarities such as emphasis on unit testing and use of generators.

SproutCores model marks an extreme departure from the way we traditionally do JavaScript/AJAX applications on Rails. Do you think SproutCore or its patterns will catch on generally or is it bettered toward to a specific niche?

I think if youre building an app where you need to add just a little dynamic behavior here and there, SproutCore is too much. Toolkits like Prototype and jQuery are much more appropriate. But if youre making something like MobileMe, PaperCube, or OtherInbox, where the users expect desktop-like fluidity or where the client software cant always rely on the server for everything, SproutCore will be a big win.

One thing that could help SproutCore catch on generally will be its support for multiple platforms. The 1.0 release candidate includes preliminary mobile support, so we should eventually be able to take the same code base and build it for web browsers, iPhones, Androids, etc., without having to change much. I think that will really turn some heads.

SproutCores learning curve appears to be pretty steep. Whats the best way to get into it on day one?

Thats tough, because there arent yet a lot of blogs or books to guide you. One of my goals for 2009 is to start contributing more tutorials, because I was lucky to be mentored by Erich Ocean. My best advice would be to get some familiarity with Cocoa because that will illuminate the parts of desktop development that are really different from web development. If you understand key-value observation, key-value coding, and bindings, it will be much easier to learn SproutCore.

Also, the #sproutcore IRC channel and Google group are really friendly places to get guidance.

20 Rails Development No-No's

Posted 11 months back at ChadFowler.com

Yesterday, I asked Twitter:

Rails programmers: what's an example of one thing you find in other people's Rails 
code that you (almost) always consider to be wrong?

Quite a few people responded, and several asked if Id post the aggregated answers. Heres an attempt. I dont necessarily agree with everything here. Id love to hear more of these in the comments.

1. Code in Views

@greg_fu

@briandoll

@mbleigh

@briandolls response was particularly interesting:

extensive logic in views. If the erb would be ugly and harder to pull off in
haml, it's likely a code smell

This is something I always liked in Java about XMLC:http://www.enhydra.org/tech/xmlc/index.html. It was really hard to end up with a mess of co-mingling code and views, because you would have to work harder in XMLC to even make that possible. Amrita used to have that same characteristic, but Im not sure it still does (or if it is even maintained).

Ive been resistant to HAML but maybe its time to give it another look.

Also, as @mbleigh points out, any code in views is suspect. CSS should be in CSS files. Javascript should be in Javascript files.

2. Tabs instead of spaces and bad indentation

@Tricon

@fowlduck

@boboroshi

Enough said.

3. Bad or Missing Tests

@rickbradley

@merbist

@alancfrancis

@joshuabates

@mattsoutherden

@seancribbs

@seancribbs singles out auto-generated tests. Ill have to agree with him.

@alancfrancis says simply rspec. Certainly not a religious topic. Im sure if you could somehow see in the code which editor someone used, wed see a lot of TextMate, vi, and emacs in the responses.

4. Bad use of Polymorphic Associations and Single-Table Inheritance

@brendanbaldwin says Ive seen Polymorphic Associations and Single Table Inheritance abused to absurdity.

As have I. Ive also abused them to absurdity myself. The storage of a column called type which holds a class name is a pretty good indicator that something fishy is going on. Its fishy but not always bad. I think, though, that any time you use it you should ask yourself more than once if its the right solution. Databases dont do what they do best as well when you have lots of STI and polymorphic associations.

5. Cavalier Exception Handling

@technomancy

@mikesax

I once had to debug a problem with a commercial, Java-based portal product. The mail module was failing, saying it couldnt connect to the mail server. In fact, what was happening was my login credentials were incorrect. Guess what the offending JSP looked like (pseudo-code)?

try {
  // do everything here
} catch (Exception e) {
  out.println("Unable to connect to the mail server")    ;
}

Exceptions are one of the only places in Ruby where you rely on checking the classes of objects to do useful and interesting things. Never rescue Exception unless its at the end of a stanza in which youve already handled one or more other exception types.

UPDATE: from the comments, Phil didnt mean what I typed here:

Thats not actually what I meant. Never rescue Exception unless youre working with code that abuses the   
exception hierarchy. In normal circumstances you should be rescuing StandardError instead. If you rescue  
Exception, you wont even be able to ctrl-c your way out of your code(!) among other problems

More info on his weblog here

6. Accidental Local-variable Assignment

@technomancy

  class Potato
    attr_accessor :size
  end
  potato = Potato.new
  potato.instance_eval do
    size = 123
  end
  p potato.size
  # => nil

Rubys assignment to method-call syntax sugar only works when theres an explicit receiver (such as self.size = 123)

7. Code That Produces Ruby Warnings

@tenderlove

Always run with -w. Fix any warnings you see. A good place to see them is when running your applications comprehensive test suite.

8. Code that uses the features of Rails :)

@drawohara

@drawohara

@brianleroux

@joshuabates

RJS, render, nested named routes, and ERb are the targets of these criticisms.

9. Misuse of validates_uniqueness_of

@hedron

validates_uniqueness_of is a strange critter. Its so easy to use, that its tempting to slap it into your code and consider it good enough. But even the documentation for it warns you:

Using this validation method in conjunction with ActiveRecord::Base#save
does not guarantee the absence of duplicate record insertions, because
uniqueness checks on the application level are inherently prone to race
conditions.

10. Code in the Wrong Place

@mperham

@bcalloway

@mikesax

@joshuabates

Here are some rules of thumb:

  • Nothing that looks at all like SQL should go into a controller, view, or helper.
  • Try to avoid code that causes a database query (even through a shielded API) in views and helpers
  • Never generate HTML in a model
  • Not counting respond_to blocks, shoot for controller actions of 5 lines or less.

11. Configuration Files That Contain Their Own Environment Sections

@stonean

If you create your own configuration file and have to check RAILS_ENV, youre not using a built-in facility of Rails.

12. Too Much Code

@aaroncampos

OK, so this isnt really what he meant. But its a good thing to think about. If you find yourself writing a lot of code to do something simple, you might be missing something Rails (or a plugin) provides. Ive seen a whole lot of wheels re-invented inside models, controllers, and helpers. Part of Rails mastery is learning to ask yourself, Shouldnt somebody have already done this for me by now?

13. Messing Up $LOAD_PATH

@drnic

Seems like if you find yourself manipulating Rubys load path in your Rails code, youre doing something strange. If you dont know youre doing something strange, then youre probably doing something wrong.

14. Obfuscated Logic

@sneakin

@pelargir

As Dave Thomas likes to say Dont not use positive logic (or something like that).

Can someone tell me where the use of !!true crept into the Ruby vernacular? Sure, its syntactically valid to do:

    !!!!!!!!!!!!!!!!!!ok?
  

But why?

Its April 1st, so my mind wanders to the hypothesis that someone introduced this and started spreading it as an elaborate joke. If so, well played. Is there a good use of this idiom Im missing?

15. Seed Data in Migrations

@lifo

ActiveRecord migrations define a simple API/DSL for doing schema manipulation. But when they run, theyre just Ruby classes and methods executing within the Rails environment. So you can do anything you like in that context.

Sometimes people use migrations to load seed data. Pratik doesnt like this.

Im on the fence on this one. In small doses it doesnt strike me as a horrible practice. That being said, I dont tend to do it. Id be interested in hearing opinions.

16. Sending Data on GET Requests

@Adkron

I guess it depends on what youre doing with the parameters. Sometimes long parameter lists on GET requests are a sign that there are two actions worth of work happening in a single action. Or that youre doing something that belongs behind a POST or PUT. This one doesnt bother me as much as it does @Adkron.

17. Not Using Transactions

@sbfaulkner

    if foo.save && bar.save
  

if bar fails to save, foo doesnt. Oops. Ive seen this more than once in the wild.

18. Bad Use of Javascript

@leandroico

@RobotDeathSquad

Obtrusive Javascript and RJS are the themes here.

Whats the worst use of RJS youve seen? I love RJS. I may be one of the offenders. Doesnt feel like it (usually) though.

19. Long Methods

@mikesax

Smalltalk people, as a collective culture, seem to get this right. Kent Beck documents a simple pattern in his Smalltalk Best Practice Patterns called Compose Method. He says that one way to determine whether a new method is required is whether all of the code in a given method operates at the same level of abstraction. Thats a simple but powerful rule. Think about it next time youre coding and I think youll agree.

20. Chatty Comments

@mikesax

I agree with Mike on this one. I dont tend to write any comments, so I may be on the anti-comment side to a fault. My goal is always to write code that is clear enough that comments dont add anything. If you name everything well and keep your classes, modules, and methods short, youre already going to make comments redundant in a lot of cases.

Whenever I see a comment inside a method definition, I consider it a bad sign. In most cases, whenever you encounter a comment inside a method, its a marker for where code should be extracted into a new method.

If youre writing a framework, the rules are different. But most of the time as a Rails developer, you shouldnt be writing a framework.

Mephisto 0.8.2 released

Posted about 1 year back at Mephisto - Home

Mephisto 0.8.2 is now available on the download page!

Mephistos JavaScript is in much better shape, and most of the remaining tainted string errors should now be fixed. The default article and comment filter is now Textile (instead of raw HTML), and our gem management has been cleaned up.

Many thanks to the Mephisto contributors brought you this release: Chris Cummer, James McCarthy, Matthias Ldtke, Sean OBrien and Gustavo Sales (who wrote the first version of the theme homepage fix).

Thanks also go to Chris Cummer for investigating Mephisto multisite caching issues. For information on setting up multisite, please see Mika Tuupolas blog post and the thread on the MephistoBlog group. Alternatively, you can disable multisite support in config/initializers/custom.rb.

Were actively working on a simpler solution for multisite caching. In particular, were looking into writing a custom Rack module under Rails 2.3. If youre interested in helping, please join us on #mephisto. Contributors are always welcome!

A full list of patches appears after the jump.

Chris Cummer (2):

  • Fixes attempted to output tainted string error when rendering email address for mailto
  • Changed user login to send user to admin section on succesful login instead of the blog homepage since users have the ability to post to the blog

Eric Kidd (23):

  • Unbundle tzinfo gem
  • Fix theme controller bugs
  • Change default comment filter to Textile
  • Change default user article filter to Textile
  • Rename *.rhtml files to *.html.erb
  • Rename *.rxml files to *.xml.builder
  • Modernize rjs: admin/articles
  • Added some notes about fixing JavaScript
  • Begin updating to latest Prototype
  • JavaScript: Fix asset search
  • JavaScript: Fix authenticity_token problems
  • JavaScript: Rename admin/assets/*.js -> *.js.rjs
  • Remove RSpec StoryRunner files
  • Write login integration tests using Webrat
  • Add integration test for reset password
  • Upgrade to interim release of Webrat from github
  • Add version numbers to config.gem statements
  • Allow newer versions of these gems
  • Add TODO item for explaining how to make Unicode work
  • Merge branch master of git://github.com/technoweenie/mephisto
  • Merge branch master of git://github.com/mat/mephisto
  • Require test gems in development environment, not test
  • Fix display of theme homepage links

James McCarthy (2):

  • escaped link in _page.html.erb
  • Added some brackets

Matthias Ldtke (4):

  • Fixed typo.
  • Moved test gems from environment.rb to environments/test.rb.
  • Added missing config.gem ruby-debug to environments/test.rb.
  • Added info: install test gems before testing.

Sean OBrien (1):

  • missing tainted string in cache listing


1 2 3