Last checked about 1 hour ago.
59 people have subscribed to this feed.
post frequency (last month)
PostRank™
From Ryan's Scraps - Blog, 13 days ago,
0 comments
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
From Ryan's Scraps - Blog, 1 month ago,
0 comments
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
From Ryan's Scraps - Blog, 1 month ago,
0 comments
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
From Ryan's Scraps - Blog, 1 month ago,
0 comments
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
From Ryan's Scraps - Blog, 1 month ago,
0 comments
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
From Ryan's Scraps - Blog, 1 month ago,
0 comments
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
From Ryan's Scraps - Blog, 2 months ago,
0 comments
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
From Ryan's Scraps - Blog, 2 months ago,
0 comments
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
From Ryan's Scraps - Blog, 2 months ago,
0 comments
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
From Ryan's Scraps - Blog, 3 months ago,
0 comments
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
From Ryan's Scraps - Blog, 3 months ago,
0 comments
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
From Ryan's Scraps - Blog, 3 months ago,
0 comments
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
From Ryan's Scraps - Blog, 3 months ago,
0 comments
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
From Ryan's Scraps - Blog, 4 months ago,
0 comments
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:
(sorted by date added – most recent first)
Chow down.
tags: ruby, rubyonrails
From Ryan's Scraps - Blog, 4 months ago,
0 comments
For those of you that have run into this gem error when running the latest rails:
./script/../config/../vendor/rails/railties/lib/initializer.rb:175:in `install_gem_spec_stubs': undefined method `loaded_specs' for Gem:Module (NoMethodError)
from ./script/../config/../vendor/rails/railties/lib/initializer.rb:175:in `reject!'
from ./script/../config/../vendor/rails/railties/lib/initializer.rb:175:in `install_gem_spec_stubs'
from ./script/../config/../vendor/rails/railties/lib/initializer.rb:89:in `send'
from ./script/../config/../vendor/rails/railties/lib/initializer.rb:89:in `run'
from ./script/../config/boot.rb:46:in `load_initializer'
from ./script/../config/boot.rb:38:in `run'
from ./script/../config/boot.rb:11:in `boot!'
from ./script/../config/boot.rb:109
from script/dbconsole:2:in `require'
from script/dbconsole:2
the solution is a quick and dirty:
gem update --system
Or, if you currently have rubygems v0.8.4 or earlier installed:
gem install rubygems-update
update_rubygems
Just a public service announcement from the “Ryan’s been bitten by this one” dept.