RSS Ryan's Scraps - Blog

http://ryandaigle.com/

Thursday October 23rd, 2008

1.5

What's New in Edge Rails: You Smell That?

From Ryan's Scraps - Blog, 2 months ago, 0 comments Comment

... smells like Rails v2.2 is right around the corner…

tags: ruby, rubyonrails

Wednesday September 24th, 2008

7.6

What's New in Edge Rails: Nested Model Mass Assignment

From Ryan's Scraps - Blog, 3 months ago, 0 comments Comment

Looks like this feature has been pulled from Edge Rails though it’s tentatively scheduled to come back post the 2.2 release.

Nested models (nested forms by another name) describe the scenario when you want to create and modify values of nested attributes through a containing model. For instance, if you have an user model with many phone numbers:

1
2
3
4
5
6
7
8
9
class User < ActiveRecord::Base
  validates_presence_of :login
  has_many :phone_numbers
end

class PhoneNumber < ActiveRecord::Base
  validates_presence_of :area_code, :number
  belongs_to :user
end

you may want to be able to create the user and a group of phone numbers at the same time. This is what this looks like with the new mass assignment functionality of Rails keyed off of the :accessible option of the association declaration (:phone_numbers, in this case).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class User < ActiveRecord::Base
  validates_presence_of :login
  has_many :phone_numbers, :accessible => true
end

ryan = User.create( {
  :login => 'ryan',
  :phone_numbers => [
    { :area_code => '919', :number => '123-4567' },
    { :area_code => '920', :number => '123-8901' }
  ]
})

ryan.phone_numbers.count #=> 2

A single hash of values being sent to User.create results in both a new user object and new associated phone numbers. Previously, you would have had to manually create your own phone_numbers= setter method on user to get this same functionality:

1
2
3
4
5
6
7
8
9
10
11
class User < ActiveRecord::Base

  ...

  def phone_numbers=(attrs_array)
    attrs_array.each do |attrs|
      phone_numbers.create(attrs)
    end
  end

end

Mass assignment now gives you this functionality for free.

This may not look like much, but it is a step in the direction of letting you use nested forms. Consider a user registration form where a user can enter their login name and their phone numbers in the same form (through the use of fields_for which will bundle nested model attributes into a single form):

1
2
3
4
5
6
7
8
<% form_for @user do |f| %>
  <%= f.text_field :login %>
  <% fields_for :phone_numbers do |pn_f| %>
    <%= pn_f.text_field :area_code %>
    <%= pn_f.text_field :number %>
  <% end %>
  <%= submit_tag %>
<% end %>

This form, when submitted to the following standard RESTful UserController, will correctly create the user and its associated phone numbers through the beauty of mass assignment.

1
2
3
4
5
6
7
8
9
10
class UserController < ApplicationController

  # Create a new user and their phone numbers with mass assignment
  def new
    @user = User.create(params[:user])
    respond_to do |wants|
      ...
    end
  end
end

Mass assignment can be used on all association types – :belongs_to, :has_one, :has_many and :has_and_belongs_to_many as long as the :accessible => true option is specified.

This is a very convenient addition to ActiveRecord, but the real zinger will come with full nested form support when you can create, update and delete these nested models directly from what is pushed down in the parameter hash of a form submission. This would allow for the functionality in this complex forms screencast with minimal hassle. What a fine day that would be.

tags: ruby, rubyonrails

Sunday September 7th, 2008

3.4

What's New in Edge Rails: Connection Pools

From Ryan's Scraps - Blog, 4 months ago, 0 comments Comment

With the upcoming thread-safety of Rails comes the necessity of database connection pools. While less a feature and more a hidden implementation detail, you do have the ability to adjust the default size of these new pools. In your database.yml configuration file just set the pool key:

1
2
3
4
5
development:
  adapter: mysql
  username: root
  database: myapp_dev
  pool: 10

So there ya go – connection pools. That was easy…

tags: ruby, rubyonrails

3.3

What's New in Edge Rails: Mailer Layouts

From Ryan's Scraps - Blog, 4 months ago, 0 comments Comment

Rails mailer templates now have the ability to utilize layouts just like their view-template siblings. The only caveat is that for a mailer layout to be automatically recognized it must end with _mailer. So this mailer:

1
2
3
4
5
6
class UserMailer < ActionMailer::Base
  def registration(user)
    subject    "You've registered"
    from       "system@example.com"
  end
end

would need a layout at layouts/user_mailer.html.erb. If you explicitly specify the layout it doesn’t need the _mailer suffix. So, in this example, a layout at layouts/email.html.erb would suffice:

1
2
3
4
class UserMailer < ActionMailer::Base
  layout 'email'
  ...
end

Go forth and make your emails pretty. You’ve got all the tools now…

tags: ruby, rubyonrails

6.9

What's New in Edge Rails: Shallow Routes

From Ryan's Scraps - Blog, 4 months ago, 0 comments Comment

Rails’ routing mechanism is pretty slick. In a very intuitive way you’re able to describe the resources you want exposed at the URL level with this routing-DSL:

1
2
3
4
5
map.resources :users do |user|
  user.resources :articles do |article|
    article.resourcs :comments
  end
end

However, while this configuration makes articles available at /users/1/articles and comments at /users/1/articles/1/comments there are often times when you want to bypass the full nested hierarchy and directly access the resource in question. Now, with the shallow route option, you can.

1
2
3
4
5
map.resources :users, :shallpow => true do |user|
  user.resources :articles do |article|
    article.resourcs :comments
  end
end

This configuration keeps all the normal nested routes and give you direct routes as well:

1
2
3
articles_path #=> '/articles'
comments_path #=> '/comments'
article_comments_path(@article) #=> '/articles/1/comments'

No longer do you need to separately declare direct routes in addition to nested routes, the :shallow option automatically make all necessary routes.

tags: ruby, rubyonrails

Wednesday August 20th, 2008

3.8

Named Scope: It's Not Just for Conditions, Ya Know?

From Ryan's Scraps - Blog, 4 months ago, 0 comments Comment

Named scopes in Rails are great, everybody knows that. They’re usually used to create granular, chainable sets of SQL conditions that nicely encapsulate your domain query logic. Here’s a simple example:

1
2
3
4
5
6
7
8
9
10
11
12
class Article < ActiveRecord::Base
  
  # Get all articles that have been published
  named_scope :published, :conditions => ['published = ?', true]

  # Get all articles that were created recently
  named_scope :recent, lambda { { :conditions => ['created_at >= ?', 1.week.ago] } }

end

# Get all recently created articles that have been published
Article.published.recent #=> [<Article id: ...>, <..>]

However, as much as I use named_scope for this purpose, I also use it for some smaller and still useful functions. For instance, I find that I often need to just fetch the first X number of results for any particular query. Instead of having to call find with the :limit option you could create the following named_scope:

1
2
3
4
5
6
7
8
9
class Article < ActiveRecord::Base
  
  # Only get the first X results
  named_scope :limited, lambda { |num| { :limit => num } }

end

# Get the first 5 articles - instead of Article.find(:all, :limit => 5)
Article.limited(5) #=> [<Article id: ...>, <..>]

Hey, any less typing I’ll take, and I find myself using this limited named_scope a lot. But let’s pimp it a little so that you don’t always have to supply the number, and make it default to the per_page value that exists on the class if you’re using will_paginate.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Article < ActiveRecord::Base
  
  # Only get the first X results.  If no arg is given then try to
  # use the per_page value that will_paginate uses.  If that
  # doesn't exist then use 10
  named_scope :limited, lambda { |*num|
    { :limit => num.flatten.first || (defined?(per_page) ? per_page : 10) }
  }

  def per_page; 15; end

end

# Get the first 15 articles
Article.limited #=> [<Article id: ...>, <..>]

# Get the first 5 articles
Article.limited(5) #=> [<Article id: ...>, <..>]

Note that we have to use the variable length *num argument in the lambda to allow for no arguments.

Cool, so we’ve got a handy little tool for our toolbox now. Here’s another one I find myself using that isn’t strictly a conditional scope – ordered:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Article < ActiveRecord::Base
  
  # Order the results by the given argument, or 'created_at DESC'
  # if no arg is given
  named_scope :ordered, lambda { |*order|
    { :order => order.flatten.first || 'created_at DESC' }
  }

end

# Get all articles ordered by 'created_at DESC'
Article.ordered #=> [<Article id: ...>, <..>]

# Get all articles ordered by 'updated_at DESC'
Article.ordered('updated_at DESC') #=> [<Article id: ...>, <..>]

I’ve bundled these scopes up into a “utility scopes:http://github.com/yfactorial/utility_scopes” plugin/gem if you think they look useful to you. I’ve also added some class-level convenience initializers to let you override the default values (like the default limit and default order clause):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Article < ActiveRecord::Base

  # This class's default ordering (if not specified
  # defaults to 'created_at DESC'
  ordered_by 'published_at DESC'
  
  # By default, return 15 results (if not specified
  # defaults to 10
  default_limit 15

end

# Get the first 15 articles ordered by 'published_at DESC'
Article.ordered.limited #=> [<Article id: ...>, <..>]

# Get the first 15 articles ordered by 'popularity ASC'
Article.ordered('popularity ASC').limited #=> [<Article id: ...>, <..>]

# Get the first 20 articles ordered by 'popularity ASC'
Article.ordered('popularity ASC').limited(20) #=> [<Article id: ...>, <..>]

Need a little something else? How about the with scope which will eager load the specified associations:

1
2
3
4
5
6
7
class Article < ActiveRecord::Base
  has_many :comments
  has_many :contributors, :class_name => 'User'
end

# Get the first 10 articles along with their comments, comment authors and article contributors
Article.limit(10).with({ :comments => :author }, :contributors)

You can get all these goodies yourself by doing the following in your Rails 2.1 app. In config/environment.rb specify the gem dependency:

1
2
3
4
5
Rails::Initializer.run do |config|
  # ...
  config.gem "yfactorial-utility_scopes", :lib => 'utility_scopes', 
    :source => 'http://gems.github.com/'
end

And then to get the utility_scopes gem actually installed on your system:


rake gems:install GEM=yfactorial-utility_scopes

Or you can just install the gem as you normally would:


sudo gem install yfactorial-utility_scopes -s http://gems.github.com

Independent of whether or not you find these scopes useful, remember that named_scope is all up in your queries’ bidness – not just your queries’ conditions

Have some utility scopes you find to be indispensable? Let me know here or send me a request on github (user is yfactorial).

tags: ruby, rubyonrails

Thursday August 14th, 2008

5.6

What's New in Edge Rails: Simpler Conditional Get Support (ETags)

From Ryan's Scraps - Blog, 4 months ago, 0 comments Comment

Conditional-gets are a facility of the HTTP spec that provide a way for web servers to tell browsers that the response to a GET request hasn’t changed since the last request and can be safely pulled from the browser cache.

They work by using the HTTP_IF_NONE_MATCH and HTTP_IF_MODIFIED_SINCE headers to pass back and forth both a unique content identifier and the timestamp of when the content was last changed. If the browser makes a request where the content identifier (etag) or last modified since timestamp matches the server’s version then the server only needs to send back an empty response with a not modified status.

It is the server’s (i.e. our) responsibility to look for a last modified timestamp and the if-none-match header and determine whether or not to send back the full response. With this new conditional-get support in rails this is a pretty easy task:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ArticlesController < ApplicationController

  def show
    @article = Article.find(params[:id])

    # Set the response headers to accurately reflect the state of the
    # requested object(s)
    response.last_modified = @article.published_at.utc
    response.etag = @article

    # If the request's state is the same as the server's state then we know
    # we don't have to send back the whole body
    if request.fresh?(response)
      head :not_modified
    else
      respond_to do |wants|
        # normal response processing
      end
    end
end

The etag value is calculated for you with the etag= setter method. All you have to do is provide a single object or array of objects that uniquely identify this request. In this example the article itself contains all the information that uniquely identifies the state of this request. However, you may need to use more than one key in your app. For instance, if the request is user specific:


response.etag = [@article, current_user]

The request.fresh?(response) method is what will then tell you if the incoming request matches either the last-modified-since or if-none-match values of the outgoing response. If it does you can avoid passing the full body of the response back and save some bandwidth.

It’s also possible that you can avoid hitting the database all together if your application deals with completely static resources (though this is rare):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class ArticlesController < ApplicationController

  def show

    # If articles don't change, the etag can be based solely
    # on items we have in the request
    response.etag = [:article, params[:id]]

    # If the request's state is the same as the server's state then we can
    # avoid the db call all together
    if request.fresh?(response)
      head :not_modified
    else
      @article = Article.find(params[:id])
      respond_to do |wants|
        ...
      end
    end
end

So be a good citizen and make your requests conditional-get compatible. It’s the right thing to do – and can make your apps more performant.

tags: ruby, rubyonrails

Tuesday July 22nd, 2008

4.2

What's New in Edge Rails: Standard Internationalization Framework

From Ryan's Scraps - Blog, 5 months ago, 0 comments Comment

Internationalization (i18n) is a tough nut to crack and has long been handled in Rails by a variety of plugins. Thanks to a concerted effort by the Rails i18n team we now have a standardized framework for providing internationalization.

Rather than rehash what’s already out there, head over to Sven’s writeup of the new Rails Internationalization framework for the skinny.

It’s important to note that this does not provide actual language translations but merely a way for you to plug in other translations and internationalization implementations into your app.

tags: ruby, rubyonrails

Saturday July 19th, 2008

6.6

What's New in Edge Rails: Get Your Metaclass

From Ryan's Scraps - Blog, 5 months ago, 0 comments Comment

The ActiveSupport library now has a little nugget worth mentioning – a metaclass accessor available everywhere as metaclass.

Not sure what a metaclass (or singleton class, or eigen class) is? Let Ola explain it to you.

tags: ruby, rubyonrails

Wednesday July 16th, 2008

9.0

What's New in Edge Rails: Easy Memoization

From Ryan's Scraps - Blog, 5 months ago, 0 comments Comment

Most people will recognize the pattern of memoization to provide a basic caching mechanism (that’s not a misspelling, it really doesn’t have an ‘r’) :

1
2
3
4
5
6
class Person < ActiveRecord::Base
  def social_security
    @social_security ||= decrypt_social_security
  end
  ...
end

The big problem with this common type of memoization is that you’ve littered your method implementation with caching logic. Caching is best applied in a transparent manner – and ActiveSupport now lets you easily insert memoization into your classes:

1
2
3
4
5
6
7
8
9
10
11
class Person < ActiveRecord::Base

  # Memoize the result of the social_security method after
  # its first evaluation
  memorize :social_security

  def social_security
    decrypt_social_security_for
  end
  ...
end

memorize transparently aliases the method and stores the value of your method’s first evaluation in an instance variable – giving you the same functionality of the unrefined var ||= ... implementation with much less clutter. So start giving memorize some play – it’s just the right thing to do.

tags: ruby, rubyonrails

Monday July 7th, 2008

5.0

What's New in Edge Rails: Easy Join Table Conditions

From Ryan's Scraps - Blog, 6 months ago, 0 comments Comment

For an application with anything above a moderate level of domain complexity it’s quite likely that you’ve had to perform a query utilizing a join table:

1
2
3
4
5
6
7
8
9
10
11
class Article < ActiveRecord::Base
  belongs_to :user
end

class User < ActiveRecord::Base
  has_many :articles
end

# Get all the users that have published articles
User.find(:all, :joins => :article,
  :conditions => ["articles.published = ?", true])

It always makes me feel slightly embarrassed to have to resort to using String snippets to specify my query logic, and this little bit is no exception. Well, now we can specify conditions on a join table in a more concise manner:

1
2
3
# Get all the users that have published articles
User.find(:all, :joins => :article,
  :conditions => { :articles => { :published => true } })

Note how you’re able to specify the join-table conditions as a hash whose key corresponds to the table or association name of the join table? You can now let Rails worry about forming the correct SQL condition even across complex joins.

However, don’t let the ease of this feature make you use it over a properly associated domain-model. For instance, this join query:

1
2
3
# Get all articles for a given user
Article.find(:all, :joins => :user,
  :conditions => { :users => { :id => @user.id } })

is more appropriately represented as:


@user.articles

tags: ruby, rubyonrails

4.6

What's New in Edge Rails: Custom Length Validation Tokenizer

From Ryan's Scraps - Blog, 6 months ago, 0 comments Comment

The various ActiveRecord validation methods are some of the hardest working bits of ActiveRecord and yet they get so little love. In what may be a little-noticed change, you can now specify how validates_length_of parses the attribute value. While the default behavior is to just count the number of characters in the attribute value, you can now also do something like this to make sure the value has a minimum number of words:

1
2
3
validates_length_of :article, :minimum => 10,
  :too_short => "Your article must be at least %d words in length.",
  :tokenizer => lambda {|str| str.scan(/\w+/) }

Just pass a proc to the :tokenizer option that chops the attribute value into its relevant parts for measure by the length validation routine. You can do most anything, like ensure a minimum or maximum number of vowels, digits etc…

tags: ruby, rubyonrails

4.2

What's New in Edge Rails: Collection Partial Variable Naming

From Ryan's Scraps - Blog, 6 months ago, 0 comments Comment

From the little-but-useful department comes a new addition to Rails that lets you explicitly name the local variable exposed to a partial template when using a collection partial. So, for instance, in this statement:


render :partial => 'employees', :collection => @workers, :as => :person

each element of the workers collection will be exposed as person within the employees template. No longer are you hostage to the singular inflection of your collection name.

tags: ruby, rubyonrails

Thursday June 12th, 2008

3.8

Rails 2 Peepcode Updated w/ 2.1 Features

From Ryan's Scraps - Blog, 7 months ago, 0 comments Comment

Though I’m a little late to the party – the Rails2 Peepcode has just been re-released with write-ups of the major new features in Rails 2.1. If you’ve already purchased the Rails2 Peepcode go on over and download your free update. If you have yet to experience the goodness of this high-quality, professionally produced and detailed publication – isn’t it time you did?

This release also features a section on staying on edge Rails with the git SCM and github.com. Lots of relevant goodies!

tags: ruby, rubyonrails

Monday June 2nd, 2008

4.6

Rails 2.1 Released - Summary of Features

From Ryan's Scraps - Blog, 7 months ago, 0 comments Comment

Those of you that have purchased the Rails 2 Peepcode will get a free update with these features explained shortly. I’ll post an update when that happens.

Rails 2.1 was released over the weekend during RailsConf. As always, here’s a list of the latest and greatest features in this release:

Major Rails 2.1 Features and Changes

(sorted by date added – most recent first)

Chow down.

tags: ruby, rubyonrails

« older items