I got tired of digging through migrations, so here’s a simple console navigator. The instructions are all in there.
healthy man viagra reviewsviagra online without prescriptionRails 3 Reading Material
Rails 3 is going to bring a lot of new stuff to the table. Wouldn’t hurt to organize some reading material in categorized and chronological order. So here goes whatever I scavenged so far. Leave comments so I can add more links or if I should mark something as obsolete.
Router
- cialis 5 mg pricecialis onlineRevamped Routes in Rails 3 (12/20/09) — Examples of usage of the Rails 3 router.
- Generic Actions in Rails 3 (12/20/09) — Interesting things you can do with Rails 3 routes.
- The Rails 3 Router: Rack it Up (12/26/09) — A description of important design decisions made for the new Rails 3 router.
ActiveRecord
- ActiveModel: Make Any Ruby Object Feel Like ActiveRecord (01/10/10) — Learn about the ActiveModel API.
- Let your SQL Growl in Rails 3 (01/17/10) — A neat trick and an example on how to use Rails 3 subscription models.
- Active Record Query Interface 3.0 (01/22/10) – The new ActiveRecord API (based on ARel) thoroughly explained.
Controllers
- Dynamic session expiration time in Rails 3 (01/16/10) — An insight on how Rails 3 handles session cookies and a convenience plugin.
- Render Options in Rails 3 (01/18/10) — Those
rendermethods you call in your controllers: learn about their options in Rails 3.
ActionMailer
- New ActionMailer API in Rails 3.0 (01/26/10) — ActionMailer now shares lots of logic with controllers, learn its new API.
Views
- SafeBuffers and Rails 3.0 (02/01/10) — Learn about html_safe strings in Rails 3.0.
Generators
- Making Generators for Rails 3 with Thor (01/15/10) — A thorough tutorial on how to create, gemify, and test Rails 3 generators.
- Discovering Rails 3 generators (01/18/10) — A few important points to know about Rails 3 generators.
Running Rails 3 App
- Spinning up a new Rails app (12/31/09) — One of the first posts on how to spin up a new Rails 3 app.
- Rails 3 and Passenger (01/17/10) — A few tips on how to run Rails 3 on passenger.
- Rails 2 & Rails 3 side by side (01/28/10) — Juggling both versions of rails on the same system (check comments too).
- From Zero to Rails3 on Windows in 600 seconds (01/30/10) — Get a Rails 3 app running on Windows.
Upgrading to Rails 3
- Upgrading an Application to Rails 3 — Part 1 (01/17/10) — Learn how to upgrade your Rails 2 app to Rails 3.
- Migrating a Rails App from 2.x to 3.0 (01/17/10) — Another tutorial on steps needed to upgrade your Rails app.
- Notes from the field upgrading to Rails 3 (01/20/10) — Thanks to bugmash there are now a few of these upgrade guides.
- Getting Up To Speed With Rails 3 (01/22/10) — Good article on upgrading to Rails 3 and deploying with capistrano and passenger.
- The Path to Rails 3: Approaching the upgrade (01/26/10) — Second part in series on understanding Rails 3, this one focusing on upgrade of existing app.
- rails-upgrade: Automating a portion of the Rails 3 upgrade process (01/29/10) — Third part where a plugin is introduced to help automate the upgrade.
- rails-upgrade is now an official plugin (02/01/10) — rails-upgrade is now an official rails tool (as in it sits under github.com/rails and rails team takes part in maintaining it).
Plugin/Gem Compatibility
- Rails Bugmash 2010 (01/16/10) — Notes on how to get certain popular gems/plugins to run on Rails 3.
- Notes from Rails 3 bugmash (01/17/10) — Another set of plugin/gem compatibility notes and Rails 3 discoveries.
- Rails 3 Plugins and Gems — this wiki page lists major plugins and gems which have been verified to be Rails 3 compatible (or not).
Overall Architecture
- My Five Favorite Things About Rails 3 (08/18/09) — Early view on what’s getting into Rails 3
- The Path to Rails 3: Introduction (01/20/10) — A detailed description of architectural changes in Rails 3 (with pics).
Yehuda Katz Series on Rails 3 Progress
- Rails and Merb Merge: The Anniversary (Part 1 of 6) (12/23/09) — On refactoring Rails components for modularity.
- Rails and Merb Merge: Performance (Part 2 of 6) (12/29/09) — On improving rendering/routing performance.
- Rails and Merb Merge: Plugin API (Part 3 of 6) (01/11/10) — On API for plugin developers.
- Rails and Merb Merge: Rails Core (Part 4 of 6) (01/22/10) — On refactoring Rails core for modularity.
Multiple Table Inheritance with ActiveRecord
Update 02/02/10: There are two new sections: Fixing method_missing and Handling attributes hash, which fix a few issues that popped up since first post.
If you aren’t interested in reading all the details you may want to make the long story short.
Imagine writing an online shop with different types of products. Normally all products would have common attributes such as title and price. Some attributes will likely differ. Tee may have size such as S, M, or L, while a Pen could have an ink_color. It’s easy to see that Tee is a Product, and so is Pen. We are looking at an is_a relationship. When I program this type of relationship I usually use inheritance.
end
class Tee < Product
end
class Pen < Product
end
This inheritance looks reasonable, but now we have to come up with relational database structure. We need to find a way to store tee’s own attributes, pen’s own attributes, as well as their common (product’s) attributes without duplication. Some databases (PostgreSQL) provide support for table inheritance, but it’s a specialized feature which ties you down to the given db.
Single Table Inheritance
ActiveRecord provides only one way to handle a is_a relationship which is Single Table Inheritance. You’d have to create a table looking somewhat like the following.
| id | type | price | title | size | ink_color |
+----+------+-------+-----------------+------+-----------+
| 1 | Tee | 1000 | tie-dye t-shirt | M | |
| 2 | Pen | 500 | ball pen | | blue |
+----+------+-------+-----------------+------+-----------+
The problem here is that all attributes are stored in the same table. It’s likely that soon the number of attributes will grow unmanageable, and most of them will always stay NULL since they’ll be specific to only one type.
Polymorphic has_one Association
A has_one association allows us to split out tees, pens, and products into three different tables. In fact — as you’re about to see — this is the only way to get what we want. The problem is that it creates a has_a relationship, and we want is_a. Since there isn’t much choice, we can make it look like we have an is_a relationship, which I’m about to show.
Multiple Table Inheritance (Simulated)
I was speaking with the awesome @fowlduck over at #railsbridge IRC channel about ways to achieve something like MTI with Active Record. He pointed me to a pastie where he implemented an MTI-like behavior and called it a “hydra” pattern, which I subsequently cleaned up a bit.
So we want to have 3 tables in the database.
- product_properties
- tees
- pens
belongs_to :sellable, :polymorphic => true, :dependent => :destroy
end
class Tee < ActiveRecord::Base
has_one :product_properties, :as => :sellable, :autosave => true
end
class Pen < ActiveRecord::Base
has_one :product_properties, :as => :sellable, :autosave => true
end
Immediately we can see duplicated code between Tee and Pen. This can be easily solved with a mixin.
def self.included(base)
base.has_one :product_properties, :as => :sellable, :autosave => true
end
end
class Tee < ActiveRecord::Base
include Sellable
end
class Pen < ActiveRecord::Base
include Sellable
end
Now comes another issue. Every time we want to access price or title attributes (stored in product_properties) we have to call @tee.product_properties.price. This isn’t very convenient, especially considering that product_properties has to be built first in case it doesn’t exist. So let’s ensure it’s always built by updating the module.
def self.included(base)
base.has_one :product_properties, :as => :sellable, :autosave => true
base.alias_method_chain :product_properties, :autobuild
end
def product_properties_with_autobuild
product_properties_without_autobuild || build_product_properties
end
end
Awesome, now product_properties is built automatically in case it doesn’t exist. We still have the method accessing issue though. For that I used method_missing.
def self.included(base)
base.has_one :product_properties, :as => :sellable, :autosave => true
base.alias_method_chain :product_properties, :autobuild
end
def product_properties_with_autobuild
product_properties_without_autobuild || build_product_properties
end
def method_missing(meth, *args, &blk)
if product_properties.public_methods.include?(meth.to_s)
product_properties.send(meth, *args, &blk)
else
super
end
end
end
Now if a method is missing from Tee or Pen instance it will be delegated to product_properties, which enables us to use @tee.price and @tee.title.
However, what about validations? Let’s say we want all products to always have a title, and we want to see an error appear on a Tee instance when ProductProperties#title is missing. Basically I want to completely remove product_properties from my sight as if it doesn’t exist, make it absolutely transparent. Let’s add the necessary validation in ProductProperties.
belongs_to :sellable, :polymorphic => true, :dependent => :destroy
validates_presence_of :title
end
And now let’s make all Sellable models respect the validation as if it’s their own.
def self.included(base)
base.has_one :product_properties, :as => :sellable, :autosave => true
base.validate :product_properties_must_be_valid
base.alias_method_chain :product_properties, :autobuild
end
def product_properties_with_autobuild
product_properties_without_autobuild || build_product_properties
end
def method_missing(meth, *args, &blk)
if product_properties.public_methods.include?(meth.to_s)
product_properties.send(meth, *args, &blk)
else
super
end
end
protected
def product_properties_must_be_valid
unless product_properties.valid?
product_properties.errors.each do |attr, message|
errors.add(attr, message)
end
end
end
end
Notice that I’m including an additional validator with the Sellable module. The validator collects all the errors on ProductProperties and adds them to parent class as if the errors are on a Tee or Pen itself.
As a nice finishing touch we can put this snippet into a Rails initializer.
def self.acts_as_product
include Sellable
end
end
# now we can say
class Tee < ActiveRecord::Base
acts_as_product
end
Although that’s a matter of taste.
Fixing method_missing
There is a problem with method_missing. It checks the array of public_methods on product_properties to find out if delegation should occur. This check will fail in cases like @tee.title_changed?. That’s a magic method and therefore will not be part of static method array. Well, this is an easy fix.
def method_missing(meth, *args, &blk)
product_properties.send(meth, *args, &blk)
rescue NoMethodError
super
end
As you can see, even magic methods will work this way. Only if a NoMethodError is thrown we withdraw back into super.
Handling attributes hash
In the comments Austin brought up a case where initializing new models like Tee.new(:title => "foo") will throw an unknown attribute error. That’s expected since we rely on method_missing for accessing ProductProperties attributes. Instead we should define accessor methods explicitly in our individual products. Thankfully, it’s not too hard to accomplish with our Sellable mixin. First we need to add a submodule ClassMethods with a class method that uses class_eval to magically generate missing attributes.
def define_product_properties_accessors
all_attributes = ProductProperties.content_columns.map(&:name)
ignored_attributes = ["created_at", "updated_at", "sellable_type"]
attributes_to_delegate = all_attributes - ignored_attributes
attributes_to_delegate.each do |attrib|
class_eval <<-RUBY
def #{attrib}
product_properties.#{attrib}
end
def #{attrib}=(value)
self.product_properties.#{attrib} = value
end
def #{attrib}?
self.product_properties.#{attrib}?
end
RUBY
end
end
end
I’ll walk through this code quickly. First we’re extracting only the columns that we want to access. When we call content_columns in the first line of the method, it already excludes a bunch of special columns such as id and type. We then manually subtract more columns we’d like to ignore, such as timestamps, and polymorphic type.
Next we iterate over each remaining attribute and creating instance methods for it, such as title, title= and (for completeness) title?. Having these accessors defined explicitly is enough for ActiveRecord to see them when performing mass assignment, etc. We can now do something like Tee.new(:title => "foo") without any problems. The extra cases such as @tee.title_changed? are still handled by method_missing so we’re good.
One more thing left. We need to run this method on the base class into which we include Sellable. Just need to add a couple of lines to the self.included hook.
base.has_one :product_properties, :as => :sellable, :autosave => true
base.validate :product_properties_must_be_valid
base.alias_method_chain :product_properties, :autobuild
# Add these two lines:
base.extend ClassMethods
base.define_product_properties_accessors
end
And we’re all set.
All together now
Here’s the full picture of everything we just did.
def self.acts_as_product
include Sellable
end
end
class ProductProperties < ActiveRecord::Base
belongs_to :sellable, :polymorphic => true, :dependent => :destroy
validates_presence_of :title # for example
end
module Sellable
def self.included(base)
base.has_one :product_properties, :as => :sellable, :autosave => true
base.validate :product_properties_must_be_valid
base.alias_method_chain :product_properties, :autobuild
base.extend ClassMethods
base.define_product_properties_accessors
end
def product_properties_with_autobuild
product_properties_without_autobuild || build_product_properties
end
def method_missing(meth, *args, &blk)
product_properties.send(meth, *args, &blk)
rescue NoMethodError
super
end
module ClassMethods
def define_product_properties_accessors
all_attributes = ProductProperties.content_columns.map(&:name)
ignored_attributes = ["created_at", "updated_at", "sellable_type"]
attributes_to_delegate = all_attributes - ignored_attributes
attributes_to_delegate.each do |attrib|
class_eval <<-RUBY
def #{attrib}
product_properties.#{attrib}
end
def #{attrib}=(value)
self.product_properties.#{attrib} = value
end
def #{attrib}?
self.product_properties.#{attrib}?
end
RUBY
end
end
end
protected
def product_properties_must_be_valid
unless product_properties.valid?
product_properties.errors.each do |attr, message|
errors.add(attr, message)
end
end
end
end
class Tee < ActiveRecord::Base
acts_as_product
end
class Pen < ActiveRecord::Base
acts_as_product
end
This can be easily adapted for any other use case besides products in a store. In fact, with some meta magic or code generation this can easily be made into a plugin which I encourage you to try and send me the link when you’re done. :)