scottraymondnet 2006. bottles and cans, clap your hands..

March 7th | SXSW bound

I'm headed to Austin this week for SXSW Interactive—if you're there, please introduce yourself!

Read more...

January 24th | What's New in Prototype 1.5

XML.com just published an article I wrote on What’s New In Prototype 1.5. It outlines the major themes of the new release: drastically improved test coverage, a completely revamped website (now with API docs!), and oodles of fixes. But the bulk of the article is reference-style, with descriptions and examples for all of the new methods, classes, and features added since 1.4. By and large, that content was extracted from Prototype Quick Reference, which itself was extracted from Ajax on Rails. I’m grateful to O’Reilly for letting me share that content with the community.

One thing I didn’t mention in the article was the impressive degree of community involvement in this release. Prototype has been criticized in the past for having a “black box” style of development, but I think this release signals a renewed openness in the process. There is now a group of incredibly talented folks helping to shepherd the project, and I’m pretty confident that it will keep going strong. I’d like to publicly thank everybody who chipped in to this release, so I compiled a list of the top contributors (those with more than one credit in the changelog):

  • 49: Sam Stephenson
  • 27: Thomas Fuchs
  • 10: Tobie Langel
  • 9: Mislav Marohnić
  • 7: Christophe Porteneuve
  • 5: Andrew Dupont
  • 4: DHH
  • 3: Brandon Aaron and Todd Ross
  • 2: Martin Bialasinski, adsmart, and hawk

Next time time you see one of these guys, you owe them a pint!

Read more...

December 22nd | Ajax on Rails is here

Christmas came a few days early for me, as the FedEx man just dropped off my author's copy of Ajax on Rails. It has taken a tremendous amount of work over the last year, so I'm extremely gratified to finally see it in print.

If you're interested in Rails, Ajax, or you just like books, you can buy mine. It's available from Amazon, or from O'Reilly, or from any fine bookseller, once they get their shipments. Just ask for the Monkey Book.*

If you like the idea of books but not the physical reality of them, you're not out of luck. O'Reilly decided that Chapter 10, a reference to the Prototype JavaScript library, was useful enough to be packaged as a standalone PDF. So even if you're not a Rails user, you might be interested in the Prototype Quick Reference. When Prototype 1.5 is released (soon), we'll release a new edition covering any last-minute changes; but if you buy it now, you'll get the upgrade for free. Chapter 11 (aka Script.aculo.us Quick Reference) will be released in a similar manner soon.

I owe a huge thanks to everyone who helped make this book—all of the gifted folks at O'Reilly, my peerless peer reviewers, everyone who has worked on Rails, Prototype, and Scriptaculous, and my friends, parents, and my wife. I'm a lucky guy to be surrounded by such inspiring people, and I'm quite sure I would have given up on this project without your encouragement.

*Actually, don't do that; they won't know what you mean at all.

Read more...

October 2nd | A visit to Jacob's Well

Just a quick note to point out this article in the current issue of The Christian Century magazine: A visit to Jacob's Well. The author does a good job of portraying what Jacob's Well is about—and it reminded me of how grateful I am to be part of this community.

Read more...

October 2nd | Introducing the Blinksale API

I'm happy to announce that Firewheel just released the Blinksale API. This is a project I've been working on for quite a while, and it feels great to release it to the world. I'm excited to see what's done with it. At some point I plan to write up what we learned building this sucker, but for now I just want to get the word out.

So if you're into this sort of thing, check it out and let me know if there are any questions, problems, or suggestions!

Read more...

July 31st | Seeking Reviewers for Ajax on Rails

Progress on my upcoming O’Reilly book, Ajax on Rails, is moving fast. That means it’s time to bring a few more people into the project, as Tech Reviewers. The idea of a tech review isn’t to worry about typos or grammar, but to make sure I’ve covered all of the relevant topics thoroughly and accurately.

You don’t need to be an expert on Ajax, Rails, or writing—just interested in the subject and willing to give constructive criticism. The schedule is getting crunched, so we need feedback within a week or two.

Interested? Email me at sco@scottraymond.net.

We’ve got our reviewers. Thanks to all those who replied.

Read more...

July 20th | Refactoring to REST

Inspired largely by DHH’s RailsConf keynote, I recently set about refactoring IconBuffet.com. Specifically, I wanted to embrace the SimplyRestful plugin and see how much of the application could be re-cast in terms of resources, as opposed to RPC—without changing any functionality.

The process was gradual, but as of today it’s basically done, so I thought I’d summarize the results. Before refactoring, IconBuffet had ten controllers and seventy-six actions:

  • about: home, index, license, privacy, tos, feed
  • account: signup, login, authenticate, set_subscription, retrieve_password, index, logout, update, cart, checkout, update_states
  • delivery: index, signup, deliver, download, receive
  • products: category, product, add_to_cart, remove_from_cart, download
  • admin/categories: index, create, update, delete
  • admin/deliveries: index, new, create, edit, update, update_sidebar, push, download_file, delete_file, delete_small_preview, delete_large_preview, delete
  • admin/people: index, search, set_administrator, new, create, edit, update, delete, push, gift, ungift
  • admin/products: dashboard, index, new, create, edit, update, update_sidebar, download_file, delete_file, delete_sample, delete_small_preview, delete_large_preview, delete
  • admin/site: index, update, update_sidebar, delete_feature
  • admin/specifications: new, create, edit, update, delete

Now, without adding or removing any features, IconBuffet has thirteen controllers and fifty-eight actions:

  • about: home, index, license, privacy, tos
  • cart_items: index, create, destroy
  • categories: index, create, show, update, destroy
  • deliverables: index, new, create, show, edit, update, destroy
  • deliveries: index, new, create, show, update
  • orders: index, create
  • people: index, new, create, show, edit, update, destroy, retrieve_password
  • products: index, new, create, show, edit, update, destroy
  • purchases: update
  • pushes: index, create, show
  • sessions: new, create, destroy
  • settings: index, update
  • specifications: index, new, create, show, edit, update, destroy

So, by adding a few controllers, I cut the total number of actions by almost twenty. That’s a pretty big deal, because actions are like moving parts in a machine—the more there are, the more can go wrong. The fact that I could cut almost twenty actions indicates that there was a lot of redundancy hiding beneath the surface. A big part of that redundancy was in having an Admin module, which quickly proved unnecessary. Instead, I just use before_filters to protect certain actions.

Cutting actions is great, but even more significant is that the remaining ones are almost completely uniform. There are seven standard Rails actions: index, new, create, show, edit, update, and destroy. Everything else—oddball actions—are usually a clue that you’re doing RPC. In the old version, there were forty oddball actions; now there are only five (and four of those are essentially static pages in the about controller.) The upshot is that the controllers are very uniform, which makes the entire application conceptually simpler, and thus easier to maintain, test, and extend.

Perhaps the most dramatic example of that simplification is in the Routes configuration. Take a look at the before and after:

<strong># Old: 16 lines, giving 8 named routes</strong>
ActionController::Routing::Routes.draw do |map|

  map.with_options :controller => 'products' do |m|
    m.product     'products/:slug',          :action => 'product'
    m.category    'categories/:slug',        :action => 'category'
    m.download    'products/:slug/download', :action => 'download'
    m.add         'add/:id',                 :action => 'add_to_cart'
    m.remove_cart 'remove/:id',              :action => 'remove_from_cart'
  end

  map.with_options :controller => 'delivery' do |p|
    m.connect     'delivery',                :action => 'index'
    m.receive     'delivery/receive/:token', :action => 'receive'
    m.connect     'delivery/:action',        
    m.delivery    'delivery/:slug/:action',  :action => 'product'
  end

  map.connect     'admin', :controller => "admin/products", :action => "dashboard" 
  map.home        '', :controller => "about", :action => "home" 
  map.connect     ':controller/:action/:id'

end

<strong># New: 3 lines, giving 89 named routes</strong>
ActionController::Routing::Routes.draw do |map|

  map.resources *%w( product category person cart_item
                     order deliverable delivery push 
                     specification session setting )

  map.home '', :controller => "about", :action => "home" 
  map.connect ':controller/:action/:id'

end

Now that’s the beauty of convention over configuration. Because the actions are uniform, the routes get far more bang for the buck. Count ’em: eighty-nine named routes from just three lines of configuration.

Read more...

June 23rd | Minimal Rails

It’s been observed that the success of Ruby on Rails isn’t due to any particular technical breakthrough or insight. The framework is basically just a collection of good ideas culled from elsewhere, packaged nicely and explained well, with a heapin’ helping of hype. One of those good ideas is passive code generators: tools that spit out application boilerplate for the developer to modify as needed. On the surface, code generators are simply about cutting keystrokes. But there are subtler implications as well: they lower the barrier to entry for new developers, they encourage consistency (both intra- and inter-project), and they create invitations to best practices, like unit testing.

After installation, code generation is the first thing a new Rails developer experiences: rails myapp. (Unfortunately, the next step for newcomers is usually script/generate scaffold, but that’s another story.) The generators are a large part of what makes Rails welcoming to first-timers, and pleasant for full-timers. And they cement community conventions, which is a huge benefit—every Rails app looks the same in terms of directory structure, file names, and configuration format. Learn how one Rails app is organized, and you’ve learned how they’re all organized.

The Downside of Code Generation

Bells ring; singers sing; abstractions leak. For many Railsers—even experienced ones—generators involve some degree of voodoo. They make it easy to not understand how everything works. What are all those files? What’s actually required, and what’s just convention? The significance isn’t just academic. Someday, you’ll face a bug that requires understanding the generated code.

So let’s void the warranty, crack the case, shun the scripts, and build a Rails app from scratch. You’ll find it’s not hard. A minimal working Rails application needs only six directories, five files, and 19 lines of code—including configuration. Here’s how to do it.

But First

I’ll assume that you’ve got Rails installed and working already (if not, see Curt’s introduction.) I’ll also assume MySQL is installed and accessible with the default permissions (username root; blank password). And we’ll use the excellent Mongrel web server (gem install mongrel) to serve the app. The command line examples are for a Unix-like OS, but the ideas transfer directly to Windows. This example will expect a database named myapp, with one table named people, with one column named name. Go ahead and create a row in the table, with your name in it. Ready? Set?

Go

  1. Create a directory for your application: mkdir myapp; cd myapp
  2. Create six subdirectories: mkdir log public app config app/controllers config/environments
  3. Create the development environment, and leave it blank: touch config/environments/development.rb
  4. Create your environment file, config/environment.rb, and enter this:
    require 'rubygems'
    require_gem 'rails'
    require 'initializer'
    RAILS_ROOT = File.join File.dirname(__FILE__), '..'
    Rails::Initializer.run

    These lines pull in the framework (through the RubyGems package manager) and wind everything up. A stock environment.rb includes much more, providing configuration hooks and code to handle edge cases.

  5. Configure your database by creating config/database.yml:
    development:
      adapter: mysql
      database: myapp

    Rails will assume reasonable defaults: root for the username, a blank password, and localhost for the host.

  6. Set up your routes by creating config/routes.rb:
    ActionController::Routing::Routes.draw do |map|
      map.connect '', :controller => 'people'
    end
  7. Write your application in app/controllers/application.rb:
    class Person < ActiveRecord::Base; end
    
    class PeopleController < ActionController::Base
      def index
        @person = Person.find :first
        render :inline => "Hello, <%= @person.name %>" 
      end
    end

    Notice we don’t have an ApplicationController—it’s optional; your controllers can inherit directly from ActionController::Base. And we don’t have the models, helpers, or views directories. They aren’t needed; models can be defined anywhere, including application.rb.

That’s it—the minimal application, stripped it down to the rails. Moment of truth time: start it up by running mongrel_rails start, and then point your browser to http://localhost:3000/. If all is well, you’ll see a hyper-minimal Rails app that exercises the whole stack, M, V, and C. Now stop to consider what’s not there: helpers, views, and model files, a bunch of directories (components, db, doc, lib, script, and vendor), dispatchers, Rake tasks, and tests. All of those things are technically optional. They’re conventional, convenient, and recommended—but not essential.

Now Forget Everything I Just Told You

So is this the real man’s way to write Rails? Hell no. Rails’ generators are there for a reason, and there’s no reason to forgo them in your real work. But it’s a worthwhile exercise to go without them, at least once. Not only will you appreciate the shortcuts more, but your understanding of the framework will be stronger.

Have I overlooked anything else that can be cut? Left out something essential? Let me know!

P.S. Attending RailsConf this weekend in Chicago? I’d love to meet. I’ll be speaking at 10am Saturday morning—please stop by and introduce yourself!


Read more...

June 1st | Rails Writ Large

I’m proud to have an article published in the July issue of Linux Journal, entitled “Rails Writ Large”. The editors at LJ asked me to summarize the new additions to Rails in 1.1. Of course, there was so much new that I couldn’t possibly mention everything in the space available, but I hit the highlights. I was also able to include a quick overview of Rails for newcomers—and hopefully just enough sample code to entice them to dig deeper.

And speaking of digging deeper, the mini-bio at the end of the article reveals something I’ve been keeping quiet: I am writing a book for O’Reilly on creating Ajax-powered applications with Rails, to be available later this year. If you have started using Rails or are considering it for Ajax development, this book will have you covered from every angle—from the nuts and bolts of XMLHttpRequest, through Prototype, Scriptaculous, and RJS, and up to the higher-level issues of testing, design, and usability. It’s been a challenging and rewarding project so far, and I’m really looking forward to sharing the result with the world.

But more on that in the future. For now, if you want to start your collection of the complete works of Scott Raymond, head to your local purveyor of fine magazines and look for DHH’s mug.


Read more...

March 27th | Rails 1.1 is here

Just a quick note to mention that Rails 1.1 is real now, so I’ve once again updated my article summarizing the changelogs, and I’ve added sections for Prototype and Scriptaculous as well.

Read more...

March 18th | Rails 1.1 update

I have updated my last post on the upcoming changes in Rails 1.1 to reflect the last couple weeks of development. The most notable changes center around support for interesting HTTP capabilities—it seems that David has gotten some REST religion, which I’m pleased to see. Several of these new features have already made it into the development branch of Blinksale 2.0.

The updates to the article are scattered throughout, so if you just want the highlights, here are the keywords to look for:

  • pluggable parameter parsers
  • render(:xml => ...)
  • respond_to
  • render :content_type => ...
  • to_xml
  • scaffold verification
  • reload! and app in the console
  • Rake namespaces
  • test:uncommitted
  • group_by
  • in groups of
  • cascading eager loading
  • RJS exception notification

Read more...

February 27th | What's new in Rails 1.1

It’s been just over two months since the Rails 1.0 milestone, and the long push of testing and refining that lead up to it. Surely, the contributors have been taking a much-deserved rest in the time since then. Surely?

In fact, the core team (and over 120 other contributors) haven’t slowed down one bit, and the next major release of Rails is here. If you’re running Edge Rails, you already have access to all the latest features, but perhaps a few have missed your radar. So I’d like to round-up what’s new since 1.0 (or at least, everything that’s interesting to me—I’ve skipped a ton of bug fixes, performance improvements, environment-specific enhancements, and smaller changes.) Let’s start with the easier parts.

  1. Railties
  • The new default schema format is :ruby instead of :sql. This wins the award for best changelog comment in 1.1:
This means that we’ll assume you want to live in the world of db/schema.rb where the grass is green and the girls are pretty… Brought to you by the federation of opinionated framework builders!
  • script/process/spinner is gone. Instead, use the new -r/--repeat option to script/process/spawner.
  • The new -c/--config option on script/server allows you to specify a path to your lighttpd.conf.
  • Action and fragment caching is now ready to use out of the box, storing caches in tmp/cache.
  • The environment is forced to be “test” when running tests, so that ENV["RAILS\_ENV"] = "production" in config/environment.rb doesn’t wreak havoc. (I’ve been bitten by that nasty.)
  • The default index.html uses Ajax to fetch details about your Rails configuration from a new, omnipresent Rails::InfoController.
  • A blank public/javascripts/application.js is now included in newly-generated apps, and it’s included in javascript\_include\_tag :defaults. (I’d love it if the generator also created a blank application.css, and an app/views/layouts/application.rhtml with the standard XHTML boilerplate.)
  • script/console gets a couple shortcuts: reload! reloads all models, and app is an accessor for an instance of Integration::Session. Handy!
  • ActionView helpers are available from the console, which is great for debugging. For example:
>> puts helper.options_for_select([%w(a 1), %w(b 2), %w(c 3)])
option value="1">a</option>
option value="2">b</option>
option value="3">c</option>
> nil
  • Rake tasks now have namespaces, so for example load_fixtures is now db:fixtures:load (which you can also use to load a subset of the application’s fixtures, e.g. rake db:fixtures:load FIXTURES=customers,plans). All the old task names will still work. Run rake --tasks to see the new task names.
  • Ruby 1.8.3 is explicitly disallowed, because Rails is incompatible with it. Check your Ruby version before you upgrade!
  • New Rake task test:uncommitted tests changes since last checkin to Subversion.
  • The dreaded “white screen of death” (a blank browser window for certain Rails errors) should now mostly be a thing of the past.
  1. ActiveSupport
  • Every object now has a with_options method, useful for DRYing up multiple calls to methods having shared options. For example:
ActionController::Routing::Routes.draw do |map|
  # Account routes
  map.with_options(:controller => 'account') do |account|
    account.home   '',       :action => 'dashboard'
    account.signup 'signup', :action => 'new'
    account.logout 'logout', :action => 'logout'
  end
end
  • Every object now has a a to_json method, which outputs JSON strings. For example:
[1,2,3].to_json => "[1, 2, 3]" 
"Hello".to_json => "\"Hello\"" 
Person.find(:first).to_json =>
   "{\"attributes\": {\"id\": \"1\", \"name\": \"Scott Raymond\"}}" 
  • All enumerables get group_by, to group collections based on the result of some block. For example:
transcripts.group_by(&:day)
  • Arrays now have in_groups_of which iterates over an array in groups of a certain size. For example:
%w(1 2 3 4 5 6 7).in_groups_of(3) {|g| p g}
["1", "2", "3"]
["4", "5", "6"]
["7", nil, nil]
  • Hashes and Arrays now have to_xml.
  • New class CachingTools::HashCaching lets you created nested, autofilling hashes.
  • Added Fixnum#seconds for consistency, so you can say 5.minutes + 30.seconds instead of 5.minutes + 30.
  • New Hash method diff shows the difference between two hashes.
  • Logger now has around_* methods, to make it easy to log before and after messages for a given block. For example:
logger.around_info("Start rendering component (#{options.inspect}): ",
  "End of component rendering") { yield }
  • Time has a funny new method, beginning_of_quarter. For example:
Time.now.beginning_of_quarter => Sun Jan 01 00:00:00 CST 2006
  • New delegation support allows multiple delegations at once (unlike Forwardable). For example:
class Account < ActiveRecord::Base
  has_one :subscription
  delegate :free?, :paying?, :to => :subscription
  delegate :overdue?, :to => "subscription.last_payment" 
end
account.free?    # => account.subscription.free?
account.overdue? # => account.subscription.last_payment.overdue?
  • Proc#bind(object): changes a proc or block’s self by returning a Method bound to the given object. Based on why’s “cloaker” method.
  • Object#copy_instance_variables_from(object): copies instance variables from one object to another.
  • Object#extended_by: gets an instance’s included/extended modules.
  • Object#extend_with_included_modules_from(object): extends an instance with the modules from another instance.

Now for the fun stuff!

  1. ActiveRecord
  • has_many :through. See this and this. For example:
class Author < ActiveRecord::Base
  has_many :authorships
  has_many :books, :through => :authorships
end
class Book < ActiveRecord::Base
  has_many :authorships
  has_many :authors, :through => :authorships
end
class Authorship < ActiveRecord::Base
  belongs_to :author
  belongs_to :book
end
Author.find(:first).books.find(:all, :include => :reviews)
  • The :through option can also be used where the intermediate association is a has_many. (This originated in a patch of mine, taken from Blinksale, but it was substantially improved by Rick Olson). For example:
class Firm < ActiveRecord::Base
  has_many :clients
  has_many :invoices, :through => :clients
end
class Client < ActiveRecord::Base
  belongs_to :firm
  has_many   :invoices
end
class Invoice < ActiveRecord::Base
  belongs_to :client
end
class Address < ActiveRecord::Base
  belongs_to :addressable, :polymorphic => true
end
class User < ActiveRecord::Base
  has_one :address, :as => :addressable
end
class Company < ActiveRecord::Base
  has_one :address, :as => :addressable
end
  • Nested with_scope. See this. For example:
Developer.with_scope(:find => { :conditions => "salary > 10000", :limit => 10 }) do
  Developer.find(:all)     # => SELECT * FROM developers WHERE (salary > 10000) LIMIT 10
  # inner rule is used. (all previous parameters are ignored)
  Developer.with_exclusive_scope(:find => { :conditions => "name = 'Jamis'" }) do
    Developer.find(:all)   # => SELECT * FROM developers WHERE (name = 'Jamis')
  end
  # parameters are merged
  Developer.with_scope(:find => { :conditions => "name = 'Jamis'" }) do
    Developer.find(:all)   # => SELECT * FROM developers WHERE (( salary > 10000 ) AND ( name = 'Jamis' )) LIMIT 10
  end
end
  • Calculations: calculations and statistics need no longer require custom SQL. See this. For example:
Person.count
Person.average :age
Person.minimum :age
Person.maximum :age
Person.sum :salary, :group => :last_name
  • Cascading eager loading allows for queries like Author.find(:all, :include=> { :posts=> :comments }), which will fetch all authors, their posts, and the comments belonging to those posts in a single query. For example:
Author.find(:all, :include=>{:posts=>:comments})
Author.find(:all, :include=>[{:posts=>:comments}, :categorizations])
Author.find(:all, :include=>{:posts=>[:comments, :categorizations]})
Company.find(:all, :include=>{:groups=>{:members=>{:favorites}}})
  • Option inheritance for find calls on has\_and\_belongs\_to\_many and has\_many assosociations. For example:
class Post
  has_many :recent_comments, :class_name => "Comment", :limit => 10, :include => :author
end
post.recent_comments.find(:all) # Uses LIMIT 10 and includes authors
post.recent_comments.find(:all, :limit => nil) # Uses no limit but include authors
post.recent_comments.find(:all, :limit => nil, :include => nil) # Uses no limit and doesn't include authors
  • XML representations for records with to_xml. For example:
topic.to_xml
topic.to_xml(:skip_instruct => true, :skip_attributes => [ :id, bonus_time, :written_on, replies_count ])
firm.to_xml :include => [ :account, :clients ]
  • Allow validate\_uniqueness\_of to be scoped by multiple columns. See this.
  • ActiveRecord::Errors now mixes-in Enumerable.
  • The :exclusively\_dependent option has been deprecated in favor of :dependent => :delete_all.
  • The .find() method, and the has\_and\_belongs\_to\_many and has\_many associations, now all take :group, :limit, :offset, and :select options.
  • Fixtures may be stored in subdirectories of test/fixtures—great for organizing fixtures for STI.
  • Dynamic finders honor additional passed in :conditions. See this.
  • validates\_length\_of now works on UTF-8 strings—it counts characters instead of bytes.
  1. ActionPack
  • RJS templates. Big one. For background, see Cody’s explanation and my earlier example. The basic idea: in addition to .rhtml (Ruby HTML) templates, you can create .rjs (Ruby JavaScript) ones. In them, you can write Ruby code that will generate JavaScript code, which is sent as the result of an Ajax call, and evaluated by the browser. It sounds complicated, but believe me, Ajax development just got a lot easier.

    The RJS templates are passed an page object that represents the JavaScriptGenerator, which has many tricks up its sleeve:

    • Pop an alert() dialog: alert 'Howdy'
    • Simulate a redirect with window.location.href: redirect_to
    • Calls a JavaScript function: call
    • Assigns to a JavaScript variable: assign
    • Replaces the outerHTML of an element: replace
    • Insert text: insert\_html :bottom, 'list', '&lt;li&gt;Last item&lt;/li&gt;'
    • Call an effect: visual\_effect :highlight, 'list'
    • Show something: show 'status-indicator'
    • Hide stuff: hide 'status-indicator', 'cancel-link'
    • Refer to an element by id: ['blank\_slate']
    • ['blank\_slate'].show # => $('blank_slate').show();
    • Get elements with CSS selectors: select('p')
    • select('p.welcome b').first # => $$('p.welcome b').first();
    • select('p.welcome b').first.hide # => $$('p.welcome b').first().hide();
    • Insert some JavaScript: &lt;&lt;
    • Make a draggable: draggable 'product-1'
    • drop_receiving 'wastebasket', :url => { :action => 'delete' }
    • sortable 'todolist', :url => { action => 'change_order' }
    • Delay execution: delay(20) { page.visual_effect :fade, 'notice' } * RJS exception notification via alert() (set config.action_view.debug_rjs = true)
    • Enumerable methods can be used, and they’ll generate the equivalent JavaScript code: page.select('#items li').collect('items'){ |element| element.hide } generates var items = $$('#items li').collect(function(value, index) { return value.hide(); });
  • In addition to having .rjs files in your views directory, you can also write Inline RJS. For example:
class UserController < ApplicationController
  def refresh
    render :update do |page|
      page.replace_html  'user_list', :partial => 'user', :collection => @users
      page.visual_effect :highlight, 'user_list'
    end
  end
end
  • You can also write RJS Helpers that can be called from update blocks. For example:
module ApplicationHelper
  def update_time
    page.replace_html 'time', Time.now.to_s(:db)
    page.visual_effect :highlight, 'time'
  end
end
class UserController < ApplicationController
  def poll
    render :update { |page| page.update_time }
  end
end
  • respond_to lets an action output different formats according to the HTTP Accept header. In other words, you’ve got instance REST web services. Blinksale 2.0 already uses this. For example:
class WeblogController < ActionController::Base
  def index
    @posts = Post.find :all
    respond_to do |wants|
      wants.html # using defaults, which will render weblog/index.rhtml
      wants.xml  { render :xml => @posts.to_xml } # generates XML and sends it with the right MIME type
      wants.js   # renders index.rjs
    end
  end
end
  • Pluggable parameter parsers make writable REST web services a cinch. By default, posts submitted with the application/xml content type is handled by creating a XmlSimple hash with the same name as the root element of the submitted XML. More handlers can easily be registered like this:
# Assign a new param parser to a new content type
ActionController::Base.param_parsers['application/atom+xml'] = Proc.new do |data| 
  node = REXML::Document.new(post) 
 { node.root.name => node.root }
end
# Assign the default XmlSimple to a new content type
ActionController::Base.param_parsers['application/backpack+xml'] = :xml_simple
  • New form helpers form\_for, form\_remote\_for, and fields\_for make it easier to work with forms for single objects, even if they don’t reside in instance variables. For example:
&lt;% form_for :person => @person, :url => { :action => "update" } do |f| %&gt;
  First name: &lt;%= f.text_field :first_name %&gt;
  Last name : &lt;%= f.text_field :last_name %&gt;
  Biography : &lt;%= f.text_area :biography %&gt;
  Admin?    : &lt;%= f.check_box :admin %&gt;
&lt;% end %&gt;
&lt;% form_for :person => person, :url => { :action => "update" } do |person_form| %&gt;
  First name: &lt;%= person_form.text_field :first_name %&gt;
  Last name : &lt;%= person_form.text_field :last_name %&gt;
  &lt;% fields_for :permission => person.permission do |permission_fields| %>
    Admin?  : &lt;%= permission_fields.check_box :admin %&gt;
  &lt;% end %&gt;
&lt;% end %&gt;
  • form_for and friends can take a * option, where you can pass a custom subclass of FormBuilder. For example:
&lt;% form_for :person, @person, :url => { :action => "update" }, :builder => LabellingFormBuilder do |f| %&gt;
  &lt;%= f.text_field :first_name %&gt;
  &lt;%= f.text_field :last_name %&gt;
&lt;% end %&gt;
  • Integration Testing, a new type of test, spans multiple controllers and actions, tying them all together to ensure they work together as expected. It tests more completely than either unit or functional tests do, exercising the entire stack, from the dispatcher to the database. At its simplest, you simply extend IntegrationTest and write your tests using the get/post methods. For example:
require "#{File.dirname(__FILE__)}/test_helper" 
require "integration_test" 
class ExampleTest < ActionController::IntegrationTest
  fixtures :people
  def test_login
    # get the login page
    get "/login" 
    assert_equal 200, status
    # post the login and follow through to the home page
    post "/login", :username => people(:jamis).username,
      :password => people(:jamis).password
    follow_redirect!
    assert_equal 200, status
    assert_equal "/home", path
  end
end

Integration Tests can also have multiple session instances open per test, and even extend those instances with assertions and methods to create a very *powerful testing DSL that is specific for your application. You can even reference any named routes you happen to have defined. For example (think Campfire here):

def test_login_and_speak
  jamis, david = login(:jamis), login(:david)
  room = rooms(:office)
  jamis.enter(room)
  jamis.speak(room, "anybody home?")
  david.enter(room)
  david.speak(room, "hello!")
end
private
module CustomAssertions
  def enter(room)
    # reference a named route, for maximum internal consistency!
    get(room_url(:id => room.id))
    assert(...)
    ...
  end
  def speak(room, message)
    xml_http_request "/say/#{room.id}", :message => message
    assert(...)
    ...
  end
end
def login(who)
  open_session do |sess|
    sess.extend(CustomAssertions)
    who = people(who)
    sess.post "/login", :username => who.username,
      :password => who.password
    assert(...)
  end
end
  • render(:xml => xml) works just like render(:text => text), but sets the content-type to application/xml and the charset to UTF-8.
  • Added :content_type option to render, so you can change the content type on the fly. For example:
render :action => "atom.rxml", :content_type => "application/atom+xml"
  • Scaffolds now use verification, to prevent non-idempotent GETs.
  • Allow auto-discovery of third party template library layouts.
  • More robust relative URL root discovery for SCGI compatibility.
  • Unused helper files can be deleted without throwing exceptions.
  • JavaScriptHelper has been split into PrototypeHelper and ScriptaculousHelper.
  • Introduced d option to the select helper. Allows you to specify a selection other than the current value of object.method. See this.
  • The auto_link text helper accepts an optional block to format the link text for each url and email address. For example:
auto_link(post.body) { |text| truncate(text, 10) }
  • content\_for and capture now work in .rxml (and any non-rhtml template). See this.
  • visual\_effect supports scoped queues. See this.
  • observe\_field now has an n option to specify a different callback hook to have the observer trigger on.
  • button\_to\_function—works just like link\_to\_function, but uses a button instead of a link.
  • link\_to\_function will now honor existing :onclick definitions when adding the function call.
  • submit\_tag now has a h option to change the text of disabled submit buttons.
  • visual\_effect can now toggle visual effects. See this.
  • auto\_complete\_field now has a t option for to only use part of the auto-complete suggestion as the value for insertion.
  1. Prototype
  • New Selector class (and corresponding $$ function) matches elements by CSS selector tokens. For example:
// Find all <img> elements inside <p> elements with class 
// "summary", all inside the <div> with id "page". Hide 
// each matched <img> tag.
$$('div#page p.summary img').each(Element.hide)

// Attributes can be used in selectors as well:
$$('form#foo input[type=text]').each(function(input) {
  input.setStyle({color: 'red'});
});
  • Element methods are mixed into HTML elements referenced by $ and $$, so you can now write $('foo').show() instead of Element.show('foo').
  • Several new methods added to String: truncate, gsub, sub, scan, and strip.
  • Ajax calls set the HTTP Accept header to ‘text/javascript, text/html, application/xml, text/xml */’’ to inform Rails that it prefers RJS, but will take anything.
  • Element.replace is a cross-browser implementation of the “outerHTML” property.
  • A Template class was added for interpolating named keys from an object in a string.
  • New method Element.childOf(element, ancestor) returns true when element is a child of ancestor.
  1. Scriptaculous
  • Local/scoped effect queues. Use “limit” option to limit the maximum number of effects in a queue.
  • Added “visualEffect” method for the Element Mixin, fixed so you can chain multiple calls.
  • Make it possible to scroll window on dragging.
  • Effect.toggle for slide, blind and appear/fade effects.
  • Sortable.sequence can extract the current sequence of a Sortable as an array, and Sortable.setSequence can programmatically reorder a Sortable.
  • Default effects options are globally modifyable with Effect.DefaultOptions.
  • New core effect Effect.Move can do absolute/relative movement.
  • Scroll options are passed through from Sortable to Draggable.
  • Fix “only” option on Sortable.create to accept multiple class names.
  • New Ajax.InPlaceCollectionEditor uses a SELECT element instead of a text field. See test/functional/ajax_inplacecollectioneditor_test.html for usage.
  • In place editor now uses RJS.
  • New “activate” method to Autocompleter that allows you to trigger the suggestions through other means than user input.
  • New “select” option to Autocompleter to optionally use textnodes from elements with a specific CSS class. See test/functional/ajax_autocompleter_test.html for usage.

Read more...

January 11th | Yours truly on the Rails Podcast

Just a quick note to mention that I’m the guest on the latest Ruby on Rails Podcast. Here’s the direct link to the MP3 (22mb). Thanks for having me, Geoffrey!

Read more...

December 13th | Rails 1.0 - What a year

Congratulations to the Rails core team on today’s long-awaited release of 1.0. The new website is fittingly spare and elegant.

The release almost exactly coincides with my one-year anniversary of using Rails, and it’s been a good year indeed. It’s quite a thrill to have not one, but two applications that I developed for Firewheel listed on the showcase page. Thanks for that honor, and bravo, gents.

Read more...

November 30th | Real-world Rails RJS templates

Today Firewheel Design launched the second site that I helped them develop. The first was Blinksale (the un-QuickBooks; an invoicing tool I can’t imagine doing business without). The latest is an overhaul of IconBuffet, home of their venerable stock icon collections.

Firewheel is what you’d call a “dream client.” They thoroughly get the web: they care about standards and providing real value to users. They’re massively talented designers, both in the “make it pretty” sense and the more elusive “make is usable” sense. And they provide me, as the developer, with everything I need to make a kick-ass web application.

In the case of IconBuffet, the designers at Firewheel wanted the shopping cart to be as simple as humanly possible, with instant feedback when an item is added to the cart. Ajax made perfect sense, and Rails makes that a snap. The only catch was that I needed to update three separate elements on the page any time a product was added or removed from the cart. My first solution was to write some Javascript to handle each action. My code looked like this:

  var Cart = {
    add: function(product_id) {
      Element.addClassName('product_' + product_id, 'incart')
      new Ajax.Request('/account/add_to_cart/' + product_id,
         { method: 'post', onComplete: Cart.refresh })
    },
    remove: function(product_id) {
      Element.removeClassName('product_' + product_id, 'incart')
      new Ajax.Request('/account/remove_from_cart/' + product_id,
         { method: 'post', onComplete: Cart.refresh })
    },
    refresh: function() {
      new Ajax.Updater('cartbox', '/products/cartbox')
      new Ajax.Updater('num_items', '/products/num_items')
    }
  }

Calling Cart.add(1) would add a CSS class to a DOM element, and then send an Ajax request to the controller to add the item to the cart. That request has an onComplete callback to Cart.refresh, which made two more Ajax calls, to update the status in the sidebar and the header. It worked, but it wasn’t great: there was a noticable delay between the three changes on screen, which made the whole thing feel very sluggish. Plus, it created a slight “code smell” to have the page’s logic spread out through so many layers.

Just then, the Rails developers (notably Marcel) dropped a little goodie in my lap: RJS Templates. This addition allows you to generate Javascript from Ruby, which can be returned by Ajax calls and evaluated in the page—making problems like mine a piece of cake. Since RJS is just a couple weeks old, I suspect that IconBuffet is one of the first public apps in production to use the technique. Here’s how my code looks now:

As usual, I use link_to_remote, Rails’ standard way to create an Ajaxified link:

   link_to_remote "Add to Cart", :url => { :action => 'add_to_cart', :id => product }

The controller saves the product id and renders the add_to_cart view—but instead of the usual .rhtml or .rxml template, it’s an .rjs template:

   page.replace_html 'cartbox', :partial => 'cart'
   page.replace_html 'num_items', :partial => 'num_items'
   page.send :record, "Element.addClassName('product_#{@params[:id]}', 'incart')"

These three lines accomplish the same thing as the fourteen lines of Javascript above. The first line renders the ‘cart’ partial into the DOM element #cartbox. The second line does the same thing, but for the header. The third line just creates a line of Javascript to add a CSS class to an element. The results of the two techniques is the same, but the effect is far nicer now—the code is more succinct and centralized, and the user experience is significantly smoother.

There’s a whole lot more that’s possible, but this should whet your appetite. RJS isn’t yet available in any released version of Rails, so you’ve got to checkout the trunk from the repository. It’s quite a testament to the Rails core team that the bleeding-edge trunk is stable enough to build a production application on… thanks guys!

Read more...


This site looks best when viewed with your mom.