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

All the new OSs and windowing systems are oriented towards content consumption instead of content production.

--Steve Daniels 2013

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?

All the new OSs and windowing systems are oriented towards content consumption instead of content production.

--Steve Daniels 2013

Webinar
One Click, Universal Protection: Implementing Centralized Security Policies on Linux Systems

As Linux continues to play an ever increasing role in corporate data centers and institutions, ensuring the integrity and protection of these systems must be a priority. With 60% of the world's websites and an increasing share of organization's mission-critical workloads running on Linux, failing to stop malware and other advanced threats on Linux can increasingly impact an organization's reputation and bottom line.

Learn More

Sponsored by Bit9

Webinar
Linux Backup and Recovery Webinar

Most companies incorporate backup procedures for critical data, which can be restored quickly if a loss occurs. However, fewer companies are prepared for catastrophic system failures, in which they lose all data, the entire operating system, applications, settings, patches and more, reducing their system(s) to “bare metal.” After all, before data can be restored to a system, there must be a system to restore it to.

In this one hour webinar, learn how to enhance your existing backup strategies for better disaster recovery preparedness using Storix System Backup Administrator (SBAdmin), a highly flexible bare-metal recovery solution for UNIX and Linux systems.

Learn More

Sponsored by Storix