Trying to break free from .NET?

Luckymonk escaped their .NET entanglement and found Ruby on Rails, but they still have to live in a Windows world. Now they’re putting on a workshop teaching you how to do the same. It’s in Chicago and it’s almost sold out.

Usually we don’t mention workshops any more (there are so many), but I thought it would be nice to shine some light on one of the lesser published transitions. The one from .NET to Ruby on Rails. We got plenty of stories of how PHP and Java folks are jumping ship, so its great to hear how it’s happening from the Microsoft camp too.

Amazon goes Ruby on Rails

Never thought you’d hear that, eh? But that’s exactly what unspun.amazon.com is. A Rails application sitting on the Amazon.com domain. Adam Selipsky from Amazon Web Services introduces the site with this:

UnSpun helps you to find and create ranked lists by gathering votes from workers on Amazon Mechanical Turk and from the UnSpun community. We show the popular opinion, with no “spin” (hence the name) — along with links to websites with more information about the particular items on the list. If you don’t see the list you are looking for, simply create it and rankings will start populating within a few minutes. 2,294 ranked lists are already on UnSpun, holding 640,107 items, with more coming in all the time.

Congratulations to the AWS team at Amazon!

Coming shortly, we’ll have a bunch of other announcements for high-profile companies going Ruby on Rails for various new projects. Exciting times.

Agile Web Development with Rails, 2nd Edition

How fitting. Just as we’re wrapping up Rails 1.2, the book that covers it all has gone into print. I’m talking about Agile Web Development with Rails, 2nd Edition, of course. The book that has taught the vast majority of all Rails developers the ropes.

Yes, yes, it’ll be available in all its paper glory around December 15th. But you can actually get the final PDF now. Dave Thomas just released the same version as was shipped off to the printers. If you’d like to get access to the information now, but still enjoy reading long sections from paper, there’s the combo pack.

The timing is great and so are the sections on RJS, migrations, resources, respond_to, polymorphic associations, and all the other new-fangled wonders that has been introduced to Rails since the last version of the book. Go get it.

Rails 1.2 RC1: New in Active Support

The following are some of the smaller, but notable features added to Rails 1.2 ActiveSupport since the Rails 1.1 release. (compiled by Joshua Sierles).

Module#unloadable marks constants that require unloading after each request. Example:

    CONFIG.unloadable

Module#alias_attribute clones class attributes, including their getter, setter and query methods. Example:

class Email < ActiveRecord::Base
  alias_attribute :subject, :title
end

e = Email.find(1)
e.title    # => "Superstars"
e.subject  # => "Superstars"
e.subject? # => true
e.subject = "Megastars"
e.title    # => "Megastars"

Enumerable#sum calculates a sum from the array elements. Examples:

  [1, 2, 3].sum
  payments.sum { |p| p.price * p.tax_rate }
  payments.sum(&:price)

  This replaces: payments.inject(0) { |sum, p| sum + p.price }

Array#to_s(:db) produces a comma-separated list of ids. Example:

Purchase.find(:all, :conditions => "product_id IN (#{shops.products.to_s(:db)})"

Module#alias_method_chain encapsulates the common pattern:

alias_method :foo_without_feature, :foo
alias_method :foo, :foo_with_feature

 With alias_method_chain:

alias_method_chain :foo, :feature

Array#split divides arrays into one or more subarrays by value or block. Examples:

[1, 2, 3, 4, 5].split(3) => [[1, 2], [4, 5]] 
(1..10).to_a.split { |i| i % 3 == 0 }   # => [[1, 2], [4, 5], [7, 8], [10]]

Hash.from_xml(string) creates a hash from an XML string, typecasting its elements if possible. Example:

Hash.from_xml <<-EOT
  <note>
    <title>This is a note</title>
    <created-at type="date">2004-10-10</created-at>
  </note>
EOT

...would return:

{ :note => { :title => "This is a note", :created_at => Date.new(2004, 10, 10) } }

The Builder package has been upgraded to version 2.0. Changes include:

-- UTF-8 characters in data are now correctly translated to their XML equivalents
-- Attribute values are now escaped by default

Rails 1.2 RC1: New in Action Pack

With all respect to the reporter from the Edge, here are a few tasty bits from ActionPack in Rails 1.2 (CHANGELOG). (compiled by Geoffrey Grosenbach).

Views

You can now access nested attributes in RJS:

page['foo']['style']['color'] = 'red' # => $('foo').style.color = 'red';

Forms now use blocks instead of end_form_tag (notes from DHH):


<% form_tag(products_url) do %>
  <%= text_field :product, :title %>
  <%= submit_tag "Save" %>
<% end -%>

And how many blogs have you visited that say “Last updated 60 days ago”? Years and months have been added to distance_of_time_in_words, so you’ll see “2 months ago” or maybe even “5 years ago” now.

Controllers

Uncaught exceptions raised anywhere in your application will cause RAILS_ROOT/public/500.html to be read and shown instead of just the static “Application error (Rails).” So make it look nice if you aren’t using it already!

There is a new head(options = {}) method for responses that have no body.

head :status => 404 # return an empty response with a 404 status
head :location => person_path(@person), :status => 201

You can declare specific file extensions exempt from layouts. Bring on the CSS, PDF, and graphic generating plugins!

ActionController::Base.exempt_from_layout 'rpdf'

RESTful resources automatically get a params[:format] option that can force a content type. If :format is specified and matches a declared extension, that mime type will be used in preference to the “Accept” header. This means you can link to the same action from different extensions and use that fact to determine output (cheat sheet).

class WeblogController < ActionController::Base
  def index
    @posts = Post.find :all
    respond_to do |format|
      format.html
      format.xml { render :xml => @posts.to_xml }
      format.rss { render :action => "feed.rxml" }
    end
  end

You can also register your own custom MIME types. These will be automatically incorporated into controllers so you can use them in respond_to blocks and as file :format extensions.

Mime::Type.register(string, symbol, synonyms = [])
Mime::Type.register("image/gif", :gif)

Finally, ActionController.filter_parameter_logging makes it easy to remove passwords, credit card numbers, and other sensitive information from being logged when a request is handled.

filter_parameter_logging 'password' # Don't log fields that match 'password'

Routing and URLs

Routing has been significantly rewritten for speed and consistency. One of the benefits is that you can use named routes and RESTful routes in your mailer templates.


class MyMailer < ActionMailer::Base

  include ActionController::UrlWriter
  default_url_options[:host] = 'my_site.com'

Testing

assert_response now supports additional symbolic status codes.

  assert_response :success # You know this one
  assert_response :ok
  assert_response :not_found
  assert_response :forbidden

Added the rulin’ assert_select for CSS selector-based testing (cheat sheet). Use this instead of assert_tag from now on.

assert_select "a[href=http://assert_select_rules.com]", @item.url, "Should have a link" 
assert_select "div#products", nil, "Should show a products div on the page"

Deprecated

You’ll see warnings when you run your test suite. Here are a few that have been replaced with better syntax:

  • assert_tag → assert_select
  • start_form_tag and end_form_tag → form_tag do end
  • @cookies, @headers, @request, @response, @params, @session, @flash → cookies, headers, request, response, params, session, flash
  • .png is no longer automatically appended to extension-less image_tag calls

Rails 1.2 RC1: New in ActiveRecord

Here are some of the smaller yet notable features in the Rails 1.2 release of ActiveRecord made since the 1.1 release. (compiled by Josh Susser).

Finding

Added simple hash conditions to #find that will just convert a hash to an equality/AND-based condition string. Example:

Person.find(:all, :conditions => { :last_name => "Catlin", :status => 1 })

…is the same as:

Person.find(:all, :conditions => [ "last_name = ? and status = ?", "Catlin", 1 ])

This makes it easier to pass in the options from a form or otherwise outside.

Added find_or_initialize_by_X which works like find_or_create_by_X but doesn’t save the newly instantiated record.

Records and arrays of records are bound as quoted ids.

Foo.find(:all, :conditions => ['bar_id IN (?)', bars])
Foo.find(:first, :conditions => ['bar_id = ?', bar])

Associations

Allow :uniq => true with has_many :through associations. This is equivalent to doing a SELECT DISTINCT in SQL, but it is done in Ruby code instead.

Add records to has_many :through using «, push, and concat by creating the join model record. Raise if base or associate are new records since both ids are required to create the association. #build raises an error since you can’t associate an unsaved record. #create! takes an attributes hash and creates both the associated record and its join model record in a transaction.

For example:

# before:
post.taggings.create!(:tag => Tag.find_by_name('finally')
# after:
post.tags << Tag.find_by_name('finally')

And:

# before:
transaction { post.taggings.create!(:tag => Tag.create!(:name => 'general')) }
# after:
post.tags.create! :name => 'general'

Add #delete support to has_many :through associations.

has_one supports the :dependent options :destroy, :delete, and :nullify.

Misc

Support for row-level locking, using either the :lock finder option or the #lock! method. See ActiveRecord::Locking::Pessimistic docs for details.

# Obtain an exclusive lock on person 1 so we can safely increment visits.
Person.transaction do
  # SELECT * FROM people WHERE id=1 FOR UPDATE
  person = Person.find(1, :lock => true)
  person.visits += 1
  person.save!
end

Rails 1.2: Release Candidate 1

It’s been almost eight months since the last major release of Rails introduced RJS, respond_to, eager loading, and much more. It’s about time we introduced the latest batch of big ideas we’ve been polishing in the interim.

Since this is a major new release and we’ve gotten so much incredible uptake even since 1.1, we’re feeling the need to certify that things work as well as they can out the gates. Thus, this release candidate to fret out any regressions or major issues with the new features.

Update: Josh Susser has more on what this means for developers, and how best to go about submitting bug reports for the new release.

What’s New

But first, allow me to give you a short rundown of what you should be excited about. While these new features may not appear to have the immediate glitz and glamour the likes of RJS, they still represent a big fundamental shift in how a lot of Rails applications will be created from this day forth.

REST and Resources

REST, and general HTTP appreciation, is the star of Rails 1.2. The bulk of these features were originally introduced to the general public in my RailsConf keynote on the subject. Give that a play to get into the mindset of why REST matters for Rails.

Then start thinking about how your application could become more RESTful. How you too can transform that 15-action controller into 2-3 new controllers each embracing a single resource with CRUDing love. This is where the biggest benefit is hidden: A clear approach to controller-design that’ll reduce complexity for the implementer and result in an application that behaves as a much better citizen on the general web.

To help the transition along, we have a scaffold generator that’ll create a stub CRUD interface, just like the original scaffolder, but in a RESTful manner. You can try it out with “script/generate scaffold_resource”. Left with no arguments like that, you get a brief introduction to how it works and what’ll create.

The only real API element that binds all this together is the new map.resources, which is used instead of map.connect to wire a resource-based controller for HTTP verb love. Then, once you have a resource-loving controller, you can link with our verb-emulation link link_to "Destroy", post_url(post), :method => :delete. Again, running the resource scaffolder will give you a feel for how it all works.

Formats and respond_to

While respond_to has been with us since Rails 1.1, we’ve added a small tweak in 1.2 that ends up making a big difference for immediate usefulness of the feature. That is the magic of :format. All new applications will have one additional default route: map.connect ':controller/:action/:id.:format'. With this route installed, imagine the following example:

class WeblogController < ActionController::Base def index @posts = Post.find :all respond_to do |format| format.html format.xml { render :xml => @posts.to_xml } format.rss { render :action => “feed.rxml” } end end end GET /weblog # returns HTML from browser Accept header GET /weblog.xml # returns the XML GET /weblog.rss # returns the RSS

Using the Accept header to accomplish this is no longer necessary. That makes everything a lot easier. You can explore your API in the browser just by adding .xml to an URL. You don’t need a before_filter to look for clues of a newsreader, just use .rss. And all of them automatically works with page and action caching.

Of course, this format-goodness plays extra well together with map.resources, which automatically makes sure everything Just Works. The resource-scaffold generator even includes an example for this using format.xml, so /posts/5.xml is automatically wired up. Very nifty!

Multibyte

Unicode ahoy! While Rails has always been able to store and display unicode with no beef, it’s been a little more complicated to truncate, reverse, or get the exact length of a UTF-8 string. You needed to fool around with KCODE yourself and while plenty of people made it work, it wasn’t as plug’n’play easy as you could have hoped (or perhaps even expected).

So since Ruby won’t be multibyte-aware until this time next year, Rails 1.2 introduces ActiveSupport::Multibyte for working with Unicode strings. Call the chars method on your string to start working with characters instead of bytes.

Imagine the string ‘€2.99’. If we manipulate it at a byte-level, it’s easy to get broken dreams:

‘€2.99’[0,1] # => “\342” ‘€2.99’[0,2] # => “?” ‘€2.99’[0,3] # => “€”

The € character takes three bytes. So not only can’t you easily byte-manipulate it, but String#first and TextHelper#truncate used to choke too. In the old days, this would happen:

‘€2.99’.first # => ‘\342’ truncate(‘€2.99’, 2) # => ‘?’

With Rails 1.2, you of course get:

‘€2.99’.first # => ‘€’ truncate(‘€2.99’, 2) # => ‘€2’

TextHelper#truncate/excerpt and String#at/from/to/first/last automatically does the .chars conversion, but if when you need to manipulate or display length yourself, be sure to call .chars. Like:

You’ve written <%= @post.body.chars.length %> characters.

With Rails 1.2, we’re assuming that you want to play well with unicode out the gates. The default charset for action renderings is therefore also UTF-8 (you can set another with ActionController::Base.default_charset=(encoding)). KCODE is automatically set to UTF-8 as well.

Watch the screencast. (but note that manually setting the KCODE is no longer necessary)

Unicode was in greatest demand, but Multibyte is ready handle other encodings (say, Shift-JIS) as they are implemented. Please extend Multibyte for the encodings you use.

Thanks to Manfred Stienstra, Julian Tarkhanov, Thijs van der Vossen, Jan Behrens, and (others?) for creating this library.

Gotchas

While we’ve tried our best to remain as backwards compatible with 1.1.6 as possible, there are a few minor edge cases that will need some rework if you used to do things a certain way.

Routes

Action Pack has an all new implementation of Routes that’s both faster and more secure, but it’s also a little stricter. Semicolons and periods are separators, so a /download/:file route which used to match /download/history.txt doesn’t work any more. Use :requirements => { :file => /.*/ } to match the period.

Auto-loading

We’ve fixed a bug that allowed libraries from Ruby’s standard library to be auto-loaded on reference. Before, if you merely reference the Pathname constant, we’d autoload pathname.rb. No more, you’ll need to manually require 'pathname' now.

We’ve also improved the handling of module loading, which means that a reference for Accounting::Subscription will look for app/models/accounting/subscription.rb. At the same time, that means that merely referencing Subscription will not look for subscription.rb in any subdir of app/models. Only app/models/subscription.rb will be tried. If you for some reason depended on this, you can still get it back by adding app/models/accounting to config.load_paths in config/environment.rb.

Prototype

To better comply with the HTML spec, Prototype’s Ajax-based forms no longer serialize disabled form elements. Update your code if you rely on disabled field submission.

For consistency Prototype’s Element and Field methods no longer take an arbitrary number of arguments. This means you need to update your code if you use Element.toggle, Element.show, Element.hide, Field.clear, and Field.present in hand-written JavaScript (the Prototype helpers have been updated to automatically generate the correct thing).


// if you have code that looks like this
Element.show('page', 'sidebar', 'content');
// you need to replace it with code like this
['page', 'sidebar', 'content'].each(Element.show);

Action Mailer

All emails are MIME version 1.0 by default, so you’ll have to update your mailer unit tests: @expected.mime_version = '1.0'

Deprecation

Since Rails 1.0 we’ve kept a stable, backward-compatible API, so your apps can move to new releases without much work. Some of that API now feels like our freshman 15 so we’re going on a diet to trim the fat. Rails 1.2 deprecates a handful of features which now have superior alternatives or are better suited as plugins.

Deprecation isn’t a threat, it’s a promise! These features will be entirely gone in Rails 2.0. You can keep using them in 1.2, but you’ll get a wag of the finger every time: look for unsightly deprecation warnings in your test results and in your log files.

Treat your 1.0-era code to some modern style. To get started, just run your tests and tend to the warnings.

Installing

The release candidate gems live in the Rails gem repository. You install them like this:

gem install rails —source http://gems.rubyonrails.org —include-dependencies

Note that it’ll say something like “Successfully installed rails-1.1.6.5618”. That’s correct as we won’t use the final version numbers until the official release.

You can also grab it straight from Subversion with http://dev.rubyonrails.org/svn/rails/tags/rel_1-2-0_RC1.

Submitting regression bugs

There you have it. Those are the major changes and as always, you can get the full, nitty-gritty scoop in the CHANGELOGs. Over the last eight months, we’ve made literaly hundreds of improvements. It’s well worth traversing the CHANGELOGs for goodies. Ryan’s Scraps is doing a good job annotating the changes as well.

But with the release of any new piece of software, a number of things which used to work, will work no longer.

While the intention with Rails 1.2 is to provide seamless backwards compatibility, we’re only human, and chances are a few things have snuck through. So if you’re trying out the 1.2 release candidate, and find a bug, be sure to report it to us. There are a few steps you should follow to help us fix your bug during the release canididate cycle.

When adding your bug report, be sure to put ‘1.2regression’ in the keywords field. Bugs with this keyword show up in a trac report, if you’re looking for a place to help out, start there.

If at all possible, please include a failing unit test with your bug report. This makes our life significantly easier, and helps others verify that you’ve found a genuine case.

The Rails Way on premature extraction

Koz and Jamis has posted their first article on The Rails Way covering premature extraction. It’s a great way to start off the show of Rails learnings. Since it’s so easy to abstract and extract in Ruby, it’s ever so tempting to begin doing so before you have more than one instance to triangulate the best approach with.