Feb 27, 2006 | 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.
- Railties
- The new default schema format is
:rubyinstead 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/--repeatoption to script/process/spawner. - The new
-c/--configoption 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, andappis 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_fixturesis nowdb: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. Runrake --tasksto 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:uncommittedtests 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.
- 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.secondsinstead of5.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!
- ActiveRecord
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
- Polymorphic Associations. See Understanding Polymorphic Associations. For example:
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
findcalls onhas\_and\_belongs\_to\_manyandhas\_manyassosociations. 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\_ofto be scoped by multiple columns. See this. - ActiveRecord::Errors now mixes-in Enumerable.
- The
:exclusively\_dependentoption has been deprecated in favor of:dependent => :delete_all. - The
.find()method, and thehas\_and\_belongs\_to\_manyandhas\_manyassociations, now all take:group,:limit,:offset, and:selectoptions. - 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\_ofnow works on UTF-8 strings—it counts characters instead of bytes.
- 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
pageobject 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', '<li>Last item</li>' - 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:
<< - 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 viaalert()(setconfig.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 }generatesvar items = $$('#items li').collect(function(value, index) { return value.hide(); });
- Pop an alert() dialog:
- 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_tolets 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:
<% form_for :person => @person, :url => { :action => "update" } do |f| %>
First name: <%= f.text_field :first_name %>
Last name : <%= f.text_field :last_name %>
Biography : <%= f.text_area :biography %>
Admin? : <%= f.check_box :admin %>
<% end %>
<% form_for :person => person, :url => { :action => "update" } do |person_form| %>
First name: <%= person_form.text_field :first_name %>
Last name : <%= person_form.text_field :last_name %>
<% fields_for :permission => person.permission do |permission_fields| %>
Admin? : <%= permission_fields.check_box :admin %>
<% end %>
<% end %>
form_forand friends can take a * option, where you can pass a custom subclass of FormBuilder. For example:
<% form_for :person, @person, :url => { :action => "update" }, :builder => LabellingFormBuilder do |f| %>
<%= f.text_field :first_name %>
<%= f.text_field :last_name %>
<% end %>
- 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 likerender(:text => text), but sets the content-type to application/xml and the charset to UTF-8.
- Added
:content_typeoption torender, 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\_forandcapturenow work in .rxml (and any non-rhtml template). See this.visual\_effectsupports scoped queues. See this.observe\_fieldnow 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\_functionwill now honor existing:onclickdefinitions when adding the function call.submit\_tagnow has a h option to change the text of disabled submit buttons.visual\_effectcan now toggle visual effects. See this.auto\_complete\_fieldnow has a t option for to only use part of the auto-complete suggestion as the value for insertion.
- 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 ofElement.show('foo'). - Several new methods added to String:
truncate,gsub,sub,scan, andstrip. - 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.
- 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.
