Rails Writ Large

Ruby on Rails 1.1 and the paradox of how constraints can lead to greater freedom.

which generates this JavaScript:

var items = $$('#items li').collect(function(value, index)
 ↪{ return value.hide(); });

In addition to having .rjs files in your views directory, you also can write inline RJS. For example:


def create
  # (handle action)
  render :update do |page|
	  page.insert_html :bottom, :list, '<li>Last item</li>'
	  page.visual_effect :highlight, 'list'
  end
end

Of course, you don't want to pollute your controllers with a lot of view-specific code, so you also can 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

Debugging RJS can be tricky, because if a Ruby exception occurs, no error will be visible in the browser. To get around that, set config.action_view.debug_rjs = true, and you'll be notified of RJS exceptions via alert().

You may have noticed that the output of the RJS templates makes use of a great new feature of Prototype: methods of the Element class are mixed into all HTML elements that are referenced by $() and $$(). That means instead of writing Element.show('foo'), you now can write $('foo').show(). It's a small change that makes writing JavaScript code more natural and Ruby-like. The methods available are visible(), toggle(), hide(), show(), visualEffect(), remove(), update(html), replace(html), getHeight(), classNames(), hasClassName(className), addClassName(className), removeClassName(className), cleanWhitespace(), empty(), childOf(ancestor), scrollTo(), getStyle(style), setStyle(style), getDimensions(), makePositioned(), undoPositioned(), makeClipping() and undoClipping().

Ruby-generated JavaScript also uses another fantastic new feature of Prototype, the Selector class and its corresponding $$() function. Like the $() function, $$() is used to reference HTML elements, but this one matches elements by CSS selector strings. 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'});
});

If you're not convinced by now, take it from me, RJS and the new additions to Prototype will revolutionize the way Ajax is done in Rails.

Rich Domain Models in ActiveRecord

So far, we've looked at advancements in the controller and view layers of Rails. Let's turn now to ActiveRecord, which also got a lot of love in this release. First up, a new type of association.

Prior to 1.1, Rails supported many-to-many relationships with has_and_belongs_to_many. For example:


class Author < ActiveRecord::Base
  has_and_belongs_to_many :books
end
class Book < ActiveRecord::Base
  has_and_belongs_to_many :authors
end

That works fine, to a point. The difficulty comes in when you need data or behavior for the association itself. The solution is to make an explicit join model for the association. Take a look at this alternative:


class Author < ActiveRecord::Base
  has_many :authorships
  has_many :books, :through => :authorships
end
class Authorship < ActiveRecord::Base
  belongs_to :author
  belongs_to :book
end
class Book < ActiveRecord::Base
  has_many :authorships
  has_many :authors, :through => :authorships
end
Author.find(:first).books.find(:all, :include => :reviews)

The new :through option of has_many allows you to specify an explicit association join model, so you can have the ease of has_and_belongs_to_many but get full power of ActiveRecord for the Authorship model.

The :through option also can be used where the intermediate association is a has_many. 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

Without the :through option, getting all invoices for a firm would require multiple SQL hits to the database or a custom SQL query. Now, ActiveRecord handles the join automatically and leaves a clean API to access the associations.

Another new association option that further enriches your domain models is polymorphic associations. This solves the problem of having a model that could share relationships with multiple other models. With polymorphic associations, the model defines an abstract association, which can represent any other model, and ActiveRecord keeps track of the details. Take a look at this 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

Any developer experienced with SQL has run into the “n+1 queries” problem, where looking up a set of records, each with a related record, causes a large number of queries to the database. The solution is SQL JOIN statements, but writing them by hand quickly gets complicated, especially after more than one join. Rails 1.1 significantly reduces that pain, with cascading, bottomless eager loading. Now, queries like Author.find(:all, :include=> { :posts => :comments }) 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 } }

The next major new feature of ActiveRecord is nested with_scope. This feature allows your dealings with ActiveRecord objects to be more clearly understood—especially important for code with security implications. Here's an example:


Developer.with_scope :find => { :conditions => "salary > 10000", :limit => 10 } do

  # SELECT * FROM developers WHERE (salary > 10000) LIMIT 10:
  Developer.find :all

  # parameters are merged
  Developer.with_scope :find => { :conditions => "name = 'Jamis'" } do
    # SELECT * FROM developers WHERE (( salary > 10000 ) AND ( name = 'Jamis' )) LIMIT 10
    Developer.find :all
  end

  # inner rule is used. (all previous parameters are ignored)
  Developer.with_exclusive_scope :find => { :conditions => "name = 'Jamis'" } do
    # SELECT * FROM developers WHERE (name = 'Jamis'):
    Developer.find :all
  end

end

The last major addition to ActiveRecord provides convenient syntax for accessing calculations and statistics, without writing custom SQL. For example:

Person.count
Person.count   :conditions => "age > 26"
Person.count   :include => :job, :conditions => "age > 26 AND job.salary > 60000"
Person.average :age
Person.maximum :age
Person.minimum :age, :having => "min(age) > 17", :group => :last_name
Person.sum     :salary, :group => :last_name

______________________

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

toggle's code doesn't work for me

Bart's picture

point 8) code doesn't work for me. Browser (FF/IE) complains with RJS error (RerefenceError:'blind_down' is not defined). Here are my *.rjs files

caused problem:
page.visual_effect :toggle_blind, 'element', :effect => 'blind_down'

works fine:
page.visual_effect :toggle_blind, 'element'

Bart

Minor bug in RJS sample

Bill Mitchell's picture

www.iconbuffet.com/products/amsterdam

Click "add to cart", then click "remove from cart". Now try to click "add to cart." You can't because the buttons still say "remove." Buttons fail to refresh after removing from cart. Must reload page to see the add to cart button again.

This page displays too wide, requires horizontal scrolling for e

Randy Kramer's picture

This page displays too wide, requires horizontal scrolling for each line. I haven't tried to pinpoint the cause of the trouble.

You should limit lines to something around 80 characters, and/or make them (lines) wrap to the width of the page.

This page displays too wide

Keith Daniels's picture

I can't replicate this problem. I tested at 800x600 resolution and though the gray code blocks had horizontal scroll bars none of the body text was outside of the screen.

What browser are you using, what screen resolution and what operating system. It would also help if you would check that you have not changed any of you browser settings so that they override the style sheets on the web site.

Webmaster
Linux Journal

"I have always wished that my computer would be as easy to use as my telephone.
My wish has come true. I no longer know how to use my telephone."
-- Bjarne Stroustrup

print version runs off the line

bumparocky's picture

too wide for printing also....
print preview in the latest firefox, of the printer friently version, shows the lines getting chopped off

rocky

print version runs off the line

Keith Daniels's picture

Re: too wide for printing also....

Does the "also" imply that the horizontal scroll bars appear as well -- during regular viewing?

I too have the latest version of FireFox and the priter friendly version looks fine in FireFox's print preview.

The print preview of the regular web page does look funny but I think that is a FireFox problem dealing with complex multi column pages instead of a site problem.

Are you using the Windows version of FireFox? If not which Linux version are you using and what is the FireFox version?

"I have always wished that my computer would be as easy to use as my telephone.
My wish has come true. I no longer know how to use my telephone."
-- Bjarne Stroustrup

White Paper
Linux Management with Red Hat Satellite: Measuring Business Impact and ROI

Linux has become a key foundation for supporting today's rapidly growing IT environments. Linux is being used to deploy business applications and databases, trading on its reputation as a low-cost operating environment. For many IT organizations, Linux is a mainstay for deploying Web servers and has evolved from handling basic file, print, and utility workloads to running mission-critical applications and databases, physically, virtually, and in the cloud. As Linux grows in importance in terms of value to the business, managing Linux environments to high standards of service quality — availability, security, and performance — becomes an essential requirement for business success.

Learn More

Sponsored by Red Hat

White Paper
Private PaaS for the Agile Enterprise

If you already use virtualized infrastructure, you are well on your way to leveraging the power of the cloud. Virtualization offers the promise of limitless resources, but how do you manage that scalability when your DevOps team doesn’t scale? In today’s hypercompetitive markets, fast results can make a difference between leading the pack vs. obsolescence. Organizations need more benefits from cloud computing than just raw resources. They need agility, flexibility, convenience, ROI, and control.

Stackato private Platform-as-a-Service technology from ActiveState extends your private cloud infrastructure by creating a private PaaS to provide on-demand availability, flexibility, control, and ultimately, faster time-to-market for your enterprise.

Learn More

Sponsored by ActiveState