tag:blog.hasmanythrough.com,2006-02-27:/tag/railshas_many :through - rails2012-01-20T11:03:07-08:00tag:blog.hasmanythrough.com,2006-02-27:Article/1402012-01-20T11:03:07-08:002012-01-20T11:03:07-08:00Modularized Association Methods in Rails 3.2Josh Susser<p>Happy Friday! It's Rails 3.2 day! The <a href="http://weblog.rubyonrails.org/2012/1/20/rails-3-2-0-faster-dev-mode-routing-explain-queries-tagged-logger-store">official release announcement</a> mentions a few of the big changes, but I'd like to take a moment to highlight a relatively <a href="https://github.com/rails/rails/pull/3636">small change</a> I was responsible for, one that I hope may make your life a little easier.</p>
<p>From the ActiveRecord <a href="https://github.com/rails/rails/blob/712b0b99a273c49fb4fad48ae61b4ce252ec0562/activerecord/CHANGELOG.md">CHANGELOG</a>:</p>
<pre><code>Generated association methods are created within a separate module to allow overriding and
composition using `super`. For a class named `MyModel`, the module is named
`MyModel::GeneratedFeatureMethods`. It is included into the model class immediately after
the `generated_attributes_methods` module defined in ActiveModel, so association methods
override attribute methods of the same name. *Josh Susser*
</code></pre>
<p>The point of this change is to allow more flexibility in working with associations in your model classes. When you define an association, ActiveRecord automagically generates some methods for you to work with the association. For example, a <code>has_many :patches</code> association generates the methods <code>patches</code> and <code>patches=</code> (and a few others).</p>
<p>Previously, those association methods were inserted directly into your model class. This change moves those methods into their own module which is then included in your model class. Your model gets the same methods through inheritance, but also gets to override those methods and still call them using <code>super</code>. Let's take a look at two ways this makes things easier for you.</p>
<p>Sometimes you want to replace the standard generated association methods. That's always been easy to do simply by defining new methods in your model class. The only wrinkle was that you had to make sure you defined your method <em>after</em> you set up the association, or calling <code>has_many</code> would overwrite your method, since last writer wins. That was usually not a problem, but sometimes plugins or other monkey patching extensions could add an association after your model's class was defined, which wouldn't give you a chance to add your method afterwards. With this change, you don't have to worry about those order dependencies anymore. Since those methods are generated in their own module, the order doesn't matter. This is a pretty small issue all told and I doubt it affected many people, but it's still worth mentioning.</p>
<p>The real reason for this change is being able to compose your own methods with the standard generated methods. Before this change, you'd have to use <code>alias_method_chain</code> or some other fancy footwork to layer your own logic on top of the standard association functionality. Either that or you'd have to somehow duplicate the standard behavior in your own method. Ick. Now you can compose methods using inheritance and <code>super</code>, the way Alan Kay intended you to. Here's the example from the docs:</p>
<pre><code>class Car &lt; ActiveRecord::Base
belongs_to :owner
belongs_to :old_owner
def owner=(new_owner)
self.old_owner = self.owner
super
end
end
</code></pre>
<p>If you're familiar with ActiveRecord it's probably fairly obvious what's going on there, but I'll spell it out for the new kids. When you define the <code>belongs_to :owner</code> association, that generates a standard <code>owner=</code> method, and puts it in the module named <code>Car::GeneratedFeatureMethods</code>, which is the closest ancestor of class <code>Car</code>. If you're curious what this looks like, fire up the rails console and type <code>Car.ancestors</code> to see the class's inheritance chain. (Or use your own app and model, since that will be much easier than making up a new app just to see that one thing.)</p>
<p>In this Car class, you can see that changing owners keeps track of the old owner, so the new owner knows who to call when he can't figure out how to open the trunk. The generated <code>owner=</code> method does a fair amount of stuff including managing counter caches, running callbacks, setting inverse associations, etc. Skipping that could break a number of things, so after saving the old owner, you also want to run the generated method. Since it's in a module that Car inherits from, you only have to call <code>super</code> to get that to run. No muss, no fuss!</p>
<p>One more step towards simpler OOP in Rails! Thanks to my fellow Ruby Rogues <a href="http://about.avdi.org/">Avdi Grimm</a> and <a href="http://blog.grayproductions.net/">James Edward Gray II</a> for complaining about the old state of things enough to motivate me to finally go fix this.</p><p>Happy Friday! It's Rails 3.2 day! The <a href="http://weblog.rubyonrails.org/2012/1/20/rails-3-2-0-faster-dev-mode-routing-explain-queries-tagged-logger-store">official release announcement</a> mentions a few of the big changes, but I'd like to take a moment to highlight a relatively <a href="https://github.com/rails/rails/pull/3636">small change</a> I was responsible for, one that I hope may make your life a little easier.</p>
<p>From the ActiveRecord <a href="https://github.com/rails/rails/blob/712b0b99a273c49fb4fad48ae61b4ce252ec0562/activerecord/CHANGELOG.md">CHANGELOG</a>:</p>
<pre><code>Generated association methods are created within a separate module to allow overriding and
composition using `super`. For a class named `MyModel`, the module is named
`MyModel::GeneratedFeatureMethods`. It is included into the model class immediately after
the `generated_attributes_methods` module defined in ActiveModel, so association methods
override attribute methods of the same name. *Josh Susser*
</code></pre>
<p>The point of this change is to allow more flexibility in working with associations in your model classes. When you define an association, ActiveRecord automagically generates some methods for you to work with the association. For example, a <code>has_many :patches</code> association generates the methods <code>patches</code> and <code>patches=</code> (and a few others).</p>
<p>Previously, those association methods were inserted directly into your model class. This change moves those methods into their own module which is then included in your model class. Your model gets the same methods through inheritance, but also gets to override those methods and still call them using <code>super</code>. Let's take a look at two ways this makes things easier for you.</p>
<p>Sometimes you want to replace the standard generated association methods. That's always been easy to do simply by defining new methods in your model class. The only wrinkle was that you had to make sure you defined your method <em>after</em> you set up the association, or calling <code>has_many</code> would overwrite your method, since last writer wins. That was usually not a problem, but sometimes plugins or other monkey patching extensions could add an association after your model's class was defined, which wouldn't give you a chance to add your method afterwards. With this change, you don't have to worry about those order dependencies anymore. Since those methods are generated in their own module, the order doesn't matter. This is a pretty small issue all told and I doubt it affected many people, but it's still worth mentioning.</p>
<p>The real reason for this change is being able to compose your own methods with the standard generated methods. Before this change, you'd have to use <code>alias_method_chain</code> or some other fancy footwork to layer your own logic on top of the standard association functionality. Either that or you'd have to somehow duplicate the standard behavior in your own method. Ick. Now you can compose methods using inheritance and <code>super</code>, the way Alan Kay intended you to. Here's the example from the docs:</p>
<pre><code>class Car &lt; ActiveRecord::Base
belongs_to :owner
belongs_to :old_owner
def owner=(new_owner)
self.old_owner = self.owner
super
end
end
</code></pre>
<p>If you're familiar with ActiveRecord it's probably fairly obvious what's going on there, but I'll spell it out for the new kids. When you define the <code>belongs_to :owner</code> association, that generates a standard <code>owner=</code> method, and puts it in the module named <code>Car::GeneratedFeatureMethods</code>, which is the closest ancestor of class <code>Car</code>. If you're curious what this looks like, fire up the rails console and type <code>Car.ancestors</code> to see the class's inheritance chain. (Or use your own app and model, since that will be much easier than making up a new app just to see that one thing.)</p>
<p>In this Car class, you can see that changing owners keeps track of the old owner, so the new owner knows who to call when he can't figure out how to open the trunk. The generated <code>owner=</code> method does a fair amount of stuff including managing counter caches, running callbacks, setting inverse associations, etc. Skipping that could break a number of things, so after saving the old owner, you also want to run the generated method. Since it's in a module that Car inherits from, you only have to call <code>super</code> to get that to run. No muss, no fuss!</p>
<p>One more step towards simpler OOP in Rails! Thanks to my fellow Ruby Rogues <a href="http://about.avdi.org/">Avdi Grimm</a> and <a href="http://blog.grayproductions.net/">James Edward Gray II</a> for complaining about the old state of things enough to motivate me to finally go fix this.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/1322011-06-01T22:45:25-07:002011-06-01T22:45:25-07:00Limitless Strings for PostgreSQLJosh Susser<p>We all love ActiveRecord migrations and the sexy DSL for declaring fields. OK, I don't know if you do, but I sure do.
But life isn't perfect (don't get me started), and there's various details that make using the migration DSL a bit of
a pain.</p>
<p>The one thing that has annoyed me for ages is how string (VARCHAR) fields are handled for PostgreSQL. While the standard for SQL
requires you specify a limit for the length, PostgreSQL is more flexible. From the PostgreSQL docs at <a href="http://www.postgresql.org/docs/current/static/datatype-character.html">http://www.postgresql.org/docs/current/static/datatype-character.html</a>:</p>
<blockquote>
<p>If character varying is used without length specifier, the type accepts strings of any size. The latter is a PostgreSQL extension.</p>
</blockquote>
<p>Unless your string field needs a limited length for semantics (like a SSN or ZIP code), it's better not to specify
a limit for VARCHAR fields.</p>
<p>The problem is that ActiveRecord follows the SQL standard and insists on a limit of 255 if none is specified.
So if you define a field thusly:</p>
<pre><code>create_table "users" do |t|
t.string "name"
end
</code></pre>
<p>That is equivalent to</p>
<pre><code>CREATE TABLE users (
name character varying(255)
);
</code></pre>
<p>OK, you think, I know what to do. I'll specify a limit of nil!</p>
<pre><code>create_table "users" do |t|
t.string "name", :limit =&gt; nil
end
</code></pre>
<p>But no, that doesn't work. A nil limit is ignored and the default 255 is used anyway. There's a tiny patch for that I
might pursue, but in the mean time there is a pretty simple workaround.</p>
<p>Drop these lines into application.rb (assuming you are on Rails 3.0 or greater) within the Application class definition:</p>
<pre><code>initializer "postgresql.no_default_string_limit" do
ActiveSupport.on_load(:active_record) do
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:string].delete(:limit)
end
end
</code></pre>
<p>That removes the default limit for string fields. Problem solved. And that's even nicer than saying <code>:limit =&gt; nil</code> anyway.</p>
<p>And also, this is pretty cool. I got to use one of those fancy ActiveSupport initializer hooks. Let me explain in case you haven't
used one before. What that's doing is waiting until the <code>active_record</code> library is done loading, then running some code to
act on the initialized library. That ensures that the default value is removed after it's been created, but before anyone gets
a chance to use it.</p><p>We all love ActiveRecord migrations and the sexy DSL for declaring fields. OK, I don't know if you do, but I sure do.
But life isn't perfect (don't get me started), and there's various details that make using the migration DSL a bit of
a pain.</p>
<p>The one thing that has annoyed me for ages is how string (VARCHAR) fields are handled for PostgreSQL. While the standard for SQL
requires you specify a limit for the length, PostgreSQL is more flexible. From the PostgreSQL docs at <a href="http://www.postgresql.org/docs/current/static/datatype-character.html">http://www.postgresql.org/docs/current/static/datatype-character.html</a>:</p>
<blockquote>
<p>If character varying is used without length specifier, the type accepts strings of any size. The latter is a PostgreSQL extension.</p>
</blockquote>
<p>Unless your string field needs a limited length for semantics (like a SSN or ZIP code), it's better not to specify
a limit for VARCHAR fields.</p>
<p>The problem is that ActiveRecord follows the SQL standard and insists on a limit of 255 if none is specified.
So if you define a field thusly:</p>
<pre><code>create_table "users" do |t|
t.string "name"
end
</code></pre>
<p>That is equivalent to</p>
<pre><code>CREATE TABLE users (
name character varying(255)
);
</code></pre>
<p>OK, you think, I know what to do. I'll specify a limit of nil!</p>
<pre><code>create_table "users" do |t|
t.string "name", :limit =&gt; nil
end
</code></pre>
<p>But no, that doesn't work. A nil limit is ignored and the default 255 is used anyway. There's a tiny patch for that I
might pursue, but in the mean time there is a pretty simple workaround.</p>
<p>Drop these lines into application.rb (assuming you are on Rails 3.0 or greater) within the Application class definition:</p>
<pre><code>initializer "postgresql.no_default_string_limit" do
ActiveSupport.on_load(:active_record) do
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:string].delete(:limit)
end
end
</code></pre>
<p>That removes the default limit for string fields. Problem solved. And that's even nicer than saying <code>:limit =&gt; nil</code> anyway.</p>
<p>And also, this is pretty cool. I got to use one of those fancy ActiveSupport initializer hooks. Let me explain in case you haven't
used one before. What that's doing is waiting until the <code>active_record</code> library is done loading, then running some code to
act on the initialized library. That ensures that the default value is removed after it's been created, but before anyone gets
a chance to use it.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/1212009-02-07T18:48:10-08:002009-02-07T18:48:10-08:00The tyranny of choice: It's our turn nowJosh Susser<p>Rails used to be about simplicity. I just started a new project in Rails 2.3. Here's all the choices I'm now faced with:</p>
<ul>
<li><strong>Database:</strong> MySQL, PostgreSQL</li>
<li><strong>Testing:</strong> Test/Unit, RSpec, Shoulda, Context/Matchy, minitest</li>
<li><strong>Mocking:</strong> Mocha, FlexMock, RR</li>
<li><strong>Templates:</strong> HTML/ERb, Markaby, HAML, Erector</li>
<li><strong>Authentication:</strong> restful_authentication, Clearance</li>
<li><strong>Web Server:</strong> Apache, Nginx</li>
<li><strong>App Server:</strong> Mongrel, Thin, Passenger</li>
</ul>
<p>It's been about a year since I rolled out a greenfield project. Options are nice, but this feels like Java! And those options are just off the top of my head - I wrote them down in less than a minute without even thinking about it.</p>
<p>Admittedly, it's pretty easy for me to make choices for all these things. I have enough experience that I don't have to spend much time pondering. But for someone new to Rails this all must seem pretty intimidating.</p>
<p>I guess this is why Rails 2.3 has app templates now. Just in time!</p>
<p>Now who has a good template for a quicky open-source app?</p><p>Rails used to be about simplicity. I just started a new project in Rails 2.3. Here's all the choices I'm now faced with:</p>
<ul>
<li><strong>Database:</strong> MySQL, PostgreSQL</li>
<li><strong>Testing:</strong> Test/Unit, RSpec, Shoulda, Context/Matchy, minitest</li>
<li><strong>Mocking:</strong> Mocha, FlexMock, RR</li>
<li><strong>Templates:</strong> HTML/ERb, Markaby, HAML, Erector</li>
<li><strong>Authentication:</strong> restful_authentication, Clearance</li>
<li><strong>Web Server:</strong> Apache, Nginx</li>
<li><strong>App Server:</strong> Mongrel, Thin, Passenger</li>
</ul>
<p>It's been about a year since I rolled out a greenfield project. Options are nice, but this feels like Java! And those options are just off the top of my head - I wrote them down in less than a minute without even thinking about it.</p>
<p>Admittedly, it's pretty easy for me to make choices for all these things. I have enough experience that I don't have to spend much time pondering. But for someone new to Rails this all must seem pretty intimidating.</p>
<p>I guess this is why Rails 2.3 has app templates now. Just in time!</p>
<p>Now who has a good template for a quicky open-source app?</p>tag:blog.hasmanythrough.com,2006-02-27:Article/1152008-06-20T11:06:10-07:002008-06-20T11:06:10-07:00Extra geeky: the recursive lambdaJosh Susser<p>I'm not sure where I first heard that you could do a recursive lamdba in Ruby, but it's been simmering on the back burner of my brain for a while. I've just never had a reason to use one, until now...</p>
<p>I wanted to process the Rails request params, which is a hash of strings and hashes of strings and hashes of strings and hashes... you get the idea. The need was to strip all the accent marks from user input throughout the application. Here's what I came up with:</p>
<pre><code>class ApplicationController &lt; ActionController::Base
before_filter :strip_accents
protected
def strip_accents
thunk = lambda do |key,value|
case value
when String then value.remove_accents!
when Hash then value.each(&amp;thunk)
end
end
params.each(&amp;thunk)
end
end
</code></pre>
<p>That's all completely clear, right? The filter enumerates the top-level hash using the &amp;/to_proc operator to coerce the lambda to a block for the <code>#each</code> method. <code>#each</code> passes the key and value to the lambda, which either removes the accents from a string, or recursively enumerates the contents of a nested hash.</p>
<p>I think it's totally cool that you can do this in Ruby. Everyone thinks that Ruby is just an object-oriented language, but I like to think of it as the love-child of Smalltalk and LISP (with Miss Perl as the nanny).</p><p>I'm not sure where I first heard that you could do a recursive lamdba in Ruby, but it's been simmering on the back burner of my brain for a while. I've just never had a reason to use one, until now...</p>
<p>I wanted to process the Rails request params, which is a hash of strings and hashes of strings and hashes of strings and hashes... you get the idea. The need was to strip all the accent marks from user input throughout the application. Here's what I came up with:</p>
<pre><code>class ApplicationController &lt; ActionController::Base
before_filter :strip_accents
protected
def strip_accents
thunk = lambda do |key,value|
case value
when String then value.remove_accents!
when Hash then value.each(&amp;thunk)
end
end
params.each(&amp;thunk)
end
end
</code></pre>
<p>That's all completely clear, right? The filter enumerates the top-level hash using the &amp;/to_proc operator to coerce the lambda to a block for the <code>#each</code> method. <code>#each</code> passes the key and value to the lambda, which either removes the accents from a string, or recursively enumerates the contents of a nested hash.</p>
<p>I think it's totally cool that you can do this in Ruby. Everyone thinks that Ruby is just an object-oriented language, but I like to think of it as the love-child of Smalltalk and LISP (with Miss Perl as the nanny).</p>tag:blog.hasmanythrough.com,2006-02-27:Article/1142008-06-15T10:31:11-07:002008-06-15T10:31:11-07:00An extra special caseJosh Susser<p>A couple of months ago I ran into a weird issue in my current Rails project that made no sense at all. All we did was add a <code>lock_version</code> field to a model to enable optimistic locking and suddenly things started breaking in a big way. After a bit of digging we found it was because ActiveRecord wasn't properly quoting a table name when updating a record with optimistic locking. I submitted a patch for that issue (so it's fixed in Rails 2.1), but lately I've seen a few similar bugs having to do with table name quoting in various circumstances. The amusing thing to me is that all of these bugs have one thing in common: they were uncovered by creating a model named <em>Reference</em>.</p>
<p>At first I thought this was a pretty big coincidence, but after just a moment's thought it seemed pretty obvious. ActiveRecord pluralizes model names to form conventional table names, and <em>references</em> is a <a href="http://dev.mysql.com/doc/mysqld-version-reference/en/mysqld-version-reference-reservedwords-5-1.html">reserved keyword in MYSQL</a>. I guess Reference is a word that makes a good model name, especially if you're building a big data graph and can't think of a more specific relationship name, and it's about the only noun that pluralizes into a reserved keyword that anyone would ever use. In our case, we could have done a rename refactoring to change the model class name to CharacterReference. Instead we used an override and changed the table name to <code>t_references</code>, since that seemed like the least effort for a temporary workaround until the fix got released with Rails 2.1.</p>
<p>All these various issues with table name quoting are indeed bugs in ActiveRecord and should be reported and fixed. (There's also a major reworking of the internals of ActiveRecord in progress that should deal with virtually all of these issues in one fell swoop.) But in the mean time, you might want to avoid using model names that generate SQL reserved words, or just override the table name to something else.</p><p>A couple of months ago I ran into a weird issue in my current Rails project that made no sense at all. All we did was add a <code>lock_version</code> field to a model to enable optimistic locking and suddenly things started breaking in a big way. After a bit of digging we found it was because ActiveRecord wasn't properly quoting a table name when updating a record with optimistic locking. I submitted a patch for that issue (so it's fixed in Rails 2.1), but lately I've seen a few similar bugs having to do with table name quoting in various circumstances. The amusing thing to me is that all of these bugs have one thing in common: they were uncovered by creating a model named <em>Reference</em>.</p>
<p>At first I thought this was a pretty big coincidence, but after just a moment's thought it seemed pretty obvious. ActiveRecord pluralizes model names to form conventional table names, and <em>references</em> is a <a href="http://dev.mysql.com/doc/mysqld-version-reference/en/mysqld-version-reference-reservedwords-5-1.html">reserved keyword in MYSQL</a>. I guess Reference is a word that makes a good model name, especially if you're building a big data graph and can't think of a more specific relationship name, and it's about the only noun that pluralizes into a reserved keyword that anyone would ever use. In our case, we could have done a rename refactoring to change the model class name to CharacterReference. Instead we used an override and changed the table name to <code>t_references</code>, since that seemed like the least effort for a temporary workaround until the fix got released with Rails 2.1.</p>
<p>All these various issues with table name quoting are indeed bugs in ActiveRecord and should be reported and fixed. (There's also a major reworking of the internals of ActiveRecord in progress that should deal with virtually all of these issues in one fell swoop.) But in the mean time, you might want to avoid using model names that generate SQL reserved words, or just override the table name to something else.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/1092008-05-06T08:17:09-07:002008-05-06T13:53:00-07:00A simple alternative to namespaced modelsJosh Susser<p>A project I'm working on now is up to 57 model classes and is still growing. That's a lot of classes - welcome to domain modeling. In my opinion, the number of classes is a fair tradeoff that keeps each class simple enough to understand. In some ways it moves complexity out of the model class internals into the inheritance hierarchy, which is an important part of object-oriented design. I've worked on projects with many more model classes than that too. (Financial applications seem to require a lot of classes to model the complicated workflow and permission systems.)</p>
<p>The place where it starts to get hard to manage is when I look at the file system and see so many files in one directory. My brain usually starts to overload when I see more than a dozen or so classes in a directory. My first inclination is to throw some related class files into a subdirectory. The problem is that the standard way to do that in Rails is to put those models classes in a namespace (module). Rails used to have big problems with namespaced models, mainly with the dependency auto-loading code that finds class files based on the model class name. Most of those problems have been fixed, but there are still some usability issues with namespaced models.</p><p>A project I'm working on now is up to 57 model classes and is still growing. That's a lot of classes - welcome to domain modeling. In my opinion, the number of classes is a fair tradeoff that keeps each class simple enough to understand. In some ways it moves complexity out of the model class internals into the inheritance hierarchy, which is an important part of object-oriented design. I've worked on projects with many more model classes than that too. (Financial applications seem to require a lot of classes to model the complicated workflow and permission systems.)</p>
<p>The place where it starts to get hard to manage is when I look at the file system and see so many files in one directory. My brain usually starts to overload when I see more than a dozen or so classes in a directory. My first inclination is to throw some related class files into a subdirectory. The problem is that the standard way to do that in Rails is to put those models classes in a namespace (module). Rails used to have big problems with namespaced models, mainly with the dependency auto-loading code that finds class files based on the model class name. Most of those problems have been fixed, but there are still some usability issues with namespaced models.</p>
<p>The first problem, and the biggest one, is <em>foxy fixtures</em>. I like foxy fixtures a lot and the feature makes fixtures much easier to work with. But namespaced models just don't work with foxy fixtures. To get them to sort of work, you have to insert calls to Fixtures.identify anywhere you'd use a normal association. You also can't use the fixture helpers in your tests, so you have to do an explicit find, again using Fixtures.identify. It's pretty ugly, and it's actually worse than it used to be with pre-foxy fixtures.</p>
<p>The other problem is setting up associations. You have to use the <code>:class_name</code> option to tell the association how to find the model class. Again, it's a bit ugly, but at least it works well once you tell the association which class to use.</p>
<p>There's also an issue with STI and polymorphic class names being saved un-namespaced, but it looks like that's been fixed on edge now.</p>
<p>So what's a developer to do? The workaround is really quite simple. <em>Don't use namespaces.</em></p>
<p>By default, Rails looks for models only in the app/models directory, and in subdirectories of app/models for namespaced models. But if you just want to put the model class files in a subdirectory, all you have to do is add that subdirectory to the load paths. If you do that, you don't need to put the model class in a namespace.</p>
<p>In your environment.rb file you'll find a commented out line with an example of setting <code>config.load_paths</code> (in the initializer run block). Uncomment and adjust that line, or if it's not there, just add the following line.</p>
<pre><code>config.load_paths += %W( #{RAILS_ROOT}/app/models/role #{RAILS_ROOT}/app/models/user )
</code></pre>
<p>That incantation adds the role and user subdirectories to the load paths collection, letting Rails find the various role and user class files. No namespacing required, and it works with or without inheritance.</p>
<p><strong>UPDATE:</strong> It's true, there are no original ideas. Boy meets girl, boy loses girl, boy gets girl back. Man against nature, man against man, man against himself. Same thing for blog posts. Chris Wanstrath blogged this up last year in <a href="http://errtheblog.com/posts/3-organize-your-models">a post on errtheblog</a>. Still, good ideas like good stories are worth a retelling now and then...</p>tag:blog.hasmanythrough.com,2006-02-27:Article/1062008-04-02T09:38:16-07:002008-04-02T09:54:31-07:00simple pagesJosh Susser<blockquote>
<p>Simple things should be simple, complex things should be possible. &mdash; Alan Kay</p>
</blockquote>
<p>Here's a tiny little tip for handling those boiler-plate pages that aren't part of your app's functionality but you usually need anyway. It's good for setting up about, contact, copyright, etc. You can always throw those pages into /public as static html files, but if you want them to get styled with layouts, they need to be rendered as actions. This is a way to do that simply. It's not rocket science, but I haven't done a noob post in ages and I'm getting over a cold and I haven't posted in too long so gimme a break.</p>
<p>Say you want to have a simple landing page and a few typical boiler-plate pages. Let's start with the routes.</p>
<p>In <code>config/routes.rb</code></p>
<pre><code>map.root :controller =&gt; 'home'
map.home ':page', :controller =&gt; 'home', :action =&gt; 'show', :page =&gt; /about|contact/
</code></pre>
<p>In <code>app/controllers/home_controller.rb</code></p>
<pre><code>def index
# render the landing page
end
def show
render :action =&gt; params[:page]
end
</code></pre>
<p>Throw your about.html or about.html.erb and other pages into app/views/home and you're good to go. If you've set up page caching, this won't even slow your app down.</p>
<p>The <code>:page =&gt; /.../</code> bit in the route constrains it to match only those specific urls. If you want, you can change that to a constant, like <code>HomeController::</code>PAGES, so it's easier to manage.</p>
<p>If you want to link to those pages, you can use the route helper methods, <code>home_path</code> and <code>home_url</code></p>
<pre><code>link_to 'About', home_path('about')
</code></pre>
<p>You could always unroll the routes and have a separate route for each page, but I find this way a bit drier. But if you'd rather have a specific named route helper for each page, that's an okay way to go. Either way, you get to use layouts in your pages, and have a nice simple way to get them rendered.</p><blockquote>
<p>Simple things should be simple, complex things should be possible. &mdash; Alan Kay</p>
</blockquote>
<p>Here's a tiny little tip for handling those boiler-plate pages that aren't part of your app's functionality but you usually need anyway. It's good for setting up about, contact, copyright, etc. You can always throw those pages into /public as static html files, but if you want them to get styled with layouts, they need to be rendered as actions. This is a way to do that simply. It's not rocket science, but I haven't done a noob post in ages and I'm getting over a cold and I haven't posted in too long so gimme a break.</p>
<p>Say you want to have a simple landing page and a few typical boiler-plate pages. Let's start with the routes.</p>
<p>In <code>config/routes.rb</code></p>
<pre><code>map.root :controller =&gt; 'home'
map.home ':page', :controller =&gt; 'home', :action =&gt; 'show', :page =&gt; /about|contact/
</code></pre>
<p>In <code>app/controllers/home_controller.rb</code></p>
<pre><code>def index
# render the landing page
end
def show
render :action =&gt; params[:page]
end
</code></pre>
<p>Throw your about.html or about.html.erb and other pages into app/views/home and you're good to go. If you've set up page caching, this won't even slow your app down.</p>
<p>The <code>:page =&gt; /.../</code> bit in the route constrains it to match only those specific urls. If you want, you can change that to a constant, like <code>HomeController::</code>PAGES, so it's easier to manage.</p>
<p>If you want to link to those pages, you can use the route helper methods, <code>home_path</code> and <code>home_url</code></p>
<pre><code>link_to 'About', home_path('about')
</code></pre>
<p>You could always unroll the routes and have a separate route for each page, but I find this way a bit drier. But if you'd rather have a specific named route helper for each page, that's an okay way to go. Either way, you get to use layouts in your pages, and have a nice simple way to get them rendered.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/1052008-03-02T16:23:15-08:002008-03-03T21:46:05-08:00Migration ConcordanceJosh Susser<p>If you are a solo developer, Rails' migrations are the neatest thing since sliced bread. If you work on a team, you know that often it can be a real pain dealing with migrations. Someone on your team checks in a new migration and you don't notice it when you svn up or git pull, and suddenly all your tests are breaking. Or even worse, someone modifies an old migration and you need to reset and migrate up from zero (we're talking development here, not production). I'm not a fan of automatically running migrations (I'll leave the reasons for you to guess), but I do like to be informed of when I'm about to run headfirst into a wall. Saves so much wear and tear on my noggin.</p>
<p>And so I give you the migration_concordance plugin. It's pretty darn simple. From the README:</p>
<blockquote>
<p>This plugin extends Rails migrations to provide notification when you need to run migrations. It
will detect both new migrations and modifications to previously run migrations. It is primarily of
use for team development, but is also useful when deploying a release to a new environment to
determine when migrations need to be run. This plugin does not run migrations automatically, but
will notify you whenever you need to run them.</p>
</blockquote><p>If you are a solo developer, Rails' migrations are the neatest thing since sliced bread. If you work on a team, you know that often it can be a real pain dealing with migrations. Someone on your team checks in a new migration and you don't notice it when you svn up or git pull, and suddenly all your tests are breaking. Or even worse, someone modifies an old migration and you need to reset and migrate up from zero (we're talking development here, not production). I'm not a fan of automatically running migrations (I'll leave the reasons for you to guess), but I do like to be informed of when I'm about to run headfirst into a wall. Saves so much wear and tear on my noggin.</p>
<p>And so I give you the migration_concordance plugin. It's pretty darn simple. From the README:</p>
<blockquote>
<p>This plugin extends Rails migrations to provide notification when you need to run migrations. It
will detect both new migrations and modifications to previously run migrations. It is primarily of
use for team development, but is also useful when deploying a release to a new environment to
determine when migrations need to be run. This plugin does not run migrations automatically, but
will notify you whenever you need to run them.</p>
</blockquote>
<h3>How it works</h3>
<p>Whenever you run <code>rake db:migrate</code>, the plugin takes a snapshot of the state of your migrations by generating an MD5 hash of each migration file and dumping them all in a YAML file. Put a 1-line check in your environment.rb file, and then whenever you fire up the Rails environment, a notification is printed telling you if you need to run migrations. The snapshot strategy allows it to tell when old migrations are modified, solving one of the most annoying things about using migrations on a team.</p>
<p>Why would anyone ever edit an old migration? Well, I think there are legitimate reasons to do so. First off, let me say you should <em>never</em> modify a migration that has been deployed and used in your production environment. That's just asking for trouble with a side of getting fired for incompetence. But during a <em>development cycle</em>, it can make sense to modify a migration. On my current project we keep a migration file for each model (or cluster of related models), and as we evolve and refactor the design of the model, we go back and modify its migration to update its schema. This makes more sense to me than having the definition of a model's schema spread out over a zillion migration files, and in practice it works really well. It's nice to only have to look in one place for a model's schema definition, and more importantly if two people modify the model's schema at the same time, it's easier to find the conflict if both changes are in the same file rather than in two different migrations. That said, there are those issues with noticing when you have to run an old migration again. Thus the plugin.</p>
<p>I haven't figured out exactly how to integrate this with Capistrano yet, but I think it will be handy to get a notice when migrations have to be run as part of a deployment. Stay tuned, or if you get inspired and tackle it yourself, please let me know.</p>
<h3>How to get it</h3>
<p>The code for migration_concordance is up on github at <a href="http://github.com/joshsusser/migration_concordance">http://github.com/joshsusser/migration_concordance</a>. If you're not running git (and why aren't you?), you can grab a tarball from the github page to ease your plugin installation. You can just untar into vendor/plugins - there's no install code to run. After you git clone or untar the plugin, add the following line to the end of your config/environment.rb file:</p>
<pre><code>puts ActiveRecord::Migrator.check_concordance
</code></pre>
<p>Then whenever you run script/server, script/console, or whatever, you'll get a notice of whether you need to migrate. If you want to check manually, run <code>rake environment</code> to get the notice.</p>
<p>Lastly, make sure you add the file <code>db/migration_snapshot.yml</code> to your svn:ignores property or .gitignore file. Putting the snapshot file under version control will defeat the plugin, since the snapshot will reflect the migrations that were checked in, rather than the ones that were last run on your computer.</p>
<h3>By the way...</h3>
<p>You've probably heard this at least twelve times in the last week, but <a href="http://github.com/">github</a> is totally badass. I've never had a reason to put my code up on a hosting service like that before, but now I do. github is social networking for code geeks. If you want to hack on migration_concordance yourself, get yourself a github account and fork my repo and have at it. If you do something cool, let me know so I can grab your mods. DrNic has a <a href="http://drnicwilliams.com/2008/02/03/using-git-within-a-team/">very useful writeup</a> of how to manage that, if you're not already up on the planet-smashing powers of git. And Chris and Tom have said that github will be free for open source repos (of reasonable size), so there isn't even anything to pay. Sweet.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/1042008-02-27T14:14:57-08:002008-02-27T14:14:57-08:00count vs length vs sizeJosh Susser<p>In Ruby, #length and #size are synonyms and both do the same thing: they tell you how many elements are in an array or hash. Technically #length is the method and #size is an alias to it.</p>
<p>In ActiveRecord, there are several ways to find out how many records are in an association, and there are some subtle differences in how they work.</p>
<ul>
<li>post.comments.count - Determine the number of elements with an SQL COUNT query. You can also specify conditions to count only a subset of the associated elements (e.g. <code>:conditions =&gt; {:author_name =&gt; "josh"}</code>). If you set up a counter cache on the association, #count will return that cached value instead of executing a new query.</li>
<li>post.comments.length - This always loads the contents of the association into memory, then returns the number of elements loaded. Note that this won't force an update if the association had been previously loaded and then new comments were created through another way (e.g. <code>Comment.create(...)</code> instead of <code>post.comments.create(...)</code>).</li>
<li>post.comments.size - This works as a combination of the two previous options. If the collection has already been loaded, it will return its length just like calling #length. If it hasn't been loaded yet, it's like calling #count.</li>
</ul>
<p>That's I always have to look up these differences, so now I have them in one place so I don't have to think about it anymore.</p>
<p>By the way, today is my blog's second birthday. I just couldn't let that go by without a post!</p><p>In Ruby, #length and #size are synonyms and both do the same thing: they tell you how many elements are in an array or hash. Technically #length is the method and #size is an alias to it.</p>
<p>In ActiveRecord, there are several ways to find out how many records are in an association, and there are some subtle differences in how they work.</p>
<ul>
<li>post.comments.count - Determine the number of elements with an SQL COUNT query. You can also specify conditions to count only a subset of the associated elements (e.g. <code>:conditions =&gt; {:author_name =&gt; "josh"}</code>). If you set up a counter cache on the association, #count will return that cached value instead of executing a new query.</li>
<li>post.comments.length - This always loads the contents of the association into memory, then returns the number of elements loaded. Note that this won't force an update if the association had been previously loaded and then new comments were created through another way (e.g. <code>Comment.create(...)</code> instead of <code>post.comments.create(...)</code>).</li>
<li>post.comments.size - This works as a combination of the two previous options. If the collection has already been loaded, it will return its length just like calling #length. If it hasn't been loaded yet, it's like calling #count.</li>
</ul>
<p>That's I always have to look up these differences, so now I have them in one place so I don't have to think about it anymore.</p>
<p>By the way, today is my blog's second birthday. I just couldn't let that go by without a post!</p>tag:blog.hasmanythrough.com,2006-02-27:Article/1032008-01-30T23:41:12-08:002008-01-30T23:41:12-08:00Segregated page cache storageJosh Susser<p>Page-caching is one of the highest leverage features in Rails. It doesn't take much to set up, and the payoff is huge. When building Teldra I knew from the start that page caching would be part of my production deployment, as it should be for any site with pages where content changes infrequently relative to number of views.</p>
<p>The only thing I find annoying about using the page caching feature is how the cached pages are stored in the RAILS_ROOT/public directory, right alongside all the app's other static pages. I greatly prefer having the cached pages stored in a separate directory. This makes it a lot easier to distinguish between static pages and cached dynamic pages, and if something goes wonky with your cache you can blow it away easily with a single command.</p><p>Page-caching is one of the highest leverage features in Rails. It doesn't take much to set up, and the payoff is huge. When building Teldra I knew from the start that page caching would be part of my production deployment, as it should be for any site with pages where content changes infrequently relative to number of views.</p>
<p>The only thing I find annoying about using the page caching feature is how the cached pages are stored in the RAILS_ROOT/public directory, right alongside all the app's other static pages. I greatly prefer having the cached pages stored in a separate directory. This makes it a lot easier to distinguish between static pages and cached dynamic pages, and if something goes wonky with your cache you can blow it away easily with a single command.</p>
<p>Here's the setup I have Teldra running on. I'm using nginx for the webserver and capistrano for deployment (using slight tweaks to the EngineYard standard configs and recipes). I created a shared/cache directory, told Rails to store cached pages there, and added rules to nginx to find cached pages there. Here's how things look in detail...</p>
<p>In config/environments/production.rb, tell Rails to put cached pages in the public/cache directory.</p>
<pre><code>config.action_controller.page_cache_directory = File.join(RAILS_ROOT, 'public', 'cache')
</code></pre>
<p>In nginx.conf, set up the precedence for locating static files. First look in public for regular static files. Next look in the cache directory for an exact match for the url. Lastly, look in the cache directory for the url with .html appended. That will let you cache pages for regular URLs with no .html extension as well as ones with extensions like .xml, .atom, .json, etc.</p>
<pre><code> if (-f $request_filename) {
break;
}
if (-f /cache$request_filename) {
rewrite (.*) /cache$1 break;
break;
}
if (-f /cache$request_filename.html) {
rewrite (.*) /cache$1.html break;
break;
}
</code></pre>
<p>The capistrano recipes have to do a couple things. You need to create the shared/cache directory when setting up the deployment.</p>
<pre><code>after "deploy:setup", "create_page_cache"
task :create_page_cache, :roles =&gt; :app do
run "umask 02 &amp;&amp; mkdir -p #{shared_path}/cache"
end
</code></pre>
<p>When deploying a new release, create a symlink from public/cache to the shared/cache directory.</p>
<pre><code>after "deploy:update_code","symlink_shared_dirs"
task :symlink_shared_dirs, :roles =&gt; :app, :except =&gt; {:no_release =&gt; true, :no_symlink =&gt; true} do
run &lt;&lt;-CMD
cd #{release_path} &amp;&amp;
ln -nfs #{shared_path}/cache #{release_path}/public/cache
CMD
end
</code></pre>
<p>When doing a deploy, the standard behavior is to flush the cache, just to be on the safe side. If you want to retain cached pages, as when making a change you know won't affect rendering, tell capistrano not to flush.</p>
<pre><code># default behavior is to flush page cache on deploy
set :flush_cache, true
# page cache management
task :keep_page_cache do
set :flush_cache, false
end
after "deploy:cleanup", "flush_page_cache"
task :flush_page_cache, :roles =&gt; :app do
if flush_cache
run &lt;&lt;-CMD
rm -rf #{shared_path}/cache/*
CMD
end
end
</code></pre>
<p>With the above setup, you can deploy and retain the cache with the following capistrano command:</p>
<pre><code>$ cap keep_page_cache deploy
</code></pre>
<p>That's about it. I know I'm not the first fellow to think of this setup, but I'm surprised it's not more common. Anyone else doing something similar?</p>tag:blog.hasmanythrough.com,2006-02-27:Article/1022008-01-26T11:54:51-08:002008-01-26T12:16:45-08:00Feed readers are lame and URLs are foreverJosh Susser<p>When I changed my blog software from Typo to Mephisto, my article and feed URLs all changed. I didn't want to break all those old URLs, so I put a lot of effort into writing a ton of Apache mod_rewrite rules to redirect the old URLs to the new ones. I set them up as 301 permanent redirects to indicate that the old URL was defunct and to use the new one from then on. That means that the feed reader is supposed to change the URL for the feed <em>permanently</em>. But feed readers are lame and many treat the redirect as a temporary change, so I'm still getting requests for feed URLs that haven't existed in over a year.</p>
<p>If you're reading this article in your browser because your feed broke and you came here to see what happened, that means it's time to update the URL for your feeds. I'm sorry, but your feed reader has been told every hour for the last year that the URL has moved to a new location forever, but it chose to ignore that fact, and I'm not going to keep supporting those old URLs anymore. Here's the new URLs so you can manually update your feeds:</p>
<p>The main feed is hosted on FeedBurner: <a href="http://feeds.feedburner.com/hasmanythrough">http://feeds.feedburner.com/hasmanythrough</a> (and has been for the last year).</p>
<p>Categories have disappeared and articles are now organized only by tags. Old categories are now just tags. Tag feed URLs look like: <a href="http://blog.hasmanythrough.com/tag/rails.atom">http://blog.hasmanythrough.com/tag/rails.atom</a></p>
<p>I haven't set up comment feeds yet, but they should be along in a week or two at most.</p><p>When I changed my blog software from Typo to Mephisto, my article and feed URLs all changed. I didn't want to break all those old URLs, so I put a lot of effort into writing a ton of Apache mod_rewrite rules to redirect the old URLs to the new ones. I set them up as 301 permanent redirects to indicate that the old URL was defunct and to use the new one from then on. That means that the feed reader is supposed to change the URL for the feed <em>permanently</em>. But feed readers are lame and many treat the redirect as a temporary change, so I'm still getting requests for feed URLs that haven't existed in over a year.</p>
<p>If you're reading this article in your browser because your feed broke and you came here to see what happened, that means it's time to update the URL for your feeds. I'm sorry, but your feed reader has been told every hour for the last year that the URL has moved to a new location forever, but it chose to ignore that fact, and I'm not going to keep supporting those old URLs anymore. Here's the new URLs so you can manually update your feeds:</p>
<p>The main feed is hosted on FeedBurner: <a href="http://feeds.feedburner.com/hasmanythrough">http://feeds.feedburner.com/hasmanythrough</a> (and has been for the last year).</p>
<p>Categories have disappeared and articles are now organized only by tags. Old categories are now just tags. Tag feed URLs look like: <a href="http://blog.hasmanythrough.com/tag/rails.atom">http://blog.hasmanythrough.com/tag/rails.atom</a></p>
<p>I haven't set up comment feeds yet, but they should be along in a week or two at most.</p>
<p>Already I can see my FeedBurner subscription count has dropped significantly. I expect some of those losses are permanent, but I'm not going to cry about it. It's a good lesson though: <em>URLs are forever</em>. I like to keep that in mind as I design the URL scheme for a web app, and I like to have a scheme for URLs that isn't tightly coupled to the underlying model representation or database internals (like primary keys). Rails makes it so darn easy to expose the primary key of a record in the URL that nearly every Rails app in existence does just that. I've done it myself fairly often, but I strive not to do so in an app that's going to have a long life in the wild of the Internet. Teldra has no public routes that use the primary key.</p>
<p>If you want to avoid exposing primary keys in URLs, you should watch out for default routes and for map.resources calls. Anything that has an :id param in the route is going to get you. The easy way around that is to override the <code>#to_param</code> method for your ActiveRecord model to change the id from the primary key to something more meaningful. For example, here's the method from Teldra's Tag class:</p>
<pre><code>def to_param
name.gsub(/ /,'+')
end
</code></pre>
<p>So the tag "ruby on rails" is identified by the URL <a href="/tag/ruby+on+rails">/tag/ruby+on+rails</a>. Then you have to deal with the other side of things in the controller. Instead of doing the usual <code>@tag = Tag.find(params[:id])</code>, you want to find the tag using its name. The simplest way to define this method in class Tag:</p>
<pre><code>def self.find_by_param!(name)
record = find(:first, :conditions =&gt; {:name =&gt; name})
raise ActiveRecord::RecordNotFound if record.nil?
record
end
</code></pre>
<p>Then in the controller do <code>@tag = Tag.find_by_param!(params[:id])</code>. Or you could fix your route to use :name instead of :id to make it even more clear. I like raising the RecordNotFound error so that Rails will give you a 404 page if you try to access a tag that doesn't exist. The exclamation mark at the end of the method name says we're raising an error sometimes, to distinguish from normal dynamic finder methods that return nil if no record is found.</p>
<p>I think there's probably a little plugin that could be built around this pattern that would make it even easier. (makes a note)</p>
<p>By the way, I make it a practice to remove the default routes from routes.rb right away. I think they are a crutch and a cause of messy application design, and also a potential security hole or at least a source of unexpected behavior. If you want to have a firm grasp on the URLs of your application, get rid of the default routes and use only map.resource(s) and specific named routes for everything else.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/992007-12-20T04:31:00-08:002008-01-25T11:25:56-08:00Book Review: The Rails WayJosh Susser<p>Here's another entry in my ongoing Rails book review series.</p>
<p>Title: <a href="http://www.aw-bc.com/catalog/academic/product/0,1144,0321445619,00.html">The Rails Way</a><br/>
Author: Obie Fernandez<br/>
Publisher: Addison-Wesley</p>
<p>The latest entry in Addison-Wesley's Professional Ruby Series is <em>The Rails Way</em>, by Obie Fernandez. The name is a nod to Hal Fulton's noble classic <em>The Ruby Way</em>, and it's clear this book aims to be as significant a feature in the Rails publishing landscape. The good news is that the book delivers, and then some. <em>The Rails Way</em> appears destined to become the new bible of Rails development.</p>
<p>Before I dive into the full review, a disclaimer. Obie is a friend and fellow cabooser, and someone I have a lot of respect for. I just want to get that out of the way so that no one can say I'm shilling for his book on the sly. But I'm glad I can give him a good review with a clear conscience. Anyway, back to the review.</p><p>Here's another entry in my ongoing Rails book review series.</p>
<p>Title: <a href="http://www.aw-bc.com/catalog/academic/product/0,1144,0321445619,00.html">The Rails Way</a><br/>
Author: Obie Fernandez<br/>
Publisher: Addison-Wesley</p>
<p>The latest entry in Addison-Wesley's Professional Ruby Series is <em>The Rails Way</em>, by Obie Fernandez. The name is a nod to Hal Fulton's noble classic <em>The Ruby Way</em>, and it's clear this book aims to be as significant a feature in the Rails publishing landscape. The good news is that the book delivers, and then some. <em>The Rails Way</em> appears destined to become the new bible of Rails development.</p>
<p>Before I dive into the full review, a disclaimer. Obie is a friend and fellow cabooser, and someone I have a lot of respect for. I just want to get that out of the way so that no one can say I'm shilling for his book on the sly. But I'm glad I can give him a good review with a clear conscience. Anyway, back to the review.</p>
<p>The first thing you'll notice about <em>The Rails Way</em> is that it is simply enormous. At over 900 pages it is nearly twice the size of <em>Agile Web Development with Rails</em>. Quantity has a quality all its own, and the volume of this volume gives Obie room to go seriously in depth into just about every interesting aspect of Rails development. The size of the book makes doing a chapter-by-chapter analysis impractical. It even made doing a full read-through too daunting for me to attempt. So I did the review by reading a couple select chapters, then using the book as a reference for a couple weeks. Anytime I wanted to look something up in the API I used <em>The Rails Way</em> instead. There were a few odd corners of the API that weren't covered, but in general for everything I had to look up, the book had some useful information for me. That says something about its breadth. But it goes deep too. Obie doesn't just mention a feature and brush you off with a brief description of the API. He talks about the feature, puts it in context, sometimes talks about its history and evolution, says how you should use it, how you shouldn't use it, and when there is a better alternative. It's sort of like being tutored by a Rails expert with logorrhea (but in a good way).</p>
<p>As I said, an analysis of each chapter would be more than I can manage here, but the table of contents itself will give you an idea of the ambitious scope of the book:</p>
<ol>
<li>Rails Environments and Configuration</li>
<li>Working with Controllers</li>
<li>Routing</li>
<li>REST, Resources and Rails</li>
<li>Reflecting on Rails Routing</li>
<li>Working with ActiveRecord</li>
<li>ActiveRecord Associations</li>
<li>ActiveRecord Validations</li>
<li>Advanced ActiveRecord</li>
<li>ActionView</li>
<li>All About Helpers</li>
<li>AJAX on Rails</li>
<li>Session Management</li>
<li>Login and Authentication</li>
<li>XML and ActiveResource</li>
<li>ActionMailer</li>
<li>Testing</li>
<li>RSpec on Rails</li>
<li>Extending Rails with Plugins</li>
<li>Rails Production Configurations</li>
<li>Capistrano</li>
<li>Background Processing</li>
<li>Appendix A: ActiveSupport API Reference</li>
<li>Appendix B: Rails Essentials (plugins and other tools)</li>
</ol>
<p>You can see that the topics include things that aren't part of Rails itself, but things that everyone needs to do with Rails. It also documents parts of Rails that aren't well-documented anywhere else. Talk about exhaustive, I'm tired just from looking at that list of chapters.</p>
<p>The second thing you may notice is the cheery yellow stamp on the cover that says, "Covers Rails 2.0". Obie's been working on this book for ages, but somehow he managed to time it to hit the shelves within a week or two of the release of Rails 2 itself. I'm sure keeping up with all the changes and occasional API flailing was a major challenge, but it's great to have a thorough treatment of Rails that will have some staying power. Of course, Rails is a fast-moving target, so there are some details that will need to be corrected in the second printing. But don't let that stop you from getting the book now; it's worth it even with the minor blemish here and there. And if you're worried stuff might not get corrected, just take a look at the <a href="http://awprorubyseries.lighthouseapp.com/projects/6454-the-rails-way/overview">bug tracking site</a> for the book - they are on top of it!</p>
<p>Speaking of corrections, if you have a copy of the first printing (which would be everyone who has the book right now), the epigraphic quote by me at the start of Chapter 7 should say "reify" instead of "rarefy". Which is a nice segue into my next observation. The biggest strength of Rails is the community of people who use and contribute to it. So it's appropriate that Obie has invited some well-known Rails developers to contribute material to his book. He also has included many relevant quotes and commentaries from various developers (including a couple from yours-truly) and references to supporting software and writings. This strengthens the book in two ways. It adds important information, and it connects Rails to its ecosystem. As anyone who has gotten into Rails knows, trying to learn and use Rails in a vacuum is pointless. Learning about the people in the community and the extras that make Rails even more useful can be just as important as learning the API.</p>
<p>I'd like to say more about this book, but I think I've hit all the important points. I'll close by saying that on top have having a ton of information, <em>The Rails Way</em> is very well-written. It's well-organized, nicely structured, and the text itself is very readable. And the persnickety editor in me is even satisfied with the index.</p>
<p>Go get yourself a copy of <em>The Rails Way</em> and have fun upgrading to Rails 2.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/972007-10-30T18:55:00-07:002008-01-24T00:19:36-08:00Self-referential has_many :through associationsJosh Susser<p>This article updates <a href="http://blog.hasmanythrough.com/2006/4/21/self-referential-through">a previous version</a> for the Rails 2.0 way of things. Since there's not much difference, I decided to fix up the example code to be more understandable. After all, not everyone is a discrete math geek.</p>
<p>This example updates the one from the previous article. The only significant difference is that you don't need to specify the <code>:foreign_key</code> when using the <code>:class_name</code> option in a <code>belongs_to</code> association. In Rails 2.0, the key is inferred from the association name instead of the class name. I also included the <code>:dependent</code> option because I feel it's too often overlooked.</p>
<p>These classes could be used to model a food chain. Spider eats fly, bird eats spider, cat leaves bird on pillow as gift...</p>
<pre><code>create_table :animals do |t|
t.string :species
end
create_table :hunts do |t|
t.integer :predator_id
t.integer :prey_id
t.integer :capture_percent
end
class Animal &lt; ActiveRecord::Base
has_many :pursuits, :foreign_key =&gt; 'predator_id',
:class_name =&gt; 'Hunt',
:dependent =&gt; :destroy
has_many :preys, :through =&gt; :pursuits
has_many :escapes, :foreign_key =&gt; 'prey_id',
:class_name =&gt; 'Hunt',
:dependent =&gt; :destroy
has_many :predators, :through =&gt; :escapes
end
class Hunt &lt; ActiveRecord::Base
belongs_to :predator, :class_name =&gt; "Animal"
belongs_to :prey, :class_name =&gt; "Animal"
end
</code></pre>
<p>The Hunt model describes how likely a species of predator is to catch a species of prey. From the predator's perspective the hunt is a pursuit, but the ever-hopeful prey sees it as an escape. Note that you can model both kinds of hunts between the same pairings of animals: Some days you get the bear, some days the bear gets you.</p><p>This article updates <a href="http://blog.hasmanythrough.com/2006/4/21/self-referential-through">a previous version</a> for the Rails 2.0 way of things. Since there's not much difference, I decided to fix up the example code to be more understandable. After all, not everyone is a discrete math geek.</p>
<p>This example updates the one from the previous article. The only significant difference is that you don't need to specify the <code>:foreign_key</code> when using the <code>:class_name</code> option in a <code>belongs_to</code> association. In Rails 2.0, the key is inferred from the association name instead of the class name. I also included the <code>:dependent</code> option because I feel it's too often overlooked.</p>
<p>These classes could be used to model a food chain. Spider eats fly, bird eats spider, cat leaves bird on pillow as gift...</p>
<pre><code>create_table :animals do |t|
t.string :species
end
create_table :hunts do |t|
t.integer :predator_id
t.integer :prey_id
t.integer :capture_percent
end
class Animal &lt; ActiveRecord::Base
has_many :pursuits, :foreign_key =&gt; 'predator_id',
:class_name =&gt; 'Hunt',
:dependent =&gt; :destroy
has_many :preys, :through =&gt; :pursuits
has_many :escapes, :foreign_key =&gt; 'prey_id',
:class_name =&gt; 'Hunt',
:dependent =&gt; :destroy
has_many :predators, :through =&gt; :escapes
end
class Hunt &lt; ActiveRecord::Base
belongs_to :predator, :class_name =&gt; "Animal"
belongs_to :prey, :class_name =&gt; "Animal"
end
</code></pre>
<p>The Hunt model describes how likely a species of predator is to catch a species of prey. From the predator's perspective the hunt is a pursuit, but the ever-hopeful prey sees it as an escape. Note that you can model both kinds of hunts between the same pairings of animals: Some days you get the bear, some days the bear gets you.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/952007-10-29T00:11:00-07:002008-01-24T00:19:36-08:00Book Review: Pro Active RecordJosh Susser<p>I've been wanting to start doing some book reviews for a while, so here goes. The folks at Apress have been kind enough to send me review copies of a couple books, so I'm going to start with one of them. First up, <em>Pro Active Record</em>.</p>
<p>Title: <a href="http://apress.com/book/view/1590598474">Pro Active Record: Databases with Ruby and Rails</a><br/>
Authors: Kevin Marshall, Chad Pytel, Jon Yurek<br/>
Publisher: <a href="http://apress.com/">Apress</a></p>
<p>My first thought at seeing this book was, "Hey, a book all about ActiveRecord! Cool!" It's nice to finally see a volume dedicated to ActiveRecord, since it's my favorite part of Rails. However, this book ends up being something of a mixed bag. There's a lot of good information in it, and it does a decent job of covering all the basics. There are even some excellent parts here and there. But the book also has some problems, so it's hard for me to give it an unequivocal recommendation. Nevertheless, it does have value and fills a needed spot, so it may be what you're looking for.</p><p>I've been wanting to start doing some book reviews for a while, so here goes. The folks at Apress have been kind enough to send me review copies of a couple books, so I'm going to start with one of them. First up, <em>Pro Active Record</em>.</p>
<p>Title: <a href="http://apress.com/book/view/1590598474">Pro Active Record: Databases with Ruby and Rails</a><br/>
Authors: Kevin Marshall, Chad Pytel, Jon Yurek<br/>
Publisher: <a href="http://apress.com/">Apress</a></p>
<p>My first thought at seeing this book was, "Hey, a book all about ActiveRecord! Cool!" It's nice to finally see a volume dedicated to ActiveRecord, since it's my favorite part of Rails. However, this book ends up being something of a mixed bag. There's a lot of good information in it, and it does a decent job of covering all the basics. There are even some excellent parts here and there. But the book also has some problems, so it's hard for me to give it an unequivocal recommendation. Nevertheless, it does have value and fills a needed spot, so it may be what you're looking for.</p>
<h2>The Good</h2>
<p>Let's start by taking a look at what the book provides. As I said, it covers all the basics.</p>
<p><em>Pro Active Record</em> starts with a good introduction to ActiveRecord, going over a bit of the history, how to install it, and shows a small example program. The best part of this chapter is the section that shows all the configuration settings for the different database adapters. It's nice to see all that in one place.</p>
<p>Chapter 2 digs into the foundation of ActiveRecord and how it maps objects to SQL. It covers CRUD operations, transactions, and locking. I like how it is full of examples that show an AR API call and the resulting SQL. </p>
<p>Chapter 3 is entitled Setting Up Your Database, but it's mainly about using migrations. It does however start with a good summary of the main ActiveRecord conventions for table naming and structure. It would be even better to see a summary of all the magic fields in one place (e.g. features like counter cache and optimistic locking).</p>
<p>Chapter 4 is where things start to get interesting. This chapter gets into the meat and potatoes (or granola and tofu for us Californians) of ActiveRecord. The three main topics are Callbacks (what I call the life-cycle features), Associations, and Validations. This is where you start to get a feel for what using ActiveRecord is like. Every major feature comes with some code examples, which is a good thing, though as contrived examples go some of them are pretty uninspired. Probably the biggest weakness in terms of content is that the section on associations doesn't show more than the most basic use cases. ActiveRecord associations are a rich topic and could easily cover a whole chapter, if not several. But for a beginner's book on ActiveRecord, this is probably enough. Though not seeing any mention of association extensions made me sad.</p>
<p>Chapter 5 is like the whole book in microcosm. Some good stuff, some stupid stuff, but not a waste of time. It covers some "bonus features" of ActiveRecord. The section on observers was an interesting start, but the topic deserves more than just one page. On the other hand, there are thirteen whole pages wasted covering the List, Tree and Nested Set features that everyone has known for ages were slated to be pulled from ActiveRecord (and were just extracted and turned into plugins). The section on aggregations is adequate, but the examples obfuscate more than they reveal. And yet, the chapter recovers and even shines with one of the best discussions of metaprogramming in Ruby and ActiveRecord that I've yet to read. Really, I'd almost say it's worth it to get this book just for the Extending Active Record section of this chapter. The thing that makes it work is how they show a problem, then an evolving series of solutions that do similar things but with more sophistication as they progress. At each step there is a discussion of the advantages and drawbacks of each approach. If the whole book was up to this standard, it would be a no-brainer to recommend it to everyone.</p>
<p>Chapter 6 introduces ActiveRecord testing and debugging. It includes a rather light discussion of writing tests and the basics of using fixtures. Most of the testing section covers the assertion methods in <code>Test::Unit</code>. While this book's focus isn't testing, it's a shame that there weren't at least a few good examples or some deeper how-to discussion. The world really needs a good book on testing Rails (so get off your butts, guys - you know who you are!). There follows a section that lists and describes the ActiveRecord exception classes. Finally there's a section on debugging tips, which is mainly about using logging. I was disappointed the chapter didn't include any mention of using irb (or the Rails console) for experimenting with code or debugging, and that there was no mention of using the Ruby debugger.</p>
<p>Chapter 7 addresses how to work with legacy databases. This is one of the trickiest things to do with ActiveRecord, because you don't get to go with the flow and have to do a lot more configuration than normal. The first part of the chapter describes the various configuration methods ActiveRecord provides to override its default conventions, and it's great to see them listed in one place like that. Then there is a slow dive into the bowels of ActiveRecord, covering <code>find_by_sql</code> and some of the low-level database APIs. There's also a section on import/export of your favorite interchange formats (xml, yaml, csv). What is missing is any discussion of how to map typical un-Rails-ish database usage patterns into ActiveRecord. For example, how to deal with non-integer primary keys, composite keys, foreign key constraints, enums, etc. That would have made this chapter a real winner.</p>
<p>Chapter 8 is the catch-all for everything that didn't fit in the other chapters. It has a section on reading the ActiveRecord source code that provides a roadmap for finding your way around. There's a decent discussion of various alternatives to ActiveRecord (other Ruby ORM packages), though it misses some recent additions to the field like Sequel and DataMapper. The FAQ section is pretty good, and provided a number of answers that were missing from previous chapters. One answer even includes some code from my own blog (thanks for giving the proper attribution!).</p>
<p>Finally, there is an Appendix that documents the core ActiveRecord API.</p>
<p>Looking back, that's a pretty good chunk of information on ActiveRecord, and probably enough to get anyone started. It's also very readable (though all the anecdotes about Kevin got to be a bit much). I thought the organization was good and followed a logical progression.</p>
<h2>The Bad (and The Ugly, because you expected it)</h2>
<p>Unfortunately, for all the book provides, there are enough problems with it that I have to wonder if it missed out on some much-needed editing.</p>
<p>To begin, this book doesn't say anywhere what version of ActiveRecord (or Rails) it is documenting. It makes me wonder if the authors were all on the same page here, because while most places seem to be working with Rails 1.2, some look like they are talking about Rails 2.0 code. Since Rails 2.0 is being released any day now, that's an important issue. There have been a lot of changes to ActiveRecord since Rails 1.2, and readers may be pretty confused about the applicability of the information. If nothing else, this book needs an update to get it in sync with Rails 2.0.</p>
<p>Next off, the book doesn't come with any downloadable example code. I've reached the point where I think that is a basic requirement for a software book. I think it's important to provide code that you can play with, or can use to copy/paste into your own code. It's also possible to provide a lot more code in the example projects, since putting all that code in the book is usually too much. And maybe most of all, having real code from which you grab excerpts for the book means that you can have confidence the code actually works. The code examples in <em>Pro Active Record</em> were obviously written in a word processor and never executed, since many of them wouldn't work correctly or even compile. I've messed up doing that in my own blog postings enough that I've learned that lesson.</p>
<p>One of the problems that affects the code samples disproportionately is that the book is riddled with typos and capitalization errors. It looks like Word's auto-capitalization messed a lot of stuff up badly, especially the example code. Unfortunately, when you're showing source code for a language that is case-sensitive, what might otherwise be a minor annoyance can become a significant issue. This is the sort of thing that is so easily prevented that I don't understand how it can happen.</p>
<p>More seriously though, there are some glaring factual errors. For example, Chapter 2 states that <code>ActiveRecord#update_attributes</code> doesn't run validations on the model. But it's a totally common use case to use <code>update_attributes</code> like so:</p>
<pre><code>def update
@user = User.find(params[:id])
if @user.update_attributes(params[:user])
redirect_to user_path(@user)
else
render :action =&gt; "edit"
end
end
</code></pre>
<p>That's even how it's done in the standard scaffolding now. If <code>update_attributes</code> didn't run validations, that code wouldn't work. Now, the <code>update_attribute</code> (note the singular) method does <em>not</em> run validations, but it is used quite differently from the plural form method. I could see how a casual reader of the ActiveRecord API might get confused, but not someone who had ever written a typical CRUD Rails application. Oddly enough, they get it right in the API summary appendix. Ah, the joys of collaborative authorship.</p>
<p>The book also states that there is a standard <code>db:bootstrap</code> rake task in Rails, which doesn't exist even in Rails 2.0 (though Rick Olson and others have rolled their own). It says you can explicitly delete object instances in Ruby, something that is impossible in a garbage collected language like Ruby. And it says the :id attribute is optional in <code>has_and_belongs_to_many</code>, where it will in reality break certain things if you include it. They also claim that it doesn't matter which ActiveRecord class you call <code>find_by_sql</code> on, when in actuality the class determines what kind of model object is returned from the call. Those are just some examples I caught; there are plenty of others.</p>
<p>This may all sound nit-picky, but I think if you can't have confidence in the accuracy of a reference book, then you can't truly rely on any of the information in it. Fortunately for the authors, none of these detail errors are major problems and they could be corrected with a good technical edit of the book. Perhaps they can be corrected in the ebook without waiting for a second printing</p>
<h2>Bottom Line</h2>
<p>If you're looking to get started with ActiveRecord, <em>Pro Active Record</em> is a pretty good place to start. It has everything you need to get your bearings and start your journey. Be advised, however, that it's not going to take you much past the start of that journey. And at least with the current edition, you'll need to double check the directions along the way.</p>
<h2>Reviewing the Review</h2>
<p>Since this was my first book review here, I'd like to get some feedback. What did you all think of it? Useful? Too critical? Too short? Too long? Not enough pie?</p>
<p>I'm intending to do more of these (maybe one every month or two), so tell me what works and what doesn't.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/942007-10-24T16:20:00-07:002008-01-24T00:19:36-08:00MicroPlace: invest wisely, end povertyJosh Susser<p>And now for something completely different... I'm talking about the launch today of <a href="http://www.microplace.com/">MicroPlace</a>, a new website which was written in Rails. I don't usually call out such sites, but this time is different for a few reasons. Firstly, I helped build the site, so I have a personal involvement in it. Secondly, it's a good cause that's worth talking about. And lastly, the circumstances of its creation should be of interest to the Rails community.</p><p>And now for something completely different... I'm talking about the launch today of <a href="http://www.microplace.com/">MicroPlace</a>, a new website which was written in Rails. I don't usually call out such sites, but this time is different for a few reasons. Firstly, I helped build the site, so I have a personal involvement in it. Secondly, it's a good cause that's worth talking about. And lastly, the circumstances of its creation should be of interest to the Rails community.</p>
<p>But as prologue, a brief word about how I ended up working on this project. Last I said anything about it I was working for a startup company. Well, startups are rarely exactly what you expect, and things there didn't work out like I wanted. So since the summer I've been freelancing as a consultant, and generally having a good time of it. MicroPlace was also working in stealth mode prior to launch, so it was good that I didn't say much about where I was working until now. And that's all I want to say on that subject.</p>
<p>Freelancing is nice in that I get to work on a variety of things, and I have a lot of choice about what I work on. I was really interested in working on MicroPlace for a few reasons. The biggest reason for me was what MicroPlace is about. It's a <a href="http://en.wikipedia.org/wiki/Microfinance">microfinance</a> investing site that allows anyone (in the US) to loan small amounts of money to entrepreneurs in developing nations. This has been shown to be very effective in reducing poverty. Last year, Muhammad Yunus was awarded the Nobel Peace Prize for his work as founder of the Grameen Bank in Bangladesh. I'm proud to have been involved in a project that can potentially affect the lives of millions of people in the world's poorest countries.</p>
<p>The other exciting thing about MicroPlace is that <strong>its parent company is eBay</strong>. I definitely found it appealing to be working on the first Ruby on Rails project at a huge and prestigious all-Java shop like eBay. The project was staffed mainly by eBay employees, but they also brought in a couple of consultants with experience with Rails to help them out with the learning curve, which was how I got involved. From what I could see, things went very smoothly. I'll probably have more to say about the development project itself sometime soon, after I find out what it's cool for me to talk about.</p>
<p>As far as I am aware, MicroPlace is the first SEC-registered online brokerage implemented in Ruby on Rails. We had to go through an extensive security audit, and there were a lot of regulatory requirements for us to meet. Luckily for us, eBay has a lot of experience with building financial systems and is used to dealing with those kinds of requirements. But the bottom line is that we didn't have any significant problems with either Ruby or Rails in passing those hurdles.</p>
<p>Some things I like about the MicroPlace model:</p>
<ul>
<li>They actually pay a return on your investment. It's a small return, a few percent, but it'll beat the rate in a typical savings account.</li>
<li>When you fund your investment through PayPal, PayPal waives the transaction fee, so 100% of your money goes into the loan. They can do that since they are all one, big, happy eBay family.</li>
<li>You don't invest in a particular person somewhere. You choose a country, then a particular fund in that country. And that fund will make loans to individuals, so it's sort of like you're investing in a mutual fund of microloans.</li>
<li>Any profits made by MicroPlace will be donated to non-profits, including the eBay Foundation.</li>
</ul>
<p>I especially like the MicroPlace funding model because it's less of a "beauty pageant" of borrowers, and more about having an impact on a country or region you may care about. I think the local banks probably are able to evaluate borrowers and their needs in person better than I could over a website. You do get to see some real, representative borrowers for each investment fund, but for each one you see there will be many others served by that fund as well. Not everyone may like that model, but for those that don't there are alternatives.</p>
<p>The folks on the MicroPlace team are very committed to their cause. Most of the team have already traveled to developing nations to learn about microlending and poverty programs directly. eBay is also taking this on from a desire to be a good global citizen. It's great to see a big corporation use its money and capabilities to improve people's lives, rather than just to make more money. Kudos to eBay, you guys rock.</p>
<p>So congratulations to the <a href="https://www.microplace.com/learn_more/team">MicroPlace team</a> on their launch! And congratulations to eBay on being the newest corporate member of the Rails community. Way to go!</p>
<p><strong>More about MicroPlace and microfinance</strong></p>
<p><a href="https://www.microplace.com/learn_more">MicroPlace information section</a></p>
<p><a href="http://www.businessweek.com/technology/content/oct2007/tc20071023_930086.htm">Business Week: EBay: The Place for Microfinance</a></p>
<p><a href="http://home.businesswire.com/portal/site/google/index.jsp?ndmViewId=news_view&amp;newsId=20071024005417&amp;newsLang=en">Business Wire: MicroPlace Launches Investment Website to Address Global Poverty</a></p>
<p><a href="http://www.worldchanging.com/archives/007470.html">WorldChanging: Kiva vs. MicroPlace - What's the Difference?</a></p>
<p><a href="http://blog.wired.com/business/2007/10/ebay-owned-micr.html">Wired: EBay-owned MicroPlace Launches Microfinance Site</a></p>
<p>Here's a <a href="http://www.sfgate.com/cgi-bin/article.cgi?f=/c/a/2007/09/30/MN7QRSUKA.DTL&amp;tsp=1">background piece</a> on microlending from the <a href="http://www.sfgate.com/">SF Chronicle</a>. Unfortunately MicroPlace hadn't launched when it was written, so the site isn't included in the write-ups.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/932007-10-18T20:13:00-07:002008-01-24T00:19:36-08:00Simpler than dirt: RESTful Dynamic CSSJosh Susser<p>Way back when, I wrote about how to do <a href="/2006/03/23/dirt-simple-rcss-templates">Dirt Simple RCSS</a> in Rails. Now that Rails 2.0 is upon us, it's time to get even simpler. With all the restful magic in Rails 2.0, you can get even <strong>simpler than dirt</strong>.</p>
<p>Let's assume you have a restful User resource. You've got your "map.resources" in routes.rb, a typical User model, and a Users controller with standard views like index and show. Now you want to generate user-specific css based on state in the user model.</p>
<p>1) Create a view template called "show.css.erb". For example:</p>
<pre><code>p {
color: &lt;%= @user.color %&gt;;
}
</code></pre>
<p>2) Add a css format option to the respond_to block in the show action of the UsersController. You only need the default behavior for the css format, since all you need to do is render the view.</p>
<pre><code>def show
@user = User.find(params[:id])
respond_to do |format|
format.html
format.css
end
end
</code></pre>
<p>You can see the dynamically generated css at a url like /users/1.css in your browser.</p>
<p>Here's how to use the stylesheet in a view, assuming you've set up a @user object:</p>
<pre><code>&lt;%= stylesheet_link_tag formatted_user_path(@user, "css") %&gt;
</code></pre>
<p>Told you it was simple.</p>
<p>Note: Geoffrey "topfunky" Grosenbach also has a good <a href="http://nubyonrails.com/articles/dynamic-css">article on dynamic CSS</a>, which includes a nice screencast as well.</p><p>Way back when, I wrote about how to do <a href="/2006/03/23/dirt-simple-rcss-templates">Dirt Simple RCSS</a> in Rails. Now that Rails 2.0 is upon us, it's time to get even simpler. With all the restful magic in Rails 2.0, you can get even <strong>simpler than dirt</strong>.</p>
<p>Let's assume you have a restful User resource. You've got your "map.resources" in routes.rb, a typical User model, and a Users controller with standard views like index and show. Now you want to generate user-specific css based on state in the user model.</p>
<p>1) Create a view template called "show.css.erb". For example:</p>
<pre><code>p {
color: &lt;%= @user.color %&gt;;
}
</code></pre>
<p>2) Add a css format option to the respond_to block in the show action of the UsersController. You only need the default behavior for the css format, since all you need to do is render the view.</p>
<pre><code>def show
@user = User.find(params[:id])
respond_to do |format|
format.html
format.css
end
end
</code></pre>
<p>You can see the dynamically generated css at a url like /users/1.css in your browser.</p>
<p>Here's how to use the stylesheet in a view, assuming you've set up a @user object:</p>
<pre><code>&lt;%= stylesheet_link_tag formatted_user_path(@user, "css") %&gt;
</code></pre>
<p>Told you it was simple.</p>
<p>Note: Geoffrey "topfunky" Grosenbach also has a good <a href="http://nubyonrails.com/articles/dynamic-css">article on dynamic CSS</a>, which includes a nice screencast as well.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/922007-10-01T16:48:00-07:002008-01-24T00:19:36-08:00Everything old is new againJosh Susser<p>As you've likely seen in your feed reader about 50 times already, Rails is turning 2.0. The <a href="http://weblog.rubyonrails.org/2007/9/30/rails-2-0-0-preview-release">Preview Release has just been announced</a>, which means that 2.0 final is just a few release candidates away. So much has changed over the last year or so that a lot of the older material on this blog is out of date and needs updating for the reality of 2.0. Seems like now is a good time to get going on the rewriting. I'll be digging in and bringing stuff up to date over the next few weeks (months? there are quite a few articles after all).</p>
<p>Here's the list of articles I'm going to be taking a look at. I'm not certain that all need updates, but I think most of them do. If you have a request for something in particular for me to work on sooner, just leave a comment and I'll take that into account. Of course, I'll be updating the Many-to-many Dance-off first!</p>
<ul>
<li><a href="/2006/2/28/association-goodness">New association goodness in Rails 1.1</a></li>
<li><a href="/2006/3/1/association-goodness-2">New association goodness in Rails 1.1, part 2</a></li>
<li><a href="/2006/3/28/the-other-ways-through">The other ways :through</a></li>
<li><a href="/2006/4/3/polymorphic-through">The other side of polymorphic :through associations</a></li>
<li><a href="/2006/4/17/join-models-not-proxy-collections">Why aren't join models proxy collections?</a></li>
<li><a href="/2006/4/20/many-to-many-dance-off">Many-to-many Dance-off!</a></li>
<li><a href="/2006/4/21/self-referential-through">Self-referential has_many :through associations</a></li>
<li><a href="/2006/5/6/through-gets-uniq">has_many :through gets :uniq</a></li>
<li><a href="/2006/6/12/when-associations-arent-enough">When associations aren't enough</a></li>
<li><a href="/2006/6/12/when-associations-arent-enough-part-2">When associations aren't enough, part 2</a></li>
<li><a href="/2006/6/30/working-with-the-relationship-model">Working with the relationship model</a></li>
<li><a href="/2006/6/30/cruddy-searches">CRUDdy searches</a></li>
<li><a href="/2006/8/13/how-dynamic-finders-work">How dynamic finders work</a></li>
<li><a href="/2006/8/19/magic-join-model-creation">New on edge: Magic join model creation</a></li>
<li><a href="/2006/9/9/finding-unassociated-objects">Finding unassociated objects</a></li>
<li><a href="/2007/1/15/basic-rails-association-cardinality">Basic Rails association cardinality</a></li>
<li><a href="/2007/1/22/using-faux-accessors-to-initialize-values">Using faux accessors to initialize values</a></li>
</ul><p>As you've likely seen in your feed reader about 50 times already, Rails is turning 2.0. The <a href="http://weblog.rubyonrails.org/2007/9/30/rails-2-0-0-preview-release">Preview Release has just been announced</a>, which means that 2.0 final is just a few release candidates away. So much has changed over the last year or so that a lot of the older material on this blog is out of date and needs updating for the reality of 2.0. Seems like now is a good time to get going on the rewriting. I'll be digging in and bringing stuff up to date over the next few weeks (months? there are quite a few articles after all).</p>
<p>Here's the list of articles I'm going to be taking a look at. I'm not certain that all need updates, but I think most of them do. If you have a request for something in particular for me to work on sooner, just leave a comment and I'll take that into account. Of course, I'll be updating the Many-to-many Dance-off first!</p>
<ul>
<li><a href="/2006/2/28/association-goodness">New association goodness in Rails 1.1</a></li>
<li><a href="/2006/3/1/association-goodness-2">New association goodness in Rails 1.1, part 2</a></li>
<li><a href="/2006/3/28/the-other-ways-through">The other ways :through</a></li>
<li><a href="/2006/4/3/polymorphic-through">The other side of polymorphic :through associations</a></li>
<li><a href="/2006/4/17/join-models-not-proxy-collections">Why aren't join models proxy collections?</a></li>
<li><a href="/2006/4/20/many-to-many-dance-off">Many-to-many Dance-off!</a></li>
<li><a href="/2006/4/21/self-referential-through">Self-referential has_many :through associations</a></li>
<li><a href="/2006/5/6/through-gets-uniq">has_many :through gets :uniq</a></li>
<li><a href="/2006/6/12/when-associations-arent-enough">When associations aren't enough</a></li>
<li><a href="/2006/6/12/when-associations-arent-enough-part-2">When associations aren't enough, part 2</a></li>
<li><a href="/2006/6/30/working-with-the-relationship-model">Working with the relationship model</a></li>
<li><a href="/2006/6/30/cruddy-searches">CRUDdy searches</a></li>
<li><a href="/2006/8/13/how-dynamic-finders-work">How dynamic finders work</a></li>
<li><a href="/2006/8/19/magic-join-model-creation">New on edge: Magic join model creation</a></li>
<li><a href="/2006/9/9/finding-unassociated-objects">Finding unassociated objects</a></li>
<li><a href="/2007/1/15/basic-rails-association-cardinality">Basic Rails association cardinality</a></li>
<li><a href="/2007/1/22/using-faux-accessors-to-initialize-values">Using faux accessors to initialize values</a></li>
</ul>tag:blog.hasmanythrough.com,2006-02-27:Article/902007-08-08T15:38:00-07:002008-01-24T00:19:35-08:00Show flash messages on cached pagesJosh Susser<p>If you've ever used Rails page caching, you've probably run into the situation where you wanted to cache a page that had essentially static content, but couldn't because it showed a flash message. I ran into this recently, and thought it would be really slick to move the flash rendering from the server to the browser, so that the page content could be cached but the flash could still be rendered from a cookie. I mentioned my idea to a friend over at <a href="http://pivotalsf.com/">Pivotal Labs</a> and he said "Oh, we did the same thing just last week." What they did was very close to what I wanted, and they were ready to release it to the wild, so I sat down with pivot Brian Takita and spent a little while working with him to extract the feature, document it and package it as a Rails plugin.</p>
<p>The result: the <a href="http://www.pivotalblabs.com/articles/2007/08/08/cacheable-flash">Cacheable Flash</a> plugin. Brian's post has a good description, but also checkout the README. For the moment this affects the flash for all actions in a controller, but Brian and I are looking into :except/:only options to allow finer-grained control if that is ever needed.</p>
<p>To install:</p>
<pre><code>ruby script/plugin install svn://rubyforge.org/var/svn/pivotalrb/cacheable_flash/trunk
</code></pre>
<p>Thanks to Brian and Pivotal for this slick contribution. (By the way, doing TDD in JavaScript with JS-Unit is pretty freaking cool.)</p><p>If you've ever used Rails page caching, you've probably run into the situation where you wanted to cache a page that had essentially static content, but couldn't because it showed a flash message. I ran into this recently, and thought it would be really slick to move the flash rendering from the server to the browser, so that the page content could be cached but the flash could still be rendered from a cookie. I mentioned my idea to a friend over at <a href="http://pivotalsf.com/">Pivotal Labs</a> and he said "Oh, we did the same thing just last week." What they did was very close to what I wanted, and they were ready to release it to the wild, so I sat down with pivot Brian Takita and spent a little while working with him to extract the feature, document it and package it as a Rails plugin.</p>
<p>The result: the <a href="http://www.pivotalblabs.com/articles/2007/08/08/cacheable-flash">Cacheable Flash</a> plugin. Brian's post has a good description, but also checkout the README. For the moment this affects the flash for all actions in a controller, but Brian and I are looking into :except/:only options to allow finer-grained control if that is ever needed.</p>
<p>To install:</p>
<pre><code>ruby script/plugin install svn://rubyforge.org/var/svn/pivotalrb/cacheable_flash/trunk
</code></pre>
<p>Thanks to Brian and Pivotal for this slick contribution. (By the way, doing TDD in JavaScript with JS-Unit is pretty freaking cool.)</p>tag:blog.hasmanythrough.com,2006-02-27:Article/892007-07-17T01:58:00-07:002008-01-24T00:19:35-08:00New on edge: inferred foreign key name changeJosh Susser<p>Here's a change that has been a long time coming. (Really. The first time I heard DHH mention he wanted to do this was in March of 2006.) I liked it so much that I've been playing shepherd for it to make sure it happens. So if you're on edge, <a href="http://dev.rubyonrails.org/changeset/7188">now is the time</a>.</p>
<p>In 1.2.x and previous, the name of the foreign key of a belongs_to association is inferred to be the name of the association's class plus "_id". The class is inferred to be the camel-case equivalent of the association name, so in the simple case the foreign key ends up the same as the association name plus that "_id". For example: :line_item >> LineItem >> line_item_id. But if you explicitly set the class to something other than the default, the foreign key got inferred from the class instead of the association name. Example: :manager &lt;> Employee >> employee_id.</p>
<p>Pre 2.0:</p>
<pre><code>class Employee &lt; ActiveRecord::Base
belongs_to :manager, :class_name =&gt; "Employee", :foreign_key =&gt; "manager_id"
has_many :employees, :foreign_key =&gt; "manager_id"
end
</code></pre>
<p>As of now (and thus in 2.0), when the class is explicitly set, the foreign key will be inferred from the association name instead of the class. For example: :manager &lt;> Employee >> manager_id.</p>
<p>In edge/2.0:</p>
<pre><code>class Employee &lt; ActiveRecord::Base
belongs_to :manager, :class_name =&gt; "Employee"
has_many :employees, :foreign_key =&gt; "manager_id"
end
</code></pre>
<p>Why is this an improvement? Oh, it just saves a little typing.</p>
<pre><code>class Article &lt; ActiveRecord::Base
belongs_to :creator, :class_name =&gt; "User"
belongs_to :updater, :class_name =&gt; "User"
end
class User &lt; ActiveRecord::Base
has_many :creations, :class_name =&gt; "Article", :foreign_key =&gt; "creator_id"
has_many :updates, :class_name =&gt; "Article", :foreign_key =&gt; "updater_id"
end
</code></pre>
<p>Notice that you still need to be explicit about the foreign key name in the has_one or has_many associations. It would be a little nicer to be able to say something like <code>:using =&gt; :creator</code> in the has_many, but for now that's just a pipe dream.</p><p>Here's a change that has been a long time coming. (Really. The first time I heard DHH mention he wanted to do this was in March of 2006.) I liked it so much that I've been playing shepherd for it to make sure it happens. So if you're on edge, <a href="http://dev.rubyonrails.org/changeset/7188">now is the time</a>.</p>
<p>In 1.2.x and previous, the name of the foreign key of a belongs_to association is inferred to be the name of the association's class plus "_id". The class is inferred to be the camel-case equivalent of the association name, so in the simple case the foreign key ends up the same as the association name plus that "_id". For example: :line_item >> LineItem >> line_item_id. But if you explicitly set the class to something other than the default, the foreign key got inferred from the class instead of the association name. Example: :manager &lt;> Employee >> employee_id.</p>
<p>Pre 2.0:</p>
<pre><code>class Employee &lt; ActiveRecord::Base
belongs_to :manager, :class_name =&gt; "Employee", :foreign_key =&gt; "manager_id"
has_many :employees, :foreign_key =&gt; "manager_id"
end
</code></pre>
<p>As of now (and thus in 2.0), when the class is explicitly set, the foreign key will be inferred from the association name instead of the class. For example: :manager &lt;> Employee >> manager_id.</p>
<p>In edge/2.0:</p>
<pre><code>class Employee &lt; ActiveRecord::Base
belongs_to :manager, :class_name =&gt; "Employee"
has_many :employees, :foreign_key =&gt; "manager_id"
end
</code></pre>
<p>Why is this an improvement? Oh, it just saves a little typing.</p>
<pre><code>class Article &lt; ActiveRecord::Base
belongs_to :creator, :class_name =&gt; "User"
belongs_to :updater, :class_name =&gt; "User"
end
class User &lt; ActiveRecord::Base
has_many :creations, :class_name =&gt; "Article", :foreign_key =&gt; "creator_id"
has_many :updates, :class_name =&gt; "Article", :foreign_key =&gt; "updater_id"
end
</code></pre>
<p>Notice that you still need to be explicit about the foreign key name in the has_one or has_many associations. It would be a little nicer to be able to say something like <code>:using =&gt; :creator</code> in the has_many, but for now that's just a pipe dream.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/882007-07-14T17:16:00-07:002008-01-24T00:19:35-08:00Validate your existenceJosh Susser<p>Ever since I started using Rails I've been wanting a way to enforce referential integrity in the database. The Rails philosophy is to keep business logic out of the database and do it in Ruby. All those nifty validation methods are meant to take care of that. That means no foreign key constraints in the database - just use the rails validations.</p>
<p>But which validations to use? The thing that's always frustrated me is that there isn't a validation to enforce that a foreign key references a record that exists. Sure, <code>validates_presence_of</code> will make sure you have a foreign key that isn't nil. And <code>validates_associated</code> will tell you if the record referenced by that key passes its own validations. But that is either too little or too much, and what I want is in the middle ground. So I decided it was time to roll my own.</p>
<p>Enter <code>validates_existence_of</code>.</p><p>Ever since I started using Rails I've been wanting a way to enforce referential integrity in the database. The Rails philosophy is to keep business logic out of the database and do it in Ruby. All those nifty validation methods are meant to take care of that. That means no foreign key constraints in the database - just use the rails validations.</p>
<p>But which validations to use? The thing that's always frustrated me is that there isn't a validation to enforce that a foreign key references a record that exists. Sure, <code>validates_presence_of</code> will make sure you have a foreign key that isn't nil. And <code>validates_associated</code> will tell you if the record referenced by that key passes its own validations. But that is either too little or too much, and what I want is in the middle ground. So I decided it was time to roll my own.</p>
<p>Enter <code>validates_existence_of</code>.</p>
<pre><code>class Tagging &lt; ActiveRecord::Base
belongs_to :tag
belongs_to :taggable, :polymorphic =&gt; true
validates_existence_of :tag, :taggable
belongs_to :user
validates_existence_of :user, :allow_nil =&gt; true
end
</code></pre>
<p>The above example shows all the interesting bits. You can validate a simple belongs_to association, or one that is polymorphic. You can also use the <code>:allow_nil</code> option to allow for an optional foreign key. If the foreign key is non-nil, then the referenced record must exist.</p>
<p>Of course <code>validates_existence_of</code> only works for a <code>belongs_to</code> association.</p>
<p>Try it out for yourself:</p>
<pre><code>$ script/plugin install http://svn.hasmanythrough.com/public/plugins/validates_existence/
</code></pre>
<p><em>Caveat lector</em>: I've only tried this out on edge, but it should work fine on 1.2.3. Please let me know if you have problems with it.</p>
<p>If there is enough interest I'll submit this as a patch to core.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/872007-07-03T03:28:00-07:002008-01-24T00:19:35-08:00Check out your routesJosh Susser<p>Well I finally got off my keister and wrote up a <a href="http://dev.rubyonrails.org/changeset/7149">patch</a> for a feature a few of us have been using for a while. If you're running edge Rails, just svn up and type <code>rake routes</code> to see a list of all the routes defined for your app.</p>
<p>If you're running an older version of Rails (1.2.3 works, not sure about pre-1.2 though) but still want in on the action, you should install <a href="http://errtheblog.com/post/6069">sake</a> and then pour yourself a shot:</p>
<pre><code>$ sake -i http://pastie.caboo.se/74249.txt routes
$ sake routes
</code></pre>
<p>I've found this task indispensable when dealing with RESTful routing. If you're doing anything beyond the basics with routes, you should check this out. It can be a huge time saver.</p>
<p>And in closing, a little history and credit. While the patch was mine, the inspiration came from elsewhere. First there was Rick Olson's <a href="http://svn.techno-weenie.net/projects/plugins/routing_navigator/">routing_navigator plugin</a>, which I found both very useful and way too awkward to use effectively. I cobbled up some code to do that from a script, while at the same time Chris Wanstrath did the obvious thing and <a href="http://errtheblog.com/post/38">wrote a rake task</a> for it. I fixed that to show <a href="http://pastie.caboo.se/23100">routes with names</a>. And at last I got tired of the ugly formatting and fixed it up to the current incarnation. Just goes to show that software evolution beats intelligent designers. And if a tiny little patch has even that amount of history, just think about how some of the more complicated features may have developed.</p><p>Well I finally got off my keister and wrote up a <a href="http://dev.rubyonrails.org/changeset/7149">patch</a> for a feature a few of us have been using for a while. If you're running edge Rails, just svn up and type <code>rake routes</code> to see a list of all the routes defined for your app.</p>
<p>If you're running an older version of Rails (1.2.3 works, not sure about pre-1.2 though) but still want in on the action, you should install <a href="http://errtheblog.com/post/6069">sake</a> and then pour yourself a shot:</p>
<pre><code>$ sake -i http://pastie.caboo.se/74249.txt routes
$ sake routes
</code></pre>
<p>I've found this task indispensable when dealing with RESTful routing. If you're doing anything beyond the basics with routes, you should check this out. It can be a huge time saver.</p>
<p>And in closing, a little history and credit. While the patch was mine, the inspiration came from elsewhere. First there was Rick Olson's <a href="http://svn.techno-weenie.net/projects/plugins/routing_navigator/">routing_navigator plugin</a>, which I found both very useful and way too awkward to use effectively. I cobbled up some code to do that from a script, while at the same time Chris Wanstrath did the obvious thing and <a href="http://errtheblog.com/post/38">wrote a rake task</a> for it. I fixed that to show <a href="http://pastie.caboo.se/23100">routes with names</a>. And at last I got tired of the ugly formatting and fixed it up to the current incarnation. Just goes to show that software evolution beats intelligent designers. And if a tiny little patch has even that amount of history, just think about how some of the more complicated features may have developed.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/852007-05-24T17:40:00-07:002008-01-24T00:19:35-08:00Laying Tracks slidesJosh Susser<p>I had a great time at RailsConf 2007. Lots of good stuff to see, and it was great to meet so many people I've only known online. I was a bit overwhelmed by how many people introduced themselves as readers of this blog, but I appreciated every introduction. It's great to see who I'm talking to out there.</p>
<p>I was particularly pleased at the reaction to my presentation. Thanks for all the nice feedback. Here's the slides from the talk: <a href="http://hasmanythrough.com/layingtracks/">Laying Tracks</a>. They are slightly edited for online distribution, but the live show always has something that the studio recording doesn't. Like cutlery.</p>
<p>I'm still planning on converting this presentation to a guide that reads better than a slide deck. I'll get around to it in my copious spare time.</p><p>I had a great time at RailsConf 2007. Lots of good stuff to see, and it was great to meet so many people I've only known online. I was a bit overwhelmed by how many people introduced themselves as readers of this blog, but I appreciated every introduction. It's great to see who I'm talking to out there.</p>
<p>I was particularly pleased at the reaction to my presentation. Thanks for all the nice feedback. Here's the slides from the talk: <a href="http://hasmanythrough.com/layingtracks/">Laying Tracks</a>. They are slightly edited for online distribution, but the live show always has something that the studio recording doesn't. Like cutlery.</p>
<p>I'm still planning on converting this presentation to a guide that reads better than a slide deck. I'll get around to it in my copious spare time.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/842007-05-02T00:45:00-07:002008-01-24T00:19:35-08:00Getting arbitrary with assert_differenceJosh Susser<p>I'm working on Edge Rails again doing some RESTful API stuff, so I'm watching the Rails commits fairly closely these days. Earlier today Tobi committed patch <a href="http://dev.rubyonrails.org/browser/trunk/activesupport/lib/active_support/core_ext/test/difference.rb?rev=6647">[6647]</a> the long-awaited <code>assert_difference</code> and <code>assert_no_difference</code> test helpers. You can use them like so:</p>
<pre><code>def test_create_user
login = "bob"
name = "Bob Dobbs"
assert_difference(User, :count, 1) do
bob = User.create!(:login =&gt; login, :name =&gt; name)
assert_equal login, bob.login
assert_equal name, bob.name
end
end
</code></pre>
<p>That asserts that the result of sending the message :count to the User class object before and after running the code in the block will differ by 1. It's a nice way to check both pre- and post-conditions on tests without artificially inflating your assertion count. And I'll toss in the phrase "semantically rich" to see if you're paying attention.</p>
<p>I have a slightly different use case for this pattern than simply checking User.count before and after. When sending <code>User.create</code>!, I want to make sure the specific user didn't exist beforehand as a pre-condition, and does exist afterward as a post-condition. That's sort of impossible to do when all you we to work with is an object and a method, right? Not if we use the power of <em>blocks</em>!</p>
<pre><code>def test_create_user
login = "bob"
name = "Bob Dobbs"
assert_difference( lambda { User.find_all_by_login(login).size }, :call, 1 ) do
bob = User.create!(:login =&gt; login, :name =&gt; name)
assert_equal login, bob.login
assert_equal name, bob.name
end
end
</code></pre>
<p>There I use a lambda to dress up a block as a first-class object and pass it as the <code>object</code> argument, then pass <code>:call</code> as the <code>method</code> argument. Internally <code>assert_difference</code> does <code>object.send(method)</code>, which ends up doing the equivalent of <code>object.call</code>. That runs the code in the block, returning the number of users with that login identifier</p>
<p>There you have it: run arbitrary code in assert_difference using blocks.</p><p>I'm working on Edge Rails again doing some RESTful API stuff, so I'm watching the Rails commits fairly closely these days. Earlier today Tobi committed patch <a href="http://dev.rubyonrails.org/browser/trunk/activesupport/lib/active_support/core_ext/test/difference.rb?rev=6647">[6647]</a> the long-awaited <code>assert_difference</code> and <code>assert_no_difference</code> test helpers. You can use them like so:</p>
<pre><code>def test_create_user
login = "bob"
name = "Bob Dobbs"
assert_difference(User, :count, 1) do
bob = User.create!(:login =&gt; login, :name =&gt; name)
assert_equal login, bob.login
assert_equal name, bob.name
end
end
</code></pre>
<p>That asserts that the result of sending the message :count to the User class object before and after running the code in the block will differ by 1. It's a nice way to check both pre- and post-conditions on tests without artificially inflating your assertion count. And I'll toss in the phrase "semantically rich" to see if you're paying attention.</p>
<p>I have a slightly different use case for this pattern than simply checking User.count before and after. When sending <code>User.create</code>!, I want to make sure the specific user didn't exist beforehand as a pre-condition, and does exist afterward as a post-condition. That's sort of impossible to do when all you we to work with is an object and a method, right? Not if we use the power of <em>blocks</em>!</p>
<pre><code>def test_create_user
login = "bob"
name = "Bob Dobbs"
assert_difference( lambda { User.find_all_by_login(login).size }, :call, 1 ) do
bob = User.create!(:login =&gt; login, :name =&gt; name)
assert_equal login, bob.login
assert_equal name, bob.name
end
end
</code></pre>
<p>There I use a lambda to dress up a block as a first-class object and pass it as the <code>object</code> argument, then pass <code>:call</code> as the <code>method</code> argument. Internally <code>assert_difference</code> does <code>object.send(method)</code>, which ends up doing the equivalent of <code>object.call</code>. That runs the code in the block, returning the number of users with that login identifier</p>
<p>There you have it: run arbitrary code in assert_difference using blocks.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/802007-03-14T03:19:00-07:002008-01-24T00:19:35-08:00New on Edge: dynamic finders with hash attributes for creationJosh Susser<p>During the <a href="http://weblog.rubyonrails.org/2007/1/8/hackfest-2007-and-cdbaby-sprint">Hackfest</a> in January I put together a <a href="http://dev.rubyonrails.org/ticket/7368">patch</a> for an enhancement to Rails dynamic finders. It was just <a href="http://dev.rubyonrails.org/changeset/6420">committed</a> today (thanks, Jeremy!), so now's a good time to do a little explanation of it. It's just a little thing, so this should be brief.</p>
<p>Here's a typical situation:</p>
<pre><code>tag = Tag.find_by_name(name)
if tag.nil?
tag = Tag.create(:name =&gt; name, :creator =&gt; current_user)
end
</code></pre>
<p>You'd think the way to do that would be with the <code>find_or_create_by_name</code> dynamic finder, but that doesn't work since there's no way to search by name only, but create by name <em>and</em> creator. Well, that's just not right. So here's what we'll do:</p>
<pre><code>Tag.find_or_create_by_name(:name =&gt; name, :creator =&gt; current_user)
</code></pre>
<p>The new feature is that if you pass a hash to the finder, it will still use only the values named in the finder to find the object, but it will create a new object using all the values. </p>
<p>It's not a big deal or something I use every day, but I like it anyway.</p><p>During the <a href="http://weblog.rubyonrails.org/2007/1/8/hackfest-2007-and-cdbaby-sprint">Hackfest</a> in January I put together a <a href="http://dev.rubyonrails.org/ticket/7368">patch</a> for an enhancement to Rails dynamic finders. It was just <a href="http://dev.rubyonrails.org/changeset/6420">committed</a> today (thanks, Jeremy!), so now's a good time to do a little explanation of it. It's just a little thing, so this should be brief.</p>
<p>Here's a typical situation:</p>
<pre><code>tag = Tag.find_by_name(name)
if tag.nil?
tag = Tag.create(:name =&gt; name, :creator =&gt; current_user)
end
</code></pre>
<p>You'd think the way to do that would be with the <code>find_or_create_by_name</code> dynamic finder, but that doesn't work since there's no way to search by name only, but create by name <em>and</em> creator. Well, that's just not right. So here's what we'll do:</p>
<pre><code>Tag.find_or_create_by_name(:name =&gt; name, :creator =&gt; current_user)
</code></pre>
<p>The new feature is that if you pass a hash to the finder, it will still use only the values named in the finder to find the object, but it will create a new object using all the values. </p>
<p>It's not a big deal or something I use every day, but I like it anyway.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/792007-03-01T06:42:00-08:002008-01-24T00:19:34-08:00Using your connectionsJosh Susser<p>ActiveRecord does a great job of insulating you from a lot of the hard stuff about using SQL, and it packs the results of queries up nicely as model objects. What could be better than that? Well, sometimes you don't want all that. Sometimes you just want what you want.</p>
<p>Let's pretend you're building a blog and want to show a little calendar in the sidebar that shows all the days on which a post was made. You don't care about the title, the time of posting, the tags or category of the posts - just the date. You could do that the obvious way using the model class and some Ruby mojo:</p>
<pre><code>posts = Post.find(:all)
days = posts.collect { |post| post.published_on }.uniq
</code></pre>
<p>What's wrong with that? It's short and easy to understand, a nearly perfect snippet of Ruby goodness. However, it is a real dog to run. It is grabbing all the data for every post including the body text and allocating an ActiveRecord model object for each one, just to get the publication date. You really should be nicer to your database, and it's a shame throwing away all those barely used objects. What would your mother think of you? Luckily there's an easier way.</p>
<p>You probably know about the <code>find_by_sql</code> method that lets you run arbitrary SQL queries and returns ActiveRecord models. That's spiffy, but it's still overkill for what we're doing. ActiveRecord::Base also provides low-level access to its database connector that can be used to fetch primitive values using raw SQL. This is sometimes just what we want to do.</p>
<pre><code>days = Post.connection.select_values("SELECT DISTINCT published_on FROM posts")
</code></pre>
<p>That's much less data to be shipping around, and less work for Ruby to extract what you're interested in. I'll be the first to admit the code isn't as pretty. You actually have to mess up your lovely Ruby code with SQL. But you shouldn't be afraid of SQL. Just because ActiveRecord lets you avoid getting your hands dirty with SQL most of the time doesn't mean you should try to avoid SQL all the time.</p>
<p>By the way, in reality we'd probably be storing a datetime for when the post was published, so we'd do <code>DATE(published_at)</code> instead of <code>published_on</code> in the query, but I was trying to keep the example simple.</p>
<p>You can see the full set of connector <a href="http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/DatabaseStatements.html">database statements</a> in the Rails API docs. I'd stay away from using most of them, but the selection methods can come in handy when you don't want to break your back doing things the ORM way.</p><p>ActiveRecord does a great job of insulating you from a lot of the hard stuff about using SQL, and it packs the results of queries up nicely as model objects. What could be better than that? Well, sometimes you don't want all that. Sometimes you just want what you want.</p>
<p>Let's pretend you're building a blog and want to show a little calendar in the sidebar that shows all the days on which a post was made. You don't care about the title, the time of posting, the tags or category of the posts - just the date. You could do that the obvious way using the model class and some Ruby mojo:</p>
<pre><code>posts = Post.find(:all)
days = posts.collect { |post| post.published_on }.uniq
</code></pre>
<p>What's wrong with that? It's short and easy to understand, a nearly perfect snippet of Ruby goodness. However, it is a real dog to run. It is grabbing all the data for every post including the body text and allocating an ActiveRecord model object for each one, just to get the publication date. You really should be nicer to your database, and it's a shame throwing away all those barely used objects. What would your mother think of you? Luckily there's an easier way.</p>
<p>You probably know about the <code>find_by_sql</code> method that lets you run arbitrary SQL queries and returns ActiveRecord models. That's spiffy, but it's still overkill for what we're doing. ActiveRecord::Base also provides low-level access to its database connector that can be used to fetch primitive values using raw SQL. This is sometimes just what we want to do.</p>
<pre><code>days = Post.connection.select_values("SELECT DISTINCT published_on FROM posts")
</code></pre>
<p>That's much less data to be shipping around, and less work for Ruby to extract what you're interested in. I'll be the first to admit the code isn't as pretty. You actually have to mess up your lovely Ruby code with SQL. But you shouldn't be afraid of SQL. Just because ActiveRecord lets you avoid getting your hands dirty with SQL most of the time doesn't mean you should try to avoid SQL all the time.</p>
<p>By the way, in reality we'd probably be storing a datetime for when the post was published, so we'd do <code>DATE(published_at)</code> instead of <code>published_on</code> in the query, but I was trying to keep the example simple.</p>
<p>You can see the full set of connector <a href="http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/DatabaseStatements.html">database statements</a> in the Rails API docs. I'd stay away from using most of them, but the selection methods can come in handy when you don't want to break your back doing things the ORM way.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/732007-01-22T08:02:00-08:002008-01-24T00:19:34-08:00Using faux accessors to initialize valuesJosh Susser<p>One of the little annoyances of ActiveRecord is not being able to override the <code>#initialize</code> method in your class. There's the <code>#after_initialize</code> callback, but there's no way to pass an argument to it when you create the record. So what do you do if you want to create a record and initialize it with some stuff that isn't one of its attributes? The answer is, you cheat.</p>
<blockquote>
<p>The implementor must cheat, but not get caught. -- <a href="http://en.wikipedia.org/wiki/Dan_Ingalls">Dan Ingalls</a></p>
</blockquote><p>One of the little annoyances of ActiveRecord is not being able to override the <code>#initialize</code> method in your class. There's the <code>#after_initialize</code> callback, but there's no way to pass an argument to it when you create the record. So what do you do if you want to create a record and initialize it with some stuff that isn't one of its attributes? The answer is, you cheat.</p>
<blockquote>
<p>The implementor must cheat, but not get caught. -- <a href="http://en.wikipedia.org/wiki/Dan_Ingalls">Dan Ingalls</a></p>
</blockquote>
<p>The usual way to instantiate a new record is to use <code>#new</code> and pass a hash of attributes. The cool thing is that the way ActiveRecord assigns those attributes to the new record is easily hackable. For example, consider this:</p>
<pre><code>post = Post.new(:title =&gt; "Man bites dog!")
</code></pre>
<p>ActiveRecord will set the :title attribute of the new post by using the #title= accessor. Very simple. But that opens a world of possibilities...</p>
<pre><code>class User &lt; ActiveRecord::Base
def full_name=(full_name)
self.first = full_name.split(" ").first
self.last = full_name.split(" ").last
end
end
user = User.new(:full_name =&gt; "Monty Python")
</code></pre>
<p>That's a pretty simple example. Here's a more standard alternate approach:</p>
<pre><code>class User &lt; ActiveRecord::Base
attr :full_name
def after_initialize
self.first_name = @full_name.split(" ").first
self.last_name = @full_name.split(" ").last
end
end
</code></pre>
<p>That's not too bad, but I still think the first example is better. Why muck around with a callback when you don't have to?</p>
<p>Now, check this out.</p>
<pre><code>class Post &lt; ActiveRecord::Base
has_and_belongs_to_many :categories
def init_category=(category)
self.categories &lt;&lt; category
end
end
post = Post.new(:init_category =&gt; Category.find_by_name("Main"))
</code></pre>
<p>The above example shows how to create a new post with a default category set. Since we're using <code>has_and_belongs_to_many</code> here, we don't have to worry about managing whether the object is a new record since habtm does that for us. But it's a little trickier if we want to use a join model...</p>
<pre><code>class Post &lt; ActiveRecord::Base
has_many :taggings, :dependent =&gt; :destroy
has_many :tags, :through =&gt; :taggings
def tag_list=(tag_string)
if self.new_record?
@tag_string = tag_string
else
self.create_taggings(tag_string) # parse tags, create tags &amp; taggings
end
end
def after_create
self.tag_list = @tag_string if @tag_string
end
end
post = Post.new(:tag_list =&gt; "chocolate, dessert, sinful")
</code></pre>
<p><code>has_many :through</code> won't work with new records, as it needs saved records with actual ids to use in the foreign keys in the join model. This example defers processing the tag_list until a new record is saved, at which point it parses out the tags and creates the taggings to join them to the post.</p>
<p>Tricks like that are why I just love ruby!</p>tag:blog.hasmanythrough.com,2006-02-27:Article/722007-01-15T16:35:00-08:002008-01-24T00:19:34-08:00Basic Rails association cardinalityJosh Susser<p>Over the weekend I noticed that the Rails API docs didn't have any basic information on relation cardinality and how that maps to associations. It wasn't difficult to come up with some overview documentation, and a <a href="http://dev.rubyonrails.org/ticket/7022">patch</a> later, it's now part of the API docs. I'm reproducing it here because most people won't notice its appearance in the docs, and it's good to call out basic stuff like this.</p><p>Over the weekend I noticed that the Rails API docs didn't have any basic information on relation cardinality and how that maps to associations. It wasn't difficult to come up with some overview documentation, and a <a href="http://dev.rubyonrails.org/ticket/7022">patch</a> later, it's now part of the API docs. I'm reproducing it here because most people won't notice its appearance in the docs, and it's good to call out basic stuff like this.</p>
<h2>Cardinality and associations</h2>
<p>ActiveRecord associations can be used to describe relations with one-to-one, one-to-many and many-to-many cardinality. Each model uses an association to describe its role in the relation. In each case, the <code>belongs_to</code> association is used in the model that has the foreign key.</p>
<h3>One-to-one</h3>
<p>Use <code>has_one</code> in the base, and <code>belongs_to</code> in the associated model.</p>
<pre><code>class Employee &lt; ActiveRecord::Base
has_one :office
end
class Office &lt; ActiveRecord::Base
belongs_to :employee # foreign key - employee_id
end
</code></pre>
<h3>One-to-many</h3>
<p>Use <code>has_many</code> in the base, and <code>belongs_to</code> in the associated model.</p>
<pre><code>class Manager &lt; ActiveRecord::Base
has_many :employees
end
class Employee &lt; ActiveRecord::Base
belongs_to :manager # foreign key - manager_id
end
</code></pre>
<h3>Many-to-many</h3>
<p>There are two ways to build a many-to-many relationship.</p>
<p>The first way uses a has_many association with the :through option and a join model, so
there are two stages of associations.</p>
<pre><code>class Assignment &lt; ActiveRecord::Base
belongs_to :programmer # foreign key - programmer_id
belongs_to :project # foreign key - project_id
end
class Programmer &lt; ActiveRecord::Base
has_many :assignments
has_many :projects, :through =&gt; :assignments
end
class Project &lt; ActiveRecord::Base
has_many :assignments
has_many :programmers, :through =&gt; :assignments
end
</code></pre>
<p>For the second way, use <code>has_and_belongs_to_many</code> in both models. This requires a join table that has no corresponding model or primary key.</p>
<pre><code>class Programmer &lt; ActiveRecord::Base
has_and_belongs_to_many :projects # foreign keys in the join table
end
class Project &lt; ActiveRecord::Base
has_and_belongs_to_many :programmers # foreign keys in the join table
end
</code></pre>
<p>It is not always a simple decision which way of building a many-to-many relationship is best. But if you need to work with the relationship model as its own entity, then you'll need to use <code>has_many :through</code>. Use <code>has_and_belongs_to_many</code> when working with legacy schemas or when you never work directly with the relationship itself.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/712007-01-12T07:40:00-08:002008-01-24T00:19:34-08:00Don't make a new one on my accountJosh Susser<p>Here's a fairly obscure but useful trick to optimize session usage in Rails. I can pretty safely bet that you haven't used it yet because it was broken until two weeks ago when, with bloody forehead, I discovered the bug that had been causing me to bang my head repeatedly on the table. Big kudos to Jeremy Kemper for assisting in the speedy fix. Anyway, where was I?</p>
<p>Oh yeah. Sessions.</p>
<p>Sessions are mostly useless. There, I said it. I'd guess that at least 95% of the time all people are using sessions for in Rails are flash messages and setting the :user_id of authenticated users. If your app doesn't authenticate users or have some other unusual need for sessions, it's a pretty painful waste of that overhead to use them only for flash messages.</p><p>Here's a fairly obscure but useful trick to optimize session usage in Rails. I can pretty safely bet that you haven't used it yet because it was broken until two weeks ago when, with bloody forehead, I discovered the bug that had been causing me to bang my head repeatedly on the table. Big kudos to Jeremy Kemper for assisting in the speedy fix. Anyway, where was I?</p>
<p>Oh yeah. Sessions.</p>
<p>Sessions are mostly useless. There, I said it. I'd guess that at least 95% of the time all people are using sessions for in Rails are flash messages and setting the :user_id of authenticated users. If your app doesn't authenticate users or have some other unusual need for sessions, it's a pretty painful waste of that overhead to use them only for flash messages.</p>
<p>One of the nice things about how <a href="http://mephistoblog.com/">Mephisto</a> is designed is that only authenticated users get a session when they login to the admin console. (Yes, there are no flash messages.) That means that the kajillions of guest users who are just reading a blog don't get a session. And that removes a lot of overhead from serving up pages, especially when most of the pages are cached as static HTML (yay for Rails' page caching!). I think that was a great decision, but it became a pain when I decided I wanted to highlight comments I made in my own blog as special so readers could easily see which comments were mine.</p>
<p>The problem was that the main controller is configured with <code>session :off</code>, so no session was available to check to see if there was an authenticated user making the comment. Now, the admin controller has sessions on, so if I had already logged in on the admin console I'd have a session_id cookie in the browser, and the main controller could just notice it there and use it to get the commenter's user id to mark the comment as special. At least in theory. But you can't see an existing session with sessions turned off in your controller!</p>
<p>It turns out there is a lovely option on the <code>session</code> directive for controllers.</p>
<pre><code>LazyController &lt; ActiveController::Base
session :new_session =&gt; false
# ...
end
</code></pre>
<p>Using the <code>:new_session =&gt; false</code> option tells Rails not to create a new session if there isn't one already, but if there is, go ahead and use it. That's exactly what I needed, and it ended up working perfectly to let me hack Mephisto to mark my comments as special. (At least it did after Jeremy and I fixed the crashing bug.) If you use this option and there is no previously existing session, the session will appear as an empty hash. You can set values in that hash, but they won't stick around to the next action.</p>
<p>You can see the special comments in action right here in my blog - just look around. You can also use that feature in your own Mephisto blog, since Rick accepted my patch and now trunk will do that for you too. Check out <a href="http://mephisto.stikipad.com/help/show/Special+comment+styles">the wiki page</a> on how to do it.</p>
<p>And if you're not using Mephisto, you can still use the <code>:new_session =&gt; false</code> option to lighten the load on your web app. I think this is potentially very useful for RESTful apps. I like the idea of blending admin functions into the normal content UI. This way you can have sessionless guest users and still provide authorized admin features on public pages.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/702006-12-28T20:22:00-08:002008-01-24T00:19:34-08:00Stop using the rails commandJosh Susser<p>Here's a tip that may make your life a little simpler when you need to start a new Rails project. Don't use the <code>rails</code> shell command.</p>
<p>We have a few people building Rails apps at work, and I wanted to make it dead simple for people to start a new Rails app and have it include all our standard customizations. Instead of hacking the generator for the rails command, I decided to use Subversion to help me out.</p>
<p>I ran the rails command one last time to create a pristine app. I checked it in to svn as the <code>exemplar</code> project and went to town customizing it. I set up the usual svn stuff like ignoring logs and database.yml, added datbase.yml.example, set up externals for standard plugins, etc. Now starting a new app is just</p>
<pre><code>svn copy /rails/exemplar /rails/new_app
svn checkout /rails/new_app
</code></pre>
<p>Notice that I don't have to do the svn import for new projects. And the svn:ignores etc. are already there.</p>
<p>There are of course a few things you might want to tweak for new projects, like the session cookie name or anything else that uses the name of the app. And when you upgrade to a new version of Rails you'll need to run the upgrade scripts and such on each app. But with svn working for you, you can diff revisions to see what you need to update, which is also helpful.</p><p>Here's a tip that may make your life a little simpler when you need to start a new Rails project. Don't use the <code>rails</code> shell command.</p>
<p>We have a few people building Rails apps at work, and I wanted to make it dead simple for people to start a new Rails app and have it include all our standard customizations. Instead of hacking the generator for the rails command, I decided to use Subversion to help me out.</p>
<p>I ran the rails command one last time to create a pristine app. I checked it in to svn as the <code>exemplar</code> project and went to town customizing it. I set up the usual svn stuff like ignoring logs and database.yml, added datbase.yml.example, set up externals for standard plugins, etc. Now starting a new app is just</p>
<pre><code>svn copy /rails/exemplar /rails/new_app
svn checkout /rails/new_app
</code></pre>
<p>Notice that I don't have to do the svn import for new projects. And the svn:ignores etc. are already there.</p>
<p>There are of course a few things you might want to tweak for new projects, like the session cookie name or anything else that uses the name of the app. And when you upgrade to a new version of Rails you'll need to run the upgrade scripts and such on each app. But with svn working for you, you can diff revisions to see what you need to update, which is also helpful.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/682006-11-23T09:06:26-08:002008-01-24T00:19:33-08:00Rails 1.2 RC1 and test casesJosh Susser<p>Last night, <a href="http://weblog.rubyonrails.org/2006/11/23/rails-1-2-release-candidate-1">DHH announced the release</a> of <a href="http://dev.rubyonrails.org/svn/rails/branches/1-2-pre-release/">Rails 1.2 RC1</a>. I predict we'll see a minor blogstorm of posts all reiterating that fact and talking about the new features. That's all fine, but this post is not one of them. Rather, it's a call to action for what to actually do with this release candidate.</p>
<p>For those not familiar with the term a <em>release candidate</em> is a <a href="http://en.wikipedia.org/wiki/Development_stage">development stage</a> used in standard software development practices. (For those of you already familiar with the concept, please excuse this moment of pedantry while I catch everyone else up.) The point of a release candidate is to let the code bake for a bit while not making any changes, giving the development team a chance to find any lingering bugs. The developers are saying, "we think we're done, but lets make sure there aren't any showstoppers that we haven't found yet." In one sense it's an admission that test coverage is incomplete, but the world isn't perfect and neither is anyone's test suite.</p><p>Last night, <a href="http://weblog.rubyonrails.org/2006/11/23/rails-1-2-release-candidate-1">DHH announced the release</a> of <a href="http://dev.rubyonrails.org/svn/rails/branches/1-2-pre-release/">Rails 1.2 RC1</a>. I predict we'll see a minor blogstorm of posts all reiterating that fact and talking about the new features. That's all fine, but this post is not one of them. Rather, it's a call to action for what to actually do with this release candidate.</p>
<p>For those not familiar with the term a <em>release candidate</em> is a <a href="http://en.wikipedia.org/wiki/Development_stage">development stage</a> used in standard software development practices. (For those of you already familiar with the concept, please excuse this moment of pedantry while I catch everyone else up.) The point of a release candidate is to let the code bake for a bit while not making any changes, giving the development team a chance to find any lingering bugs. The developers are saying, "we think we're done, but lets make sure there aren't any showstoppers that we haven't found yet." In one sense it's an admission that test coverage is incomplete, but the world isn't perfect and neither is anyone's test suite.</p>
<p>When bugs are found in a release candidate, they are either deferred to a future release, or fixed and incorporated into a new release candidate. Bugs may be deferred if there are simple enough workarounds, or if the changes to fix the bug would be too extensive. That process continues until the development team decides the code is stable enough to release for real, or all get fired and replaced by a team of drunken monkeys who will release anything management tells them to.</p>
<p>OK, enough pedantry. If you want to know more about software development practices, buy a book.</p>
<p>So now what? Well, you've got a shiny, new release candidate. Time to find some bugs! You can easily install the gems in one line:</p>
<pre><code>gem install rails --source http://gems.rubyonrails.org --include-dependencies
</code></pre>
<p>Or you can freeze a Rails project to the RC1 tag in subversion:</p>
<pre><code>rake rails:freeze:edge TAG=rel_1-2-0_RC1
</code></pre>
<p>Note that RC1 isn't the same as edge trunk. Some changes in trunk aren't in RC1. Also note that if you freeze edge to the rel_1-2-0_RC1 tag, you'll have to re-freeze to a new tag when the next RC is made available.</p>
<p>Once you've got RC1 set up either as gems or frozen in your project, run your tests. (This assumes all your tests passed on Rails 1.1.6.) Then do the things you do to check out the parts of your app you don't really have good test coverage for. Come on, we know there are some. Maybe you want to go through a deployment cycle too. Anyway, play around with it and see how it works.</p>
<p>But what to do if your tests fail, or if something else goes wrong? File a bug on the Rails <a href="http://dev.rubyonrails.org/newticket">trac</a> and put "1.2regression" in the keywords field. You probably want to look at the <a href="http://dev.rubyonrails.org/report/29">report</a> first to see if someone has already entered the same bug, and just add a comment there if you have new information.</p>
<p>Now, here's the point of this whole post... When you create the ticket to report your bug, if by any means you can, <strong>include a failing test case</strong>. DHH said this at the end of his <a href="http://weblog.rubyonrails.org/2006/11/23/rails-1-2-release-candidate-1">announcement</a>, but it bears repeating. Test cases are important to help fix the bug, but also to create a regression test to make sure the bug never reoccurs. Incidentally, if your test case is incorporated into the Rails test suite, you get to say you're a Rails contributor. Isn't that worth the price of admission?</p>tag:blog.hasmanythrough.com,2006-02-27:Article/672006-10-31T09:04:11-08:002008-01-24T00:19:33-08:00Merb is the new blackJosh Susser<p>According to <a href="http://brainspl.at/">Ezra Zygmuntowicz</a>, I just deployed to production the second Merb app ever written. Woo-hoo. Or today, maybe I should say Woo-hooooOOOooooOOOOOoooo!</p>
<p>If you don't have your ear to the ground, you may have missed hearing about <a href="http://merb.rubyforge.org/">Merb</a>. It's Ezra's new mini-framework written in Ruby. It combines a Mongrel handler and a framework that can route requests and render ERb templates. (Mongrel + Erb = Merb) Ezra started Merb as a better way to handle file uploads. Rails isn't particularly nice about uploads - its single-threading can lock up listeners for long-running actions like uploads, which is no fun. But Merb can do multiple threads, so it doesn't lock up and happily processes multiple requests with only one Mongrel process.</p>
<p>Like most of what I do these days, my Merb app is an internal tool so I can't show it off. But just to let you know how cool Merb is becoming, let me give you the rundown. I needed a little web service that allowed me to upload RPM package files and add them to our own private repository, then run a script to rebuild the repository metadata. So I wrote an extremely simple app in Merb. It consists of one Controller with just one action. Including configuration, the whole thing is under 40 lines of code. I didn't need to make a view template since all I wanted to do was return various result strings, and I could easily do those inline. I also wrote a little client command line script in Ruby that wraps curl to upload the files to the web service. (I had to use curl because Ruby doesn't have a simple solution for doing multipart file uploads.) Most of the code in both the service and the client script is actually error handling, checking and reporting for invalid file uploads.</p>
<p>Working in a pre-0.1 framework can be rough-going. But I'm having fun working with Merb and finding bugs for Ezra, and making some small contributions to the development of the framework. I'm going to keep kibitzing in with Ezra on Merb and helping him with some features as I'm able. It seems like it has the potential to be a good sidekick for Rails for an app that needs uploading, and a nice, simple alternative for small apps that don't need all the power that Rails provides.</p><p>According to <a href="http://brainspl.at/">Ezra Zygmuntowicz</a>, I just deployed to production the second Merb app ever written. Woo-hoo. Or today, maybe I should say Woo-hooooOOOooooOOOOOoooo!</p>
<p>If you don't have your ear to the ground, you may have missed hearing about <a href="http://merb.rubyforge.org/">Merb</a>. It's Ezra's new mini-framework written in Ruby. It combines a Mongrel handler and a framework that can route requests and render ERb templates. (Mongrel + Erb = Merb) Ezra started Merb as a better way to handle file uploads. Rails isn't particularly nice about uploads - its single-threading can lock up listeners for long-running actions like uploads, which is no fun. But Merb can do multiple threads, so it doesn't lock up and happily processes multiple requests with only one Mongrel process.</p>
<p>Like most of what I do these days, my Merb app is an internal tool so I can't show it off. But just to let you know how cool Merb is becoming, let me give you the rundown. I needed a little web service that allowed me to upload RPM package files and add them to our own private repository, then run a script to rebuild the repository metadata. So I wrote an extremely simple app in Merb. It consists of one Controller with just one action. Including configuration, the whole thing is under 40 lines of code. I didn't need to make a view template since all I wanted to do was return various result strings, and I could easily do those inline. I also wrote a little client command line script in Ruby that wraps curl to upload the files to the web service. (I had to use curl because Ruby doesn't have a simple solution for doing multipart file uploads.) Most of the code in both the service and the client script is actually error handling, checking and reporting for invalid file uploads.</p>
<p>Working in a pre-0.1 framework can be rough-going. But I'm having fun working with Merb and finding bugs for Ezra, and making some small contributions to the development of the framework. I'm going to keep kibitzing in with Ezra on Merb and helping him with some features as I'm able. It seems like it has the potential to be a good sidekick for Rails for an app that needs uploading, and a nice, simple alternative for small apps that don't need all the power that Rails provides.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/632006-09-09T15:21:44-07:002008-01-24T00:19:33-08:00Finding unassociated objectsJosh Susser<p>Say you have two models with a one-to-many relationship (using belongs_to and has_many). For example, a manager has many employees.</p>
<pre><code># Manager
has_many :employees
# Employee
belongs_to :manager
</code></pre>
<p>Rails associations make it easy to find all the employees belonging to a manager (<code>manager.employees</code>). And a rather simple find with conditions gets you the list of employees who belong to any manager, or even the orphaned employees with no manager.</p>
<pre><code># Employee
def self.find_managed
find(:all, :conditions =&gt; "manager_id IS NOT NULL")
end
def self.find_orphans
find(:all, :conditions =&gt; "manager_id IS NULL")
end
</code></pre><p>Say you have two models with a one-to-many relationship (using belongs_to and has_many). For example, a manager has many employees.</p>
<pre><code># Manager
has_many :employees
# Employee
belongs_to :manager
</code></pre>
<p>Rails associations make it easy to find all the employees belonging to a manager (<code>manager.employees</code>). And a rather simple find with conditions gets you the list of employees who belong to any manager, or even the orphaned employees with no manager.</p>
<pre><code># Employee
def self.find_managed
find(:all, :conditions =&gt; "manager_id IS NOT NULL")
end
def self.find_orphans
find(:all, :conditions =&gt; "manager_id IS NULL")
end
</code></pre>
<p>What if we need to find all the employees that don't work for a particular manager?</p>
<pre><code># Manager
has_many :employees do
def not
find(:conditions =&gt; ["employees.id != ?", proxy_owner.id])
end
end
</code></pre>
<p>That association extension lets us use <code>manager.employees.not</code> to find the list of employees that don't work for that manager.</p>
<p>It's a bit harder to find manager with no employees at all. Still, we can do it without too much ugly SQL in our pretty Rails code. We want to find all managers for which there are no employees that belong to them.</p>
<pre><code># Manager
def self.find_without_employees
find(:all, :readonly =&gt; false,
:select =&gt; "managers.*",
:joins =&gt; "LEFT OUTER JOIN employees e ON managers.id = e.manager_id",
:conditions =&gt; "e.id IS NULL")
end
</code></pre>
<p>Then <code>Manager.find_without_employees</code> returns all the managers without any employees. Time for a re-org!</p>tag:blog.hasmanythrough.com,2006-02-27:Article/622006-08-27T20:55:00-07:002008-01-24T00:19:33-08:00Validate all your records in one fell swoopJosh Susser<p>I'm working on an app now that has to import a big SQL file directly into MySQL to populate the tables with data exported from an older codebase. I don't entirely trust this data, so I wanted to run it all through my model validations to make sure nothing is broken. <a href="http://www.atmos.org/">Corey Donohoe</a> gave me a snippet for validating fixtures that got me started, which I spruced up and made it deal with STI and "paginate" large tables.</p>
<pre><code># file: validate_models.rake
# task: rake db:validate_models
namespace :db do
desc "Run model validations on all model records in database"
task :validate_models =&gt; :environment do
puts "-- records - model --"
Dir.glob(RAILS_ROOT + '/app/models/**/*.rb').each { |file| require file }
Object.subclasses_of(ActiveRecord::Base).select { |c|
c.base_class == c}.sort_by(&amp;:name).each do |klass|
total = klass.count
printf "%10d - %s\n", total, klass.name
chunk_size = 1000
(total / chunk_size + 1).times do |i|
chunk = klass.find(:all, :offset =&gt; (i * chunk_size), :limit =&gt; chunk_size)
chunk.reject(&amp;:valid?).each do |record|
puts "#{record.class}: id=#{record.id}"
p record.errors.full_messages
puts
end rescue nil
end
end
end
end
</code></pre>
<p>Now I can check to see if any of that imported data is invalid. Sweet.</p>
<p>After I got done writing the task I realized it is more generally useful than just for validating imported data. If I add new validations to my models, I can use this task to check the records already in the database. Wow, I suddenly feel so embarrassed that I never thought to do that before.</p><p>I'm working on an app now that has to import a big SQL file directly into MySQL to populate the tables with data exported from an older codebase. I don't entirely trust this data, so I wanted to run it all through my model validations to make sure nothing is broken. <a href="http://www.atmos.org/">Corey Donohoe</a> gave me a snippet for validating fixtures that got me started, which I spruced up and made it deal with STI and "paginate" large tables.</p>
<pre><code># file: validate_models.rake
# task: rake db:validate_models
namespace :db do
desc "Run model validations on all model records in database"
task :validate_models =&gt; :environment do
puts "-- records - model --"
Dir.glob(RAILS_ROOT + '/app/models/**/*.rb').each { |file| require file }
Object.subclasses_of(ActiveRecord::Base).select { |c|
c.base_class == c}.sort_by(&amp;:name).each do |klass|
total = klass.count
printf "%10d - %s\n", total, klass.name
chunk_size = 1000
(total / chunk_size + 1).times do |i|
chunk = klass.find(:all, :offset =&gt; (i * chunk_size), :limit =&gt; chunk_size)
chunk.reject(&amp;:valid?).each do |record|
puts "#{record.class}: id=#{record.id}"
p record.errors.full_messages
puts
end rescue nil
end
end
end
end
</code></pre>
<p>Now I can check to see if any of that imported data is invalid. Sweet.</p>
<p>After I got done writing the task I realized it is more generally useful than just for validating imported data. If I add new validations to my models, I can use this task to check the records already in the database. Wow, I suddenly feel so embarrassed that I never thought to do that before.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/612006-08-19T14:17:00-07:002008-01-24T00:19:33-08:00New on edge: Magic join model creationJosh Susser<p>Mark the date: August 18, 2006 is the day that has_many :through finally beat has_and_belongs_to_many into submission. Yesterday, Rails <a href="http://rubyonrails.org/core">core</a> member <a href="http://bitsweat.net/">Jeremy Kemper</a> checked in a <a href="http://dev.rubyonrails.org/changeset/4786">change</a> to Rails trunk ActiveRecord that allows using <code>&lt;&lt;</code> and friends on <code>has_many :through</code> associations. If you don't know why this is cool (or needed!), take a moment and read <a href="http://blog.hasmanythrough.com/articles/2006/04/17/join-models-not-proxy-collections">this</a> for background.</p>
<p>With this change, ActiveRecord will automagically create the join model record needed to link the associated object. What does that mean? Check this out...</p>
<pre><code>post.tags &lt;&lt; Tag.find_or_create_by_name("magic")
</code></pre>
<p>In the old world, that would have been</p>
<pre><code>post.taggings.create!(:tag =&gt; Tag.find_or_create_by_name("magic"))
</code></pre>
<p>And then you'd <em>still</em> need to do a <code>post.tags.reset</code> or <code>post.tags(true)</code> to reload the tags collection so you could see the new tags in the association.</p>
<p>Kudos to Jeremy for getting this to work. I'd been working on my own hack to achieve this, but couldn't come up with an approach that was clean enough to satisfy me. Jeremy did the expedient thing and cut corners, defining the semantics on failure to throw an exception instead of returning false. That differs from how <code>&lt;&lt;</code> works on <code>has_many</code>, which returns false on failure (and how does that make sense?). I'm lobbying to fix <code>has_many &lt;&lt;</code> to do the same thing now.</p>
<p>Obligatory word of caution: As with any new edge feature, this may be unstable for a short time. I've been helping Jeremy stomp some bugs and fill in holes, but it's looking pretty solid now. If you're curious, give it a shot.</p>
<p>Now, the big bonus. This new functionality lets you easily set extra attributes when creating join model records, just like the deprecated <code>push_with_attributes</code> did for <code>has_and_belongs_to_many</code>. And of course I've got a trick for customizing default values based on associations.</p>
<p>Read on for more details...</p>
<p><em>(By the way, I'm entering this post in Peter Cooper's Ruby/Rails blogging <a href="http://www.rubyinside.com/win-100-by-blogging-about-ruby-or-rails-this-week-184.html">contest</a>. There are some good entries already, and he's always got good links on his site, so check it out.)</em></p><p>Mark the date: August 18, 2006 is the day that has_many :through finally beat has_and_belongs_to_many into submission. Yesterday, Rails <a href="http://rubyonrails.org/core">core</a> member <a href="http://bitsweat.net/">Jeremy Kemper</a> checked in a <a href="http://dev.rubyonrails.org/changeset/4786">change</a> to Rails trunk ActiveRecord that allows using <code>&lt;&lt;</code> and friends on <code>has_many :through</code> associations. If you don't know why this is cool (or needed!), take a moment and read <a href="http://blog.hasmanythrough.com/articles/2006/04/17/join-models-not-proxy-collections">this</a> for background.</p>
<p>With this change, ActiveRecord will automagically create the join model record needed to link the associated object. What does that mean? Check this out...</p>
<pre><code>post.tags &lt;&lt; Tag.find_or_create_by_name("magic")
</code></pre>
<p>In the old world, that would have been</p>
<pre><code>post.taggings.create!(:tag =&gt; Tag.find_or_create_by_name("magic"))
</code></pre>
<p>And then you'd <em>still</em> need to do a <code>post.tags.reset</code> or <code>post.tags(true)</code> to reload the tags collection so you could see the new tags in the association.</p>
<p>Kudos to Jeremy for getting this to work. I'd been working on my own hack to achieve this, but couldn't come up with an approach that was clean enough to satisfy me. Jeremy did the expedient thing and cut corners, defining the semantics on failure to throw an exception instead of returning false. That differs from how <code>&lt;&lt;</code> works on <code>has_many</code>, which returns false on failure (and how does that make sense?). I'm lobbying to fix <code>has_many &lt;&lt;</code> to do the same thing now.</p>
<p>Obligatory word of caution: As with any new edge feature, this may be unstable for a short time. I've been helping Jeremy stomp some bugs and fill in holes, but it's looking pretty solid now. If you're curious, give it a shot.</p>
<p>Now, the big bonus. This new functionality lets you easily set extra attributes when creating join model records, just like the deprecated <code>push_with_attributes</code> did for <code>has_and_belongs_to_many</code>. And of course I've got a trick for customizing default values based on associations.</p>
<p>Read on for more details...</p>
<p><em>(By the way, I'm entering this post in Peter Cooper's Ruby/Rails blogging <a href="http://www.rubyinside.com/win-100-by-blogging-about-ruby-or-rails-this-week-184.html">contest</a>. There are some good entries already, and he's always got good links on his site, so check it out.)</em></p>
<p>First, the basics. Here's what's new:</p>
<ul>
<li>Add records to <code>has_many :through</code> associations using <code>&lt;&lt;</code>, <code>push</code>, and <code>concat</code> by creating the join record. Raises an error if base or associate are new records since both ids are required to create the join record.</li>
<li><code>#build</code> raises and error since you can't associate an unsaved record (one without an id).</li>
<li><code>#create!</code> takes an attributes hash and creates both the associated record and the join record in a transaction.</li>
</ul>
<p>And of course, I have some tricks for you as well. Here are some models you may have seen <a href="http://blog.hasmanythrough.com/articles/2006/05/06/through_gets_uniq">before</a>.</p>
<pre><code>create_table "contributions" do |t|
t.column "book_id", :integer
t.column "contributor_id", :integer
t.column "role", :string
end
class Contribution &lt; ActiveRecord::Base
belongs_to :book
belongs_to :contributor
end
class Book &lt; ActiveRecord::Base
has_many :contributions, :dependent =&gt; :destroy
has_many :contributors, :through =&gt; :contributions
end
class Contributor &lt; ActiveRecord::Base
has_many :contributions, :dependent =&gt; :destroy
has_many :books, :through =&gt; :contributions
end
</code></pre>
<p>Let me just spruce them up to take advantage of the new features.</p>
<pre><code>class Contributor &lt; ActiveRecord::Base
has_many :contributions, :dependent =&gt; :destroy
has_many :books, :through =&gt; :contributions do
def push_with_attributes(book, join_attrs)
Contribution.with_scope(:create =&gt; join_attrs) { self &lt;&lt; book }
end
end
end
</code></pre>
<p>Here I've created an association extension with the method <code>push_with_attributes</code>. That lets me do this:</p>
<pre><code>dave = Contributor.create(:name =&gt; "Dave")
awdr = Book.create(:title =&gt; "Agile Web Development with Rails")
dave.books.push_with_attributes(awdr, :role =&gt; "author")
</code></pre>
<p>That automagically creates the join record with the role attribute set to "author". Pretty nifty, eh? But we can do better.</p>
<pre><code>class Contribution &lt; ActiveRecord::Base
belongs_to :book
belongs_to :contributor
belongs_to :author, :class_name =&gt; "Contributor"
belongs_to :editor, :class_name =&gt; "Contributor"
end
class Book &lt; ActiveRecord::Base
has_many :contributions, :dependent =&gt; :destroy
has_many :contributors, :through =&gt; :contributions, :uniq =&gt; true
has_many :authors, :through =&gt; :contributions, :source =&gt; :author,
:conditions =&gt; "contributions.role = 'author'" do
def &lt;&lt;(author)
Contribution.with_scope(:create =&gt; {:role =&gt; "author"}) { self.concat author }
end
end
has_many :editors, :through =&gt; :contributions, :source =&gt; :editor,
:conditions =&gt; "contributions.role = 'editor'" do
def &lt;&lt;(editor)
Contribution.with_scope(:create =&gt; {:role =&gt; "editor"}) { self.concat editor }
end
end
end
</code></pre>
<p>Then give this a shot...</p>
<pre><code>dave = Contributor.create(:name =&gt; "Dave")
chad = Contributor.create(:name =&gt; "Chad")
awdr = Book.create(:title =&gt; "Agile Web Development with Rails")
awdr.authors &lt;&lt; dave
awdr.editors &lt;&lt; chad
</code></pre>
<p>The above code creates a join record with the role attribute set to "author", and another with the role set to "editor". The trick is using the association extension to define a new <code>&lt;&lt;</code> method. Since that method is defined in the context of a specialized association, it can assume correctly that it knows what the role should be. This example code isn't very DRY, but it shouldn't be much work to come up with a way to generate those extensions dryly.</p>
<p>I think I'm happy now.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/602006-08-13T09:49:57-07:002008-01-24T00:19:33-08:00How dynamic finders workJosh Susser<p>I got into a discussion on IRC last week talking about how ActiveRecord's magic <code>find_by_whatever</code> methods work. It's an interesting topic (at least to geeks like us) and probably worth an explanation.</p>
<p>As part of its magic, ActiveRecord gives you some convenience methods to find records by their attribute values. These methods are called <em>dynamic finders</em>. There are two forms, <code>find_by_whatever</code>, and <code>find_or_create_by_whatever</code> to create the record if you can't find it. (In edge Rails there is also <code>find_or_initialize_by_whatever</code> which instantiates the model but doesn't save it.) You can use one attribute or many. Here are some examples and their equivalents in standard finders.</p>
<pre><code>User.find(:first, :conditions =&gt; ["name = ?", name])
User.find_by_name(name)
User.find(:all, :conditions =&gt; ["city = ?", city])
User.find_all_by_city(city)
User.find(:all, :conditions =&gt; ["street = ? AND city IN (?)", street, cities])
User.find_all_by_street_and_city(street, cities)
</code></pre>
<p>Notice how much shorter and easier to read the dynamic finders are than the standard finders. Pretty nice! There are some limitations, the most obvious being that you can only test based on equality, so you couldn't find all users who signed up more than 30 days ago.</p>
<p>Read on for an under-the-covers look at how this magic works.</p><p>I got into a discussion on IRC last week talking about how ActiveRecord's magic <code>find_by_whatever</code> methods work. It's an interesting topic (at least to geeks like us) and probably worth an explanation.</p>
<p>As part of its magic, ActiveRecord gives you some convenience methods to find records by their attribute values. These methods are called <em>dynamic finders</em>. There are two forms, <code>find_by_whatever</code>, and <code>find_or_create_by_whatever</code> to create the record if you can't find it. (In edge Rails there is also <code>find_or_initialize_by_whatever</code> which instantiates the model but doesn't save it.) You can use one attribute or many. Here are some examples and their equivalents in standard finders.</p>
<pre><code>User.find(:first, :conditions =&gt; ["name = ?", name])
User.find_by_name(name)
User.find(:all, :conditions =&gt; ["city = ?", city])
User.find_all_by_city(city)
User.find(:all, :conditions =&gt; ["street = ? AND city IN (?)", street, cities])
User.find_all_by_street_and_city(street, cities)
</code></pre>
<p>Notice how much shorter and easier to read the dynamic finders are than the standard finders. Pretty nice! There are some limitations, the most obvious being that you can only test based on equality, so you couldn't find all users who signed up more than 30 days ago.</p>
<p>Read on for an under-the-covers look at how this magic works.</p>
<p>ActiveRecord implements dynamic finders using a method_missing trick. If you're not familiar with how <code>method_missing</code> works, you can find a good description in Chapter 13 of David Black's <em>Ruby for Rails</em>. In brief, <code>method_missing</code> lets you trap when you send an object a message that it doesn't have a method for, then do something useful instead of just choking. In this case, <code>method_missing</code> lets ActiveRecord objects pretend they have all these <code>find_by_whatever</code> methods that don't really exist in code anywhere.</p>
<p>If you want to follow along in the code, look at <a href="http://dev.rubyonrails.org/svn/rails/tags/rel_1-1-6/activerecord/lib/active_record/base.rb">ActiveRecord::Base</a>, line 1090 (or search for "method_missing"). I'm going to simplify things by glossing over some details that don't matter much to understanding the basic operation.</p>
<p>First off, lets look at the top-level structure of <code>method_missing</code>.</p>
<pre><code>def method_missing(method_id, *arguments)
if match = /find_(all_by|by)_([_a-zA-Z]\w*)/.match(method_id.to_s)
# find...
elsif match = /find_or_create_by_([_a-zA-Z]\w*)/.match(method_id.to_s)
# find_or_create...
else
super
end
end
</code></pre>
<p>If you're like me, you might be a little tempted to refactor the method to move the guts of those cases into their own methods just so <code>method_missing</code> can look as clean in the real sources as my abbreviated example. Are you, just a little? Anyway, <code>method_missing</code> is called with a <code>method_id</code> symbol that is the name of the missing method, and a splat list of the arguments that were passed to that method. If all is well, those arguments should be the values that will get bound to the query's conditions.</p>
<p>There are then just three cases for what can be done with the <code>method_id</code>. First, it might be a <code>find_by_whatever</code> or <code>find_all_by_whatever</code> method. Second, it might be a <code>find_or_create_by_whatever</code> method. If it doesn't match either of those, then it's not our problem so we do a <code>super</code> to pass handling of the missing method on up the superclass chain.</p>
<p>Drilling down to see what happens in the first (find only) case, we see this (cleaned up a bit):</p>
<pre><code>finder = determine_finder(match)
attribute_names = extract_attribute_names_from_match(match)
super unless all_attributes_exists?(attribute_names)
conditions = construct_conditions_from_arguments(attribute_names, arguments)
case extra_options = arguments[attribute_names.size]
when nil
options = { :conditions =&gt; conditions }
send(finder, options)
when Hash
# deal with extra options
else
# deprecated extra options API
end
</code></pre>
<p>First, we determine whether we're dealing with a method name starting with <code>find_by</code> or a <code>find_all_by</code>. The "finder" method is an internal method of <code>ActiveRecord::Base</code> that doesn't appear in the public API. For <code>find_by</code> it is <code>find_initial</code>, which corresponds to sending <code>find(:first)</code> to the model class. For <code>find_all_by</code> it is <code>find_every</code>, which likewise corresponds to <code>find(:all)</code>.</p>
<p>Next, we use <code>extract_attribute_names_from_match</code> to parse the rest of the missing method name and extract the names of the attributes we want to search on. <code>split('_and_')</code> does what we want since attributes are delimited by "_and_". If <code>unless all_attributes_exists?</code> tells us that any of the attribute names don't match actual attributes of the model, we have to punt and use a <code>super</code> to make the missing method someone else's problem.</p>
<p>Once we know what attributes we're dealing with, we need to get the values we'll use to search for matching records. Say we are handling <code>User.find_by_name_and_city("Josh", "San Francisco")</code>. By this point we have "name" and "city" as the attribute names, so the next step is to generate <code>:conditions =&gt; ["name = 'Josh' AND city = 'San Francisco'"]</code>. The <code>construct_conditions_from_arguments</code> method creates the conditions string for us. It provides for three types of equality tests depending on the type of argument: <code>IS</code> for nil values, <code>=</code> for singular non-nil values, and <code>IN</code> for lists of values.</p>
<p>Looking back at the code for the find case we can see we're almost done. The next thing is to deal with any options in the call. Dynamic finders allow the usual find options. For example, you can sort the results using an <code>:order</code> option:</p>
<pre><code>User.find_all_by_city("San Francisco", :order =&gt; "name")
</code></pre>
<p>I'm going to skip over the options processing since it's not all that interesting. You can explore that code on your own if you want the gory details. So, ignoring extra options, all we have left to do is to call the finder method with the generated conditions string as the only option.</p>
<pre><code>send(finder, options)
</code></pre>
<p>For <code>find_all_by_street_and_city("Lombard", "San Francisco")</code>, that is equivalent to</p>
<pre><code>find_every(:conditions =&gt; "street = 'Lombard' AND city = 'San Francisco'")
</code></pre>
<p>That's it for the basic find case. Now we can look at <code>find_or_create</code>.</p>
<pre><code>attribute_names = extract_attribute_names_from_match(match)
super unless all_attributes_exists?(attribute_names)
options = { :conditions =&gt; construct_conditions_from_arguments(attribute_names, arguments) }
find_initial(options) || create(construct_attributes_from_arguments(attribute_names, arguments))
</code></pre>
<p>The <code>find_or_create</code> case looks much shorter than the find case. That might seem a bit odd, since it isn't just finding, it also has to create missing records. However, the find case had to support both "find one" and "find all", and also had to deal with potential extra options. But <code>find_or_create</code> doesn't support the "find all" case, and so needn't support any of the extra options that are used to process lists of multiple results.</p>
<p>This is an important point worth mentioning, as it isn't mentioned in the API documentation. <code>find_or_create</code> dynamic finders don't support options as <code>find_by</code> and <code>find_all_by</code> do. I suppose it's time for a doc patch.</p>
<p>Anyway, back to the code. We've seen the first part before (which suggests a potential refactoring). It's just like the code in the simple find case, and extracts the attribute names from the method name.</p>
<p>The rest looks somewhat similar to the find case code. We generate the conditions for the search query based on the attribute names and argument values. Note that we don't need to use <code>send</code> to use the finder method, since it will always be <code>find_initial</code> (remember, "find all" is not supported). The final bit is to notice if the search query returned nil, and if so create a new record using the supplied arguments. That is done by creating a hash that maps attribute names to argument values in order. The code for that is the clearest explanation:</p>
<pre><code>attribute_names.each_with_index { |name, idx| attributes[name] = arguments[idx] }
</code></pre>
<p>The found record is returned, or if there was none found, the created record is returned.</p>
<p>And that's all there is.</p>
<h3>Limitations</h3>
<p>It's worth noting a couple limitations of dynamic finders.</p>
<p>Dynamic finders work only with model attributes, not with associations. So if you have a <code>belongs_to :user</code> association on an article, you have to write <code>Article.find_by_user_id(user.id)</code>. I have an <a href="http://blog.hasmanythrough.com/articles/2006/07/02/patch-test-plugin-dynamic-finders-with-belongs_to-names">experimental patch</a> that provides this functionality. I'm not entirely sure what to do with the patch. It works fine for simple belongs_to associations, but doesn't yet handle polymorphism. I'm looking into supporting polymorphic belongs_to associations, but the code is getting ugly enough that I'm not sure it's worth it. Feedback would be appreciated.</p>
<p>One more thing to note is that <code>find_or_create</code> has yet another limitation. While you can pass an array of values to <code>find_all_by</code> finders, you can't do so for <code>find_or_create</code>. It might not make sense to pass arrays to <code>find_by</code> (as opposed to <code>find_all_by</code>) either, but I can see both sides of the issue so I'll let it be for now.</p>
<h3>Last words</h3>
<p>I hope this spelunking expedition through the dynamic finder code has been useful. I find that I appreciate the magic of Rails even more when I understand how it works, and I also think I can make better decisions about when to rely on the magic and when to roll my own solution. This may also give you some ideas of how to use <code>method_missing</code> in your own code, though I'll caution against profligate use.</p>
<p>If you like dynamic finders, you should also take a look at Ezra Zygmuntowicz and Fabien Franzen's <a href="http://agilewebdevelopment.com/plugins/ez_where">ez_where plugin</a>, which lets you build queries with Ruby operators and also supports more than simple equality conditions.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/552006-07-07T11:54:00-07:002008-01-24T00:19:32-08:00more on naming and the CRUDJosh Susser<p>Sebastian Delmont blogged recently about a <a href="http://www.notsostupid.com/blog/2006/07/07/urls-on-rails/">flexible approach</a> to handling permalinks that is an interesting compromise between numeric IDs and semantically rich textual slugs. Coincidentally, Aristotle Pagaltzis <a href="http://plasmasturm.org/log/358/">described the same thing</a> last December.</p>
<p>To summarize: Combine the ID and the textual slug into a URL. Your app will resolve the URL based only on the ID part. The text is there to make users comfortable and the SEO-obsessed happy. So <code>/users/13</code>, <code>/users/13-josh</code>, and <code>/users/13-joshua</code> would resolve to the same entity. Sebastian claims this happens in the database, but I'll note that <code>"13-josh".to_i</code> returns the integer 13 so your Ruby code will be happy too.</p>
<p>I think that combining this naming scheme with a 301 permanent redirect to update stale names sounds fairly workable. You still have the problem of your del.ico.us bookmarks not being equated, but if this technique becomes common del.ico.us could notice the 301s and merge rankings of different URLs for the same thing. Maybe they even do that already - I don't use any bookmarking sites so I wouldn't know. I'm assuming the overhead for the slug comparison and redirect won't be an excessive burden to an app.</p>
<p>On another note, Dan Peterson put together a nice <a href="http://svn.dpiddy.net/plugins/crud_generator/">CRUD scaffolding generator</a>. It creates controllers with the 7 CRUD actions (index, new, create, show, edit, update, destroy) and compatible view templates. If you're looking to get started with CRUD, using <a href="http://dev.rubyonrails.org/svn/rails/plugins/simply_restful/">simply_restful</a> with Dan's generator is a good way to go. Unfortunately there is barely any documentation on how to structure RESTful controllers. Note the opportunity for someone who wants to write some much needed docs!</p><p>Sebastian Delmont blogged recently about a <a href="http://www.notsostupid.com/blog/2006/07/07/urls-on-rails/">flexible approach</a> to handling permalinks that is an interesting compromise between numeric IDs and semantically rich textual slugs. Coincidentally, Aristotle Pagaltzis <a href="http://plasmasturm.org/log/358/">described the same thing</a> last December.</p>
<p>To summarize: Combine the ID and the textual slug into a URL. Your app will resolve the URL based only on the ID part. The text is there to make users comfortable and the SEO-obsessed happy. So <code>/users/13</code>, <code>/users/13-josh</code>, and <code>/users/13-joshua</code> would resolve to the same entity. Sebastian claims this happens in the database, but I'll note that <code>"13-josh".to_i</code> returns the integer 13 so your Ruby code will be happy too.</p>
<p>I think that combining this naming scheme with a 301 permanent redirect to update stale names sounds fairly workable. You still have the problem of your del.ico.us bookmarks not being equated, but if this technique becomes common del.ico.us could notice the 301s and merge rankings of different URLs for the same thing. Maybe they even do that already - I don't use any bookmarking sites so I wouldn't know. I'm assuming the overhead for the slug comparison and redirect won't be an excessive burden to an app.</p>
<p>On another note, Dan Peterson put together a nice <a href="http://svn.dpiddy.net/plugins/crud_generator/">CRUD scaffolding generator</a>. It creates controllers with the 7 CRUD actions (index, new, create, show, edit, update, destroy) and compatible view templates. If you're looking to get started with CRUD, using <a href="http://dev.rubyonrails.org/svn/rails/plugins/simply_restful/">simply_restful</a> with Dan's generator is a good way to go. Unfortunately there is barely any documentation on how to structure RESTful controllers. Note the opportunity for someone who wants to write some much needed docs!</p>tag:blog.hasmanythrough.com,2006-02-27:Article/542006-07-02T23:07:00-07:002008-01-24T00:19:32-08:00Patch test plugin: dynamic finders with belongs_to namesJosh Susser<p>Dynamic finders are pretty neat, but I've grown tired of having to think in terms of foreign keys instead of the names of belongs_to associations. So I fixed it.</p>
<p>This patch enhances dynamic finders to allow use of a belongs_to association name instead of having to use the foreign key attribute directly. For example, these two lines are equivalent:</p>
<pre><code>posts = Post.find_all_by_author_id(author.id)
posts = Post.find_all_by_author(author)
</code></pre>
<p>Of course, that makes more of a difference when the association name and foreign key name differ more. But I think that makes dynamic finders a bit more transparent and easier to use.</p>
<p>I'll be submitting this as a patch fairly soon, but first I wanted to toss it out into the wild to see how well it works. I've written unit tests, but for something like this I'd feel just slightly better knowing a few people have given it a try first. So I've packaged the patch as a plugin to make it easy for you to try it out.</p>
<p><a href="http://svn.hasmanythrough.com/public/plugins/dynamic_finders_with_belongs_to_names/">dynamic_finders_with_belongs_to_names</a></p>
<p>Give it a shot, and either email me or leave a comment here to let me know if it works or if it breaks something.</p>
<p><strong>UPDATE:</strong> I've fixed the plugin so that the monkey patch loads properly now. If it wasn't working for you before, update to the new version and try it again now. Thanks!</p><p>Dynamic finders are pretty neat, but I've grown tired of having to think in terms of foreign keys instead of the names of belongs_to associations. So I fixed it.</p>
<p>This patch enhances dynamic finders to allow use of a belongs_to association name instead of having to use the foreign key attribute directly. For example, these two lines are equivalent:</p>
<pre><code>posts = Post.find_all_by_author_id(author.id)
posts = Post.find_all_by_author(author)
</code></pre>
<p>Of course, that makes more of a difference when the association name and foreign key name differ more. But I think that makes dynamic finders a bit more transparent and easier to use.</p>
<p>I'll be submitting this as a patch fairly soon, but first I wanted to toss it out into the wild to see how well it works. I've written unit tests, but for something like this I'd feel just slightly better knowing a few people have given it a try first. So I've packaged the patch as a plugin to make it easy for you to try it out.</p>
<p><a href="http://svn.hasmanythrough.com/public/plugins/dynamic_finders_with_belongs_to_names/">dynamic_finders_with_belongs_to_names</a></p>
<p>Give it a shot, and either email me or leave a comment here to let me know if it works or if it breaks something.</p>
<p><strong>UPDATE:</strong> I've fixed the plugin so that the monkey patch loads properly now. If it wasn't working for you before, update to the new version and try it again now. Thanks!</p>tag:blog.hasmanythrough.com,2006-02-27:Article/532006-07-01T10:05:00-07:002008-01-24T00:19:32-08:00validates_numericality_of :resource_nameJosh Susser<p>And now, more discussion of DHH's World of Resources ideas...</p>
<p>From what I saw at RailsConf, the reaction to the unveiling of ActiveResource was quite mixed. The two big concerns were, "is CRUD a good fit for my model/controller design?" and "should I use human-readable names for resources, or numeric IDs?". I've seen a fair bit of discussion on various blogs about the former, but none at all on the latter.</p>
<p>Some context: In his by now <a href="http://www.google.com/search?q=dhh+keynote+railsconf">infamous</a> <a href="http://www.loudthinking.com/arc/000593.html">keynote</a>, DHH said that simple numeric IDs were the best way to name resources in URLs. This is analogous to ActiveRecord's standard of using auto-increment integers for primary keys instead of whatever natural key the data model may imply. For example:</p>
<pre><code>natural: /employees/josh
numeric: /employees/13
</code></pre>
<p>DHH made the point that naming resources using what is effectively a portion of their contents is not a reliably permanent name. The city of Sunset, Florida changed its name to <a href="http://en.wikipedia.org/wiki/Sunrise,_Florida">Sunrise</a> (not surprising, given the local demographics) - should that make all URLs to "/cities/Sunset" invalid?</p><p>And now, more discussion of DHH's World of Resources ideas...</p>
<p>From what I saw at RailsConf, the reaction to the unveiling of ActiveResource was quite mixed. The two big concerns were, "is CRUD a good fit for my model/controller design?" and "should I use human-readable names for resources, or numeric IDs?". I've seen a fair bit of discussion on various blogs about the former, but none at all on the latter.</p>
<p>Some context: In his by now <a href="http://www.google.com/search?q=dhh+keynote+railsconf">infamous</a> <a href="http://www.loudthinking.com/arc/000593.html">keynote</a>, DHH said that simple numeric IDs were the best way to name resources in URLs. This is analogous to ActiveRecord's standard of using auto-increment integers for primary keys instead of whatever natural key the data model may imply. For example:</p>
<pre><code>natural: /employees/josh
numeric: /employees/13
</code></pre>
<p>DHH made the point that naming resources using what is effectively a portion of their contents is not a reliably permanent name. The city of Sunset, Florida changed its name to <a href="http://en.wikipedia.org/wiki/Sunrise,_Florida">Sunrise</a> (not surprising, given the local demographics) - should that make all URLs to "/cities/Sunset" invalid?</p>
<p>First off, let me say that if resource URLs are meant for consumption by another program via a RESTful web service, than having human-readable URLs is a non-issue. Machines are totally fine with numbers. However, low-level naming schemes have a tendency to bubble up to where the user can see them. Admit it: you've written at least one app in Rails that used primary keys in the URLs. Don't your users just love seeing their profiles displayed with the friendly URL "/users/show/13"?</p>
<p>I keep running into this issue myself, not only in software design, but even in how I choose permalink names for articles in this blog. It's nice to have a human-readable URL to paste into an email, as it gives a reader an idea of what the referenced article is about, and might make it easier to find a link in a list of bookmarks. But I also worry about the longevity of links, and managing identity equivalence. Bookmark services like del.ico.us don't equate two different URLs to the same page, so <a href="http://blog.hasmanythrough.com/articles/2006/06/30/cruddy-searches">http://blog.hasmanythrough.com/articles/2006/06/30/cruddy-searches</a> and <a href="http://blog.hasmanythrough.com/articles/read/375">http://blog.hasmanythrough.com/articles/read/375</a> will be counted separately. This may not seem a big deal, but it not only affects the popularity ranking of a page, but also messes with finding areas of overlapping interest with other users. I wonder if the folks at bookmarking services are thinking about this issue; I assume Google is.</p>
<p>So there seem to be three issues to consider for URL names of resources:</p>
<ol>
<li>Readability by humans</li>
<li>Longevity (permanence)</li>
<li>Identity comparisons</li>
</ol>
<p>I talked this issue over with a bunch of people at RailsConf, and the tentative consensus that emerged was this: Use numeric IDs for resource permalinks. Allow human-readable (or actually, human-writable) URLs as an alias or heuristic search for an entity, but do a permanent redirect (301) to the numeric permalink once the entity was found.</p>
<p>Looks good on paper, but I want to do some usability testing to see how it flies.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/522006-06-30T19:58:00-07:002008-01-24T00:19:32-08:00CRUDdy searchesJosh Susser<p>An open question with the World of Resources paradigm is how to do searches. <code>GET /people</code> returns all people, <code>GET /people/23</code> gets one person... but how do you get something in between? What we need is the equivalent of <code>:conditions</code> on an ActiveRecord find.</p>
<p>One approach is to use a GET request with conditions on the URL. So to find all males older than 25 years of age:</p>
<pre><code>GET /people?gender=male&amp;age=gt,25
</code></pre>
<p>DHH said this is a good way to go, but I wonder. One, this approach will eventually run into the URL length limit. But more importantly, it's not using the CRUD approach of reifying abstractions.</p>
<p>Instead, consider using a Search model. Create a search resource that holds all the options for the query. Then you can execute it whenever you want. <code>POST /searches</code> (post data contains query options) would redirect to <code>GET /searches/42</code> or <code>GET /people/?searches=42</code>. You could even make it so that searches are unbound until execution, like blocks: <code>GET /searches/42?on=people</code>.</p>
<p>I need to try this out and see how it works, but I won't have a chance to do that for a few days.</p>
<p>Thoughts?</p><p>An open question with the World of Resources paradigm is how to do searches. <code>GET /people</code> returns all people, <code>GET /people/23</code> gets one person... but how do you get something in between? What we need is the equivalent of <code>:conditions</code> on an ActiveRecord find.</p>
<p>One approach is to use a GET request with conditions on the URL. So to find all males older than 25 years of age:</p>
<pre><code>GET /people?gender=male&amp;age=gt,25
</code></pre>
<p>DHH said this is a good way to go, but I wonder. One, this approach will eventually run into the URL length limit. But more importantly, it's not using the CRUD approach of reifying abstractions.</p>
<p>Instead, consider using a Search model. Create a search resource that holds all the options for the query. Then you can execute it whenever you want. <code>POST /searches</code> (post data contains query options) would redirect to <code>GET /searches/42</code> or <code>GET /people/?searches=42</code>. You could even make it so that searches are unbound until execution, like blocks: <code>GET /searches/42?on=people</code>.</p>
<p>I need to try this out and see how it works, but I won't have a chance to do that for a few days.</p>
<p>Thoughts?</p>tag:blog.hasmanythrough.com,2006-02-27:Article/512006-06-30T14:46:00-07:002008-01-24T00:19:32-08:00Working with the relationship modelJosh Susser<p>There's been a lot of buzz this week about <a href="http://www.loudthinking.com/arc/000593.html">DHH's keynote at RailsConf</a> last weekend. I've been meaning to write something but, but my laziness has triumphed and a few others have already written up pretty good summaries of the state of dealing with <a href="http://www.loudthinking.com/lt-files/worldofresources.pdf">A World of Resources</a>. In particular, Ryan Daigle has a nice <a href="http://www.ryandaigle.com/articles/2006/06/30/whats-new-in-edge-rails-activeresource-is-here">summary</a> that covers all the basics, and Jim Greer's <a href="http://jimonwebgames.com/articles/2006/06/26/dont-say-crud-say-fucd">post</a> is a must-read too.</p>
<p>I like the direction of CRUDdying up relationships. I've said for a while that reifying relationships is a win, as any time you can turn a concept into an object it gives you more leverage to work with it. The nice thing about DHH's approach is that is not only turns abstract relationships into concrete objects, but it also uses polymorphism to establish standard CRUD behavior for all those objects.</p>
<p>But embracing CRUD takes a shift in perspective. Actions become nouns that are tossed at other nouns. Others have already observed that this is like the Command pattern. There's a lot of richness to that pattern that can be played with, such as rewinding and replaying commands.</p><p>There's been a lot of buzz this week about <a href="http://www.loudthinking.com/arc/000593.html">DHH's keynote at RailsConf</a> last weekend. I've been meaning to write something but, but my laziness has triumphed and a few others have already written up pretty good summaries of the state of dealing with <a href="http://www.loudthinking.com/lt-files/worldofresources.pdf">A World of Resources</a>. In particular, Ryan Daigle has a nice <a href="http://www.ryandaigle.com/articles/2006/06/30/whats-new-in-edge-rails-activeresource-is-here">summary</a> that covers all the basics, and Jim Greer's <a href="http://jimonwebgames.com/articles/2006/06/26/dont-say-crud-say-fucd">post</a> is a must-read too.</p>
<p>I like the direction of CRUDdying up relationships. I've said for a while that reifying relationships is a win, as any time you can turn a concept into an object it gives you more leverage to work with it. The nice thing about DHH's approach is that is not only turns abstract relationships into concrete objects, but it also uses polymorphism to establish standard CRUD behavior for all those objects.</p>
<p>But embracing CRUD takes a shift in perspective. Actions become nouns that are tossed at other nouns. Others have already observed that this is like the Command pattern. There's a lot of richness to that pattern that can be played with, such as rewinding and replaying commands.</p>
<p>However, the other perspective shift is how you work with the relationship model objects. Here's an example from a conversation on an email list that took place earlier today.</p>
<pre><code>class User &lt; ActiveRecord::Base
has_many :registrations
has_many :products, :through =&gt; :registrations, :uniq =&gt; true
end
class Product &lt; ActiveRecord::Base
has_many :registrations
has_many :users, :through =&gt; :registrations, :uniq =&gt; true
end
class Registration &lt; ActiveRecord::Base
belongs_to :user
belongs_to :product
end
</code></pre>
<p>Then a view template to show all the registations for a user's products.</p>
<pre><code>&lt;% @user_products.each do |product| %&gt;
&lt;tr&gt;
&lt;td&gt;&lt;%= product.name %&gt;&lt;/td&gt;
&lt;td&gt;&lt;%= product.description %&gt;&lt;/td&gt;
&lt;td width="250"&gt;
&lt;% product.registrations.each do |reg| %&gt;
&lt;% reg.serial_number %&gt; &lt;br /&gt;
&lt;% end %&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;% end %&gt;
</code></pre>
<p>However, that will show all the registrations for a product, not just those for the user in question. The goal is to show all the registrations for all the products of that type that a user has registered. Here are some ways to get that:</p>
<pre><code>regs = Registration.find(:all, :conditions =&gt; ["user_id = ? AND product_id = ?", user, product])
regs = Registration.find_all_by_user_id_and_product_id(user.id, product.id)
regs = product.registrations.find(:all, ["user_id = ?", user])
regs = product.registrations.find_all_by_user_id(user.id)
</code></pre>
<p>I like the last of these best, but it's still a bit icky having to pass the id explicitly. We can clean that up and make it more idiomatic by using an extension:</p>
<pre><code>class Product &lt; ActiveRecord::Base
has_many :registrations do
def find_all_by_user(user)
find_all_by_user_id(user.id)
end
end
has_many :users, :through =&gt; :registrations, :uniq =&gt; true
end
</code></pre>
<p>Then we get to fix the view thusly:</p>
<pre><code>&lt;% @user_products.each do |product| %&gt;
&lt;tr&gt;
&lt;td&gt;&lt;%= product.name %&gt;&lt;/td&gt;
&lt;td&gt;&lt;%= product.description %&gt;&lt;/td&gt;
&lt;td width="250"&gt;
&lt;% product.registrations.find_all_by_user(@user).each do |reg| %&gt;
&lt;% reg.serial_number %&gt; &lt;br /&gt;
&lt;% end %&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;% end %&gt;
</code></pre>
<p>It may seem like a minor point, but it's an important one.</p>
<p>Now I think I need to look into dynamic finders and see if I can make them work with belongs_to associations too.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/442006-06-12T15:40:00-07:002008-01-24T00:19:31-08:00When associations aren't enough, part 2Josh Susser<p>After I posted <a href="http://blog.hasmanythrough.com/articles/2006/06/12/when-associations-arent-enough">part 1</a> this morning, the inimitable <a href="http://www.rubyinside.com/">Peter Cooper</a> left a comment of a very different way to get the same results. I'd been thinking about using my approach to do tagging, and wasn't too happy with it. Doing two or three joins is one thing, but doing a dozen or so is another matter and likely to make a database cranky. And with a spiffy tagging UI, you can expect users to be filtering by a bunch of tags. Looks like my many-joins approach won't scale well.</p><p>After I posted <a href="http://blog.hasmanythrough.com/articles/2006/06/12/when-associations-arent-enough">part 1</a> this morning, the inimitable <a href="http://www.rubyinside.com/">Peter Cooper</a> left a comment of a very different way to get the same results. I'd been thinking about using my approach to do tagging, and wasn't too happy with it. Doing two or three joins is one thing, but doing a dozen or so is another matter and likely to make a database cranky. And with a spiffy tagging UI, you can expect users to be filtering by a bunch of tags. Looks like my many-joins approach won't scale well.</p>
<p>Peter's approach uses grouping and then counting the number of matches. Pretty slick trick. Here's how the (ANSIfied) SQL for his approach looks:</p>
<pre><code>SELECT * FROM movies
INNER JOIN appearances a ON movies.id = a.movie_id
WHERE (a.actor_id IN (17, 23, 39, 42))
GROUP BY movies.id HAVING COUNT(movies.id) = 4
</code></pre>
<p>The join finds all the appearances for any actor in a movie. The WHERE filters that down to just the actors we care about, so our result set is now all the movies any of the Marx brothers appeared in. Then we group all the movies by their id, and count how many of our specified actors appeared in each movie. Only the movies that have the right number of actors are selected.</p>
<p>Here's the method for generating that query:</p>
<pre><code>def self.find_with_all_actors(*actors)
return [] if actors.empty?
actors = actors.flatten
find(:all, :readonly =&gt; false,
:joins =&gt; "INNER JOIN appearances a ON movies.id = a.movie_id",
:conditions =&gt; "a.actor_id IN (#{actors.map(&amp;:id).join(', ')})",
:group =&gt; "movies.id HAVING COUNT(movies.id) = #{actors.size}")
end
</code></pre>
<p>Note that if you're running edge, you can use this for the conditions: </p>
<pre><code> :conditions =&gt; ["a.actor_id IN (?)", actors],
</code></pre>
<p>I haven't had time to do any sort of performance comparison against the two approaches, but I'm pretty sure that for more than a few actors, Peter's approach will usually win. For a small number of actors, it may depend on a lot of factors.</p>
<p>The other nice thing Peter points out is that his approach lets you match <em>most</em> of the actors. If you change the HAVING to <code>COUNT(movies.id) &gt; 1</code>, you'll find all the movies where at least two of the brothers appear together, not not necessarily all four. Or you can order them by number of actors appearing, etc.</p>
<p>Very nice. I'll probably use this technique for a tagging system I'm about to implement.</p>
<p>By the way, <a href="http://ruby.meetup.com/6/events/4929994/">SF Ruby Meetup</a> tonight. And just 10 days to RailsConf!</p>tag:blog.hasmanythrough.com,2006-02-27:Article/432006-06-12T09:55:00-07:002008-01-24T00:19:31-08:00When associations aren't enoughJosh Susser<p>ActiveRecord associations are the neatest thing since sliced bread. They are great for simplifying a common set of relational database access patterns, something even sliced bread can't do. ActiveRecord associations provide a lot of flexibility, but there comes a point there they run out of steam and you have to roll up your sleeves and write some custom SQL.</p>
<p>Last week I read a <a href="http://www.ruby-forum.com/topic/68432">question</a> on the rails email list that had, I think, a rather interesting solution. I think it's a good example of how to use custom SQL without throwing all of ActiveRecord out the window and resorting to <code>find_by_sql</code>. I'm going to answer that question again here because it deserves more attention than I was able to give it in just a quick email. I also wanted to test my answer and make sure everything works correctly (which it does now).</p><p>ActiveRecord associations are the neatest thing since sliced bread. They are great for simplifying a common set of relational database access patterns, something even sliced bread can't do. ActiveRecord associations provide a lot of flexibility, but there comes a point there they run out of steam and you have to roll up your sleeves and write some custom SQL.</p>
<p>Last week I read a <a href="http://www.ruby-forum.com/topic/68432">question</a> on the rails email list that had, I think, a rather interesting solution. I think it's a good example of how to use custom SQL without throwing all of ActiveRecord out the window and resorting to <code>find_by_sql</code>. I'm going to answer that question again here because it deserves more attention than I was able to give it in just a quick email. I also wanted to test my answer and make sure everything works correctly (which it does now).</p>
<p><a href="http://www.imdb.com/">IMDb</a> is one of my best friends on the internet. One of its nifty features is the ability to find all the movies in which several actors appear together. How would we go about doing something like that using ActiveRecord associations? Actors appearing in movies is quite similar to the example of dancers in movies that I used in ye olde <a href="http://blog.hasmanythrough.com/articles/2006/04/20/many-to-many-dance-off">Many-to-many Dance-off</a>, so we can start with pretty much the same tables I used there, just turning dancers into actors (like that would be the first time that ever happened).</p>
<p>With either of <code>has_and_belongs_to_many</code> or <code>has_many :through</code> you can find all the movies for a given actor simply by using the magic accessor <code>actor.movies</code>, and all the actors for a movie with <code>movie.actors</code>. But there's no simple way to use the standard association machinery to find the set of movies in which several actors appear. The naive approach would be to use a condition that compares the <code>actor_id</code> against the ids of several actors, but that just won't work.</p>
<pre><code>movies = Movie.find(:all, :conditions =&gt; "actor_id = 17 AND actor_id = 23")
</code></pre>
<p>First off, there is no <code>actor_id</code> in the Movie model - it's in the join table! (Or in the join model's table if using <code>has_many :through</code>.) So if we mess around and get the join table into the SQL, we'll just be looking for rows in the join table where the <code>actor_id</code> is both 17 and 23 at the same time, and that only happens in Bizarro world. What we need to do is to join with the join table <em>multiple times</em>. If you know SQL well then this won't be at all impressive. But if you're a SQL novice it might seem like some kind of black magic. Don't worry, it's not that difficult.</p>
<p>Say we want to find all the Marx Brothers' movies.</p>
<pre><code>movies = Movie.find_with_all_actors([groucho, harpo, chico, zeppo])
</code></pre>
<p>Time to write some custom SQL. We can build a query using SQL's ability to join to the same table more than once in a single query. You have to alias the table to a different name for each join, but other than that the rest is straightforward.</p>
<pre><code>SELECT * FROM movies
INNER JOIN appearances ap0 ON movies.id = ap0.movie_id
INNER JOIN appearances ap1 ON movies.id = ap1.movie_id
INNER JOIN appearances ap2 ON movies.id = ap2.movie_id
INNER JOIN appearances ap3 ON movies.id = ap3.movie_id
WHERE (ap0.actor_id = 17 AND ap1.actor_id = 23 AND
ap2.actor_id = 39 AND ap3.actor_id = 42)
</code></pre>
<p>I'm using the join model table <code>appearances</code> in that SQL, but for a habtm join table you could just change that to <code>actors_movies</code> and it would work the same. No need to join to the actors table because we aren't searching on any of those attributes, just the <code>actor_id</code> in the join model. If we run that query we'll get back all the movies that all four of the Marx Brothers appeared in. Duck soup!</p>
<p>Let's write the method to generate that SQL. But wait, we're going to take a slight detour and before we write the method and write some simple tests for it. We'd like the method to work correctly whether we give it no arguments, a single actor, an array of actors, or just a bunch of actors not in an array.</p>
<pre><code>assert_equal [], Movie.find_with_all_actors()
assert_equal 12, Movie.find_with_all_actors(groucho).length
assert_equal 7, Movie.find_with_all_actors([groucho, harpo, chico, zeppo]).length
assert_equal 7, Movie.find_with_all_actors(groucho, harpo, chico, zeppo).length
</code></pre>
<p>Alright, <em>now</em> let's write the method to generate that SQL. Since we're looking for movies, we'll create a class method in the Movie model:</p>
<pre><code>def self.find_with_all_actors(*actors)
return [] if actors.empty?
join_frags, condition_frags = [], []
actors.flatten.each_with_index do |actor,i|
join_frags &lt;&lt; "INNER JOIN appearances ap#{i} ON movies.id = ap#{i}.movie_id"
condition_frags &lt;&lt; "ap#{i}.actor_id = #{actor.id}"
end
find(:all, :readonly =&gt; false,
:joins =&gt; join_frags.join(" "),
:conditions =&gt; condition_frags.join(" AND "))
end
</code></pre>
<p>That method isn't all that complex, but I'll break it down and walk through it so it's clear. Note that if <code>actors</code> is empty we just return an empty Array. After that we create some arrays to collect the SQL fragments in as they are generated.</p>
<pre><code> return [] if actors.empty?
join_frags, condition_frags = [], []
</code></pre>
<p>Then comes the meat of the method. We enumerate the actors and create both the JOIN clause and WHERE condition we'll need for that actor. Each join aliases the <code>appearances</code> table to a unique name that is used only for that join to the table.</p>
<pre><code> actors.flatten.each_with_index do |actor,i|
join_frags &lt;&lt; "INNER JOIN appearances ap#{i} ON movies.id = ap#{i}.movie_id"
condition_frags &lt;&lt; "ap#{i}.actor_id = #{actor.id}"
end
</code></pre>
<p>Then we put the fragments together into the JOIN and WHERE clauses and do the find.</p>
<pre><code> find(:all, :readonly =&gt; false,
:joins =&gt; join_frags.join(" "),
:conditions =&gt; condition_frags.join(" AND "))
</code></pre>
<p>We set <code>:readonly</code> to false to override the default behavior where ActiveRecord assumes that joined results can't be written back to a single table. Since we're selecting only the columns from movies, our records are writable.</p>
<p>As I said, we're not joining with the <code>actors</code> table. In this contrived example there aren't a lot of reasons to do that. If you want to find all the movies starring Kevin Bacon and any one of the Baldwins, you might be tempted to write a join against the actors table to search on the last name "Baldwin". However it's simpler to use one query to fetch all the Baldwins then use the ids of those records to create a query that uses OR and AND to find your movies. It takes two queries instead of just one, but odds are that's not going to be a significant performance hit. If it is, then you can sweat out the nasty join.</p>
<p><strong>UPDATE:</strong> The story continues in <a href="http://blog.hasmanythrough.com/articles/2006/06/12/when-associations-arent-enough-part-2">part 2</a>.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/402006-05-28T21:44:00-07:002008-01-24T00:19:31-08:00Come on, Eileen!Josh Susser<p>Unlike Dexy's Midnight Runners, I'm no longer a one-hit-wonder. Contributor, that is. Today I got two more patches accepted to Rails (thanks Rick!). One was just to lay groundwork for my experimental ThroughExtender module that will enable some of the association collection methods (<code>&lt;&lt;</code>, <code>build</code>, <code>create</code>) for some subset of <code>has_many :through</code> associations - more on this in another post, and fairly soon I hope.</p>
<p>The more timely change was a fix to habtm <code>create</code> that corrects a problem where the join table wasn't being populated for newly created associated objects (<a href="http://dev.rubyonrails.org/ticket/3692">#3692</a>). As so often seems to be the case, the fix was fairly easy (almost a one-liner), but getting the test cases to prove things were working right was the harder part. And just to be a good citizen, I created a plugin that will make the exact same fix for habtm <code>create</code> available to those who don't run off of trunk, to tide you over until the next release version. You can get it from my <a href="http://svn.hasmanythrough.com/public/plugins/habtm_create/">public svn repository</a>.</p><p>Unlike Dexy's Midnight Runners, I'm no longer a one-hit-wonder. Contributor, that is. Today I got two more patches accepted to Rails (thanks Rick!). One was just to lay groundwork for my experimental ThroughExtender module that will enable some of the association collection methods (<code>&lt;&lt;</code>, <code>build</code>, <code>create</code>) for some subset of <code>has_many :through</code> associations - more on this in another post, and fairly soon I hope.</p>
<p>The more timely change was a fix to habtm <code>create</code> that corrects a problem where the join table wasn't being populated for newly created associated objects (<a href="http://dev.rubyonrails.org/ticket/3692">#3692</a>). As so often seems to be the case, the fix was fairly easy (almost a one-liner), but getting the test cases to prove things were working right was the harder part. And just to be a good citizen, I created a plugin that will make the exact same fix for habtm <code>create</code> available to those who don't run off of trunk, to tide you over until the next release version. You can get it from my <a href="http://svn.hasmanythrough.com/public/plugins/habtm_create/">public svn repository</a>.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/382006-05-17T10:46:00-07:002008-01-24T00:19:31-08:00A gentle reminder about pluralizationsJosh Susser<p>Watching the <a href="http://dev.rubyonrails.org/timeline?milestone=on&amp;ticket=on&amp;changeset=on&amp;wiki=on&amp;max=50&amp;daysback=90&amp;format=rss">RSS feed</a> from the Ruby on Rails trac is a great way to keep up on what's happening in Rails development. If you're doing any development on the Ruby on Rails project it's required reading. Even if you just are using Rails for a web app, it's useful to keep up on what bugs people are reporting.</p>
<p>Lately I've noticed a slew of bugs being opened against the Inflector, the class in Rails that transforms words from one form to another: singular to plural, classname to tablename, etc. The bugs all complain that Inflector is getting a pluralization or singularization wrong. But this isn't a bug in Inflector, it is just an inherent limitation of how it works. But fear not, there is a better solution than opening a bug against the Inflector.</p>
<p>I guess this has been a constant thing over the history of Rails, but since it's still going on, it deserves a rehash.</p><p>Watching the <a href="http://dev.rubyonrails.org/timeline?milestone=on&amp;ticket=on&amp;changeset=on&amp;wiki=on&amp;max=50&amp;daysback=90&amp;format=rss">RSS feed</a> from the Ruby on Rails trac is a great way to keep up on what's happening in Rails development. If you're doing any development on the Ruby on Rails project it's required reading. Even if you just are using Rails for a web app, it's useful to keep up on what bugs people are reporting.</p>
<p>Lately I've noticed a slew of bugs being opened against the Inflector, the class in Rails that transforms words from one form to another: singular to plural, classname to tablename, etc. The bugs all complain that Inflector is getting a pluralization or singularization wrong. But this isn't a bug in Inflector, it is just an inherent limitation of how it works. But fear not, there is a better solution than opening a bug against the Inflector.</p>
<p>I guess this has been a constant thing over the history of Rails, but since it's still going on, it deserves a rehash.</p>
<p>Class Inflector has a bunch of regular expressions it uses to match a word then transform it from singular to plural or plural to singular. It's a pretty clever but fundamentally limited piece of code, as it relies on a list of special cases to do its work.</p>
<p>You can see all the standard rules for pluralization and singularization in <a href="http://dev.rubyonrails.org/svn/rails/tags/rel_1-1-2/activesupport/lib/active_support/inflections.rb">inflections.rb</a> in the ActiveSupport library. Notice there are only 18 rules for pluralization, which is not going to cover all the vagaries of the English language. Given the highly irregular nature of English, it would be a daunting task to create an exhaustive list of rules for pluraliztion, and that's even ignoring neologisms and ad-hoc corruptions. Rails solves the problem by allowing each project to define its own set of custom rules for singular and plural forms.</p>
<p>If the inflector is not handling any of your model names correctly, don't open a bug on it. Instead, go to the environments.rb file in your project and add your own rules for the names of the models in question.</p>
<p>At the end of environments.rb you'll see the following commented out block of code:</p>
<pre><code># Inflector.inflections do |inflect|
# inflect.plural /^(ox)$/i, '\1en'
# inflect.singular /^(ox)en/i, '\1'
# inflect.irregular 'person', 'people'
# inflect.uncountable %w( fish sheep )
# end
</code></pre>
<p>Uncomment the block and put your own rules in it. See the docs on <a href="http://api.rubyonrails.org/classes/Inflector/Inflections.html">Inflections</a> for the detailed API.</p>
<pre><code>Inflector.inflections do |inflect|
inflect.irregular 'leaf', 'leaves'
inflect.irregular 'staff', 'staves'
end
</code></pre>
<p>The <code>irregular</code> method is most convenient to create a rule for a particular word. If you want to use a regular expression for several similar words, use the <code>plural</code> and <code>singular</code> methods (you need a matched set of them both, or only one direction will work). And of course <code>uncountable</code> indicates a word is the same in both singular and plural forms.</p>
<p>So that's it. Don't be creating any more pluralization bug tickets. Just use the handy Inflector interface to customize pluralizations for yourself.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/372006-05-10T13:52:00-07:002008-01-24T00:19:31-08:00Laying TracksJosh Susser<p>The <a href="http://ruby.meetup.com/6/events/4899197/">SF Ruby Meetup</a> last night was a great event. There were over 50 people there, and thanks to Adaptive Path we had a nice big room with plenty of space for everyone (though not quite enough chairs, heh). Dumb me forgot to bring a camera so I don't have any pictures to post, sorry! Fortunately I remembered everything else so my talk went off without a hitch.</p>
<p><a href="http://ruby.meetup.com/6/events/4929994/">Next month's meetup</a> is on June 13th, and will (tentatively) be held at the offices of Odeo, just a block from Adaptive Path.</p><p>The <a href="http://ruby.meetup.com/6/events/4899197/">SF Ruby Meetup</a> last night was a great event. There were over 50 people there, and thanks to Adaptive Path we had a nice big room with plenty of space for everyone (though not quite enough chairs, heh). Dumb me forgot to bring a camera so I don't have any pictures to post, sorry! Fortunately I remembered everything else so my talk went off without a hitch.</p>
<p><a href="http://ruby.meetup.com/6/events/4929994/">Next month's meetup</a> is on June 13th, and will (tentatively) be held at the offices of Odeo, just a block from Adaptive Path.</p>
<p>Ryan Carver's talk on Measure Map was very interesting. If anyone has concerns about the scalability of Rails apps, just check out Measure Map. Ryan said they can handle 10 million hits a day. Sweet.</p>
<p>My <em>Laying Tracks</em> talk on how to contribute to Ruby on Rails went over very well. The response was much better than I expected. I guess a lot of people are interested in the topic! And now that I've foolishly demonstrated I can speak in public, it looks like I'll be giving a talk at the SDForum Ruby SIG next month (stay stuned for details). If anyone has an idea for a topic, I'm just waiting for inspiration to strike.</p>
<p>I'm making the slides available under Creative Commons license <a href="http://creativecommons.org/licenses/by-nc-sa/2.5/">Attribution-NonCommercial-ShareAlike 2.5</a>. That means you can do what you want with them as long as you don't make money off it and give me credit for my work.</p>
<p>The slides: <a href="http://hasmanythrough.com/layingtracks/LayingTracks.pdf">Laying Tracks</a></p>
<p>And now that you're all revved up to contribute to Ruby on Rails, cruise on over to Kevin Clark's blog to read about <a href="http://glu.ttono.us/articles/2006/05/10/documenting-rails">Documenting Rails</a>. Writing docs for Rails is a great way to get started as a contributor. Thanks, Kevin, for leading the effort on improving the documentation!</p>tag:blog.hasmanythrough.com,2006-02-27:Article/352006-05-06T18:40:00-07:002008-01-24T00:19:31-08:00has_many :through gets :uniqJosh Susser<p>If you don't follow the Rails Trac <a href="http://dev.rubyonrails.org/timeline">commit log</a>, you may find this interesting. Jeremy Kemper just checked in a <a href="http://dev.rubyonrails.org/changeset/4325">change</a> to enable the <code>:uniq</code> option for <code>has_many :through</code> associations.</p><p>If you don't follow the Rails Trac <a href="http://dev.rubyonrails.org/timeline">commit log</a>, you may find this interesting. Jeremy Kemper just checked in a <a href="http://dev.rubyonrails.org/changeset/4325">change</a> to enable the <code>:uniq</code> option for <code>has_many :through</code> associations.</p>
<p>So what does the <code>:uniq</code> option do? The docs for the <code>has_many</code> options say:</p>
<blockquote>
<p><code>:uniq</code> - if set to true, duplicates will be omitted from the collection. Useful in conjunction with :through.</p>
</blockquote>
<p>This is very handy when there are multiple connections between two model objects via a join model but you only care about whether there are any connections at all. Let's go back to my favorite example, where contributors contribute to books in various roles, such as author, editor, illustrator, etc. I'll omit the migrations for Book and Contributor as they aren't interesting for what we're doing here.</p>
<pre><code>create_table "contributions" do |t|
t.column "book_id", :integer
t.column "contributor_id", :integer
t.column "role", :string
end
class Contribution &lt; ActiveRecord::Base
belongs_to :book
belongs_to :contributor
end
class Book &lt; ActiveRecord::Base
has_many :contributions, :dependent =&gt; :destroy
has_many :contributors, :through =&gt; :contributions
end
class Contributor &lt; ActiveRecord::Base
has_many :contributions, :dependent =&gt; :destroy
has_many :books, :through =&gt; :contributions
end
</code></pre>
<p>That's all well and good, but the associations in the Book and Contributor models are a bit weak. If Sam contributed to a book as both an author and an illustrator, then <code>book.contributors</code> will include Sam twice. Since the contributors collection doesn't include any information about the roles, Sam showing up twice is mere redundancy. Let's try to improve the model so things are more useful.</p>
<pre><code>class Book &lt; ActiveRecord::Base
has_many :contributions, :dependent =&gt; :destroy
has_many :contributors, :through =&gt; :contributions, :uniq =&gt; true
end
</code></pre>
<p>Adding the <code>:uniq =&gt; true</code> option tells the association to eliminate redundant results. Now it doesn't matter how many different ways Sam contributes to a book; he is listed as a contributor only once.</p>
<p>That's nice, but we can do better.</p>
<pre><code>class Contribution &lt; ActiveRecord::Base
belongs_to :book
belongs_to :contributor
belongs_to :author, :class_name =&gt; "Contributor"
belongs_to :editor, :class_name =&gt; "Contributor"
belongs_to :illustrator, :class_name =&gt; "Contributor"
belongs_to :proofreader, :class_name =&gt; "Contributor"
end
class Book &lt; ActiveRecord::Base
has_many :contributions, :dependent =&gt; :destroy
has_many :contributors, :through =&gt; :contributions, :uniq =&gt; true
has_many :authors, :through =&gt; :contributions, :source =&gt; :author, :conditions =&gt; "contributions.role = 'author'"
has_many :editors, :through =&gt; :contributions, :source =&gt; :editor, :conditions =&gt; "contributions.role = 'editor'"
has_many :illustrators, :through =&gt; :contributions, :source =&gt; :illustrator, :conditions =&gt; "role = 'contributions.illustrator'"
has_many :proofreaders, :through =&gt; :contributions, :source =&gt; :proofreader, :conditions =&gt; "role = 'contributions.proofreader'"
end
</code></pre>
<p>Here I've added some special associations to make it easier to access contributors by role. I'm assuming there will only be one contribution record for each role, so there's no <code>:uniq</code> option needed. With the above associations we can say <code>book.contributors</code> and <code>book.authors</code>, and both will return collections of contributors with no duplicates.</p>
<p><strong>A word about performance...</strong></p>
<p>The <code>:uniq</code> option removes duplicates in Ruby code, not in the database query. If you have a large number of duplicates, it might be better to use the <code>:select</code> option to tell the database to remove duplicates using the <code>DISTINCT</code> keyword. Like so:</p>
<pre><code>class Book &lt; ActiveRecord::Base
has_many :contributions, :dependent =&gt; :destroy
has_many :contributors, :through =&gt; :contributions, :select =&gt; "DISTINCT contributors.*"
end
</code></pre>
<p>I find using this approach a tad messy, as you have to explicitly include the name of the table in the select option, which isn't very DRY. I'd love to see a <code>:distinct</code> option that could be used like the one in counters. (I've looked into what it would take to implement that, but the association code is some of the nastiest code in ActiveRecord, and I'm not brave enough to try a change like that yet.)</p>
<p>And remember, as always, the best way to decide between using <code>:uniq =&gt; true</code> and <code>:select =&gt; "DISTINCT..."</code> is to run performance measurements on your application. Only the data can tell you which way is best for you, or if there's even enough of a difference to matter.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/332006-04-25T20:25:00-07:002008-01-24T00:19:31-08:00Ruby Conference in Silicon ValleyJosh Susser<p>As I <a href="http://blog.hasmanythrough.com/articles/read/190">mentioned</a>, I spent the weekend at the Silicon Valley Ruby Conference. Everyone I spoke with there agreed that the conference was surprisingly good. I haven't been to a conference that was so much fun since the early days of OOPSLA. Congrats to SDForum and Ruby Central for putting on such a good event.</p>
<p>What made SVRC so good? Getting to meet so many great people. Ruby and Rails definitely have a good community going. Lots of sharp people, and very friendly too.</p>
<p>The presentations were mostly quite interesting (more after the flip). I'm glad there was a good amount of break time so I got to talk with lots of people and find out what they were up to. I didn't bring my PowerBook so I couldn't go nuts trying to code stuff up during the conference, but I came away with a few ideas that I'm going to have to look into soon.</p><p>As I <a href="http://blog.hasmanythrough.com/articles/read/190">mentioned</a>, I spent the weekend at the Silicon Valley Ruby Conference. Everyone I spoke with there agreed that the conference was surprisingly good. I haven't been to a conference that was so much fun since the early days of OOPSLA. Congrats to SDForum and Ruby Central for putting on such a good event.</p>
<p>What made SVRC so good? Getting to meet so many great people. Ruby and Rails definitely have a good community going. Lots of sharp people, and very friendly too.</p>
<p>The presentations were mostly quite interesting (more after the flip). I'm glad there was a good amount of break time so I got to talk with lots of people and find out what they were up to. I didn't bring my PowerBook so I couldn't go nuts trying to code stuff up during the conference, but I came away with a few ideas that I'm going to have to look into soon.</p>
<p>I rated most of the presentations as very good or excellent. I think Rich Kilmer and Jason Hoffman tied for the most kick-ass presentations - both of them made my head spin! There were a couple dogs, but even the dogs of the show weren't too bad. I think it was unfortunate that there were three separate sessions on building DSLs (Domain Specific Lanugages) - that seemed sort of redundant and the time could have been better spent on other material. Alex Chaffee could have used that time to do his three different rants (and still would have run over!).</p>
<p>I didn't take many notes at the conference, so these are my impressions mostly from memory.</p>
<h3>Day One</h3>
<p><strong>Adam Keys</strong> - his talk on the Ruby community was a great way to start the conference. My favorite bit, on the power of Ruby: "Unwritten software is the easiest to maintain."</p>
<p><strong>Ryan Davis</strong> - a great presentation on TDD with <a href="http://www.zenspider.com/ZSS/Products/ZenTest/">ZenTest</a>. I'm going to check it out soon - the AutoTest feature looks wildly useful. A philosophical point: Ryan talked about "No Peekie TDD", where you write your code from the tests <em>without</em> looking at the UI at all. I can see how this would force you to follow TDD more because you couldn't "cheat" and see if something was working by looking at the view in the browser, so you would have to write the required tests to know if it was doing the right thing. However, this is somewhat counter to what DHH preaches about starting with the HTML in the view and working your way down. I guess the thing to do is try both approaches and see which one works best for me. These things are often as much a matter of preference and work style as anything else.</p>
<p><strong>Hal Fulton</strong> - spoke on interfacing Ruby with Oracle. This was good information, though not something I personally was interested in. It was a bit odd to me that the material presented was more of a report on someone else's work than something the presenter created or was involved in, but it turned out a couple of the sessions were like that. I suppose it's useful to get that sort of "book report" presentation, but I wasn't used to it so I was a bit confused as to what was going on for a while.</p>
<p><strong>Rich Kilmer</strong> - I think it's impossible to meaningfully describe Rich's presentation. It was sort of a tall tale about how he used Ruby and Rails to do in 200 hours what a team of 3 spent 8 months not achieving in Java. Rich is a great speaker - not only entertaining but spews information so fast it left me gasping. If this was an OOPSLA conference, this would have been the keynote talk at the big fancy dinner.</p>
<p><strong>Chad Fowler</strong> - a nice summary of the new features in Rails 1.1. What made the talk special was the way he set everything in the context of linguistic expression.</p>
<p><strong>Eric Hodel</strong> - Distributed Ruby. This ended up being a bit of a book report presentation too, but Eric had done enough with DRb that he had a lot of information from his own experiences to share. (The whole talk was <em>deja vu</em> for me, since I used to do a lot of work with distributed systems and implemented a very similar system in Smalltalk/V for use in a research project.) The integration with ZeroConf/Bonjour was nice. Kind of makes OMG/CORBA stuff look lame in comparison.</p>
<p><strong>David Pollak</strong> - this talk was on metaprogramming and building classes on the fly. Unfortunately David hasn't yet grokked the Ruby way of metaprogramming so his approach was confusing and hard to use. The DSL he was creating using metaprogramming seemed interesting, but he needs to read <a href="http://whytheluckystiff.net/articles/seeingMetaclassesClearly.html">why on metaclasses</a>.</p>
<h3>Day Two</h3>
<p><strong>Steven Baker</strong> - rSpec and Behavior Driven Development. BDD is the new TDD, or so it seems. I didn't get what Behavior Driven Development was about during the presentation, but after reading up on it I can see that it's actually pretty nifty. I should apologize to Steven for being argumentative during his presentation. I did a lot of thinking about his DSL for BDD afterwards, and I've got some ideas that I want to look into. I'm not sure if the DSL is a red herring or really cool, but I like the process and the focus on testing the interface instead of the implementation.</p>
<p><strong>Jason Hoffman</strong> - how to scale your Rails app. This was my other favorite presentation. Jason blew the whole room away with numbers, pictures, and tales of his adventures discovering the secret sauce of scaling. Optimize for RAM, not CPU. Use Solaris and ZFS. Power outlets matter. He's doing a <a href="http://scalewithrails.com/">workshop</a> to teach this stuff in depth - probably worth checking out if you're serious about deploying a big app.</p>
<p><strong>Joe O'Brien</strong> - Yet more DSL stuff. This talk would have been great on its own, but following the other two presentations on building DSLs it was a bit redundant. There's only so much you can say about DSLs once you show how nifty they are, and by that point we'd heard much of it before. People wanted to see an example <em>how</em> to make a DSL in Ruby, but Joe wasn't prepared for that and didn't want to wing it live.</p>
<p><strong>Alex Chaffee</strong> - All Alex needed was a Hawaiian shirt and he'd have been a dead ringer for Wash, the pilot of <em>Serenity</em>. Same maniacal sense of humor, and underneath he's a genius savant. Alex was supposedly talking about building Web 2.0 apps, but mostly he just ranted about problems with Rails, JavaScript, CSS, and just about everything else he could fit in. It's great to hear about some of the nastier problems that are just waiting to ambush the unsuspecting Rails developer. I really want to get a copy of his slides because I missed half of what he was saying it all went by so fast!</p>
<p><strong>Joel VanderWerf</strong> - this talk was about mixing Ruby and C to build a high-performance simulation system. Pretty nifty stuff. Man, I hadn't thought about Runge-Kutta 4th order integration since college. I like how flexible Ruby is - creating special-purpose primitives in Smalltalk used to be a total PITA, and there was no good way to create and link them on the fly (though I guess Squeak can do that now).</p>
<h3>Whew!</h3>
<p>I'm so glad I went to this conference. Now I don't feel so bad that I missed Canada on Rail. If RailsConf in June is this good, I'll be a happy camper.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/312006-04-21T11:50:00-07:002008-01-24T00:19:30-08:00Self-referential has_many :through associationsJosh Susser<p><strong>Update:</strong> This article is now superceded by a <a href="http://blog.hasmanythrough.com/2007/10/30/self-referential-has-many-through">new version</a> that is updated for Rails 2.0 changes.</p>
<p>Here we go. Rick Olson helped me figure out how to do bi-directional, self-referential associations using <code>has_many :through</code>. It's not obvious (until you know the trick), so here's how it's done.</p>
<p>This example is for modeling digraphs.</p>
<pre><code>create_table "nodes" do |t|
t.column "name", :string
t.column "capacity", :integer
end
create_table "edges" do |t|
t.column "source_id", :integer, :null =&gt; false
t.column "sink_id", :integer, :null =&gt; false
t.column "flow", :integer
end
class Edge &lt; ActiveRecord::Base
belongs_to :source, :foreign_key =&gt; "source_id", :class_name =&gt; "Node"
belongs_to :sink, :foreign_key =&gt; "sink_id", :class_name =&gt; "Node"
end
class Node &lt; ActiveRecord::Base
has_many :edges_as_source, :foreign_key =&gt; 'source_id', :class_name =&gt; 'Edge'
has_many :edges_as_sink, :foreign_key =&gt; 'sink_id', :class_name =&gt; 'Edge'
has_many :sources, :through =&gt; :edges_as_sink
has_many :sinks, :through =&gt; :edges_as_source
end
</code></pre>
<p>A few pictures would probably be helpful, but I don't have time to be artistic this morning so you'll have to use your imagination and visualize things on your own. Each edge connects a source node to a sink node, and each node can have any number of incoming or outgoing edges.</p>
<p>The tricky bit is using the main <code>has_many</code> associations to distinguish the direction of the edge. Use the <code>:foreign_key</code> option to specify whether the edge refers to the node as its source or sink. Then the node can refer to other nodes as its sources or sinks by going through the appropriate <code>has_many</code> association.</p>
<p>And that's it.</p><p><strong>Update:</strong> This article is now superceded by a <a href="http://blog.hasmanythrough.com/2007/10/30/self-referential-has-many-through">new version</a> that is updated for Rails 2.0 changes.</p>
<p>Here we go. Rick Olson helped me figure out how to do bi-directional, self-referential associations using <code>has_many :through</code>. It's not obvious (until you know the trick), so here's how it's done.</p>
<p>This example is for modeling digraphs.</p>
<pre><code>create_table "nodes" do |t|
t.column "name", :string
t.column "capacity", :integer
end
create_table "edges" do |t|
t.column "source_id", :integer, :null =&gt; false
t.column "sink_id", :integer, :null =&gt; false
t.column "flow", :integer
end
class Edge &lt; ActiveRecord::Base
belongs_to :source, :foreign_key =&gt; "source_id", :class_name =&gt; "Node"
belongs_to :sink, :foreign_key =&gt; "sink_id", :class_name =&gt; "Node"
end
class Node &lt; ActiveRecord::Base
has_many :edges_as_source, :foreign_key =&gt; 'source_id', :class_name =&gt; 'Edge'
has_many :edges_as_sink, :foreign_key =&gt; 'sink_id', :class_name =&gt; 'Edge'
has_many :sources, :through =&gt; :edges_as_sink
has_many :sinks, :through =&gt; :edges_as_source
end
</code></pre>
<p>A few pictures would probably be helpful, but I don't have time to be artistic this morning so you'll have to use your imagination and visualize things on your own. Each edge connects a source node to a sink node, and each node can have any number of incoming or outgoing edges.</p>
<p>The tricky bit is using the main <code>has_many</code> associations to distinguish the direction of the edge. Use the <code>:foreign_key</code> option to specify whether the edge refers to the node as its source or sink. Then the node can refer to other nodes as its sources or sinks by going through the appropriate <code>has_many</code> association.</p>
<p>And that's it.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/302006-04-20T15:53:00-07:002008-01-24T00:19:30-08:00Many-to-many Dance-off!Josh Susser<p>I've noticed there's a bit of confusion about the differences between the two ways to create many-to-many relationships using Rails associations. That confusion is understandable, since <code>has_many :through</code> is still pretty new there isn't much written about it. <code>has_and_belongs_to_many</code> is the old, established player and most stuff out there assumes that's what you use for a many-to-many relationship. In fact, a lot of people don't seem to grasp that there is a difference at all!</p>
<p>As we all learned from watching classic movies, the best way to tell the difference between two prospective choices is to have a dance-off. You get to see everyone's moves (which of course are an accurate reflection of inner character), nobody has to die, and the hummable tune makes it a shoe-in for an Oscar nomination. Well, it's either that or do one of those boring "compare and contrast" essays they taught us about in sixth grade English class.</p><p>I've noticed there's a bit of confusion about the differences between the two ways to create many-to-many relationships using Rails associations. That confusion is understandable, since <code>has_many :through</code> is still pretty new there isn't much written about it. <code>has_and_belongs_to_many</code> is the old, established player and most stuff out there assumes that's what you use for a many-to-many relationship. In fact, a lot of people don't seem to grasp that there is a difference at all!</p>
<p>As we all learned from watching classic movies, the best way to tell the difference between two prospective choices is to have a dance-off. You get to see everyone's moves (which of course are an accurate reflection of inner character), nobody has to die, and the hummable tune makes it a shoe-in for an Oscar nomination. Well, it's either that or do one of those boring "compare and contrast" essays they taught us about in sixth grade English class.</p>
<p>So who are the players we have to choose between? Let's take a quick look at them before the music starts and we get to see their moves.</p>
<h2>Join Table: Simple Associations</h2>
<p>Table:</p>
<pre><code>create_table "dancers_movies", :id =&gt; false do |t|
t.column "dancer_id", :integer, :null =&gt; false
t.column "movie_id", :integer, :null =&gt; false
end
</code></pre>
<p>Models:</p>
<pre><code>class Dancer &lt; ActiveRecord::Base
has_and_belongs_to_many :movies
end
class Movie &lt; ActiveRecord::Base
has_and_belongs_to_many :dancers
end
</code></pre>
<p><code>has_and_belongs_to_many</code> associations are simple to set up. The join table has only foreign keys for the models being joined - no primary key or other attributes. (Other attributes were supported using <code>push_with_attributes</code> for a while, but that feature has been deprecated.) There is no model class for the join table.</p>
<h2>Join Model: Rich Associations</h2>
<p>Table:</p>
<pre><code>create_table "appearances", do |t|
t.column "dancer_id", :integer, :null =&gt; false
t.column "movie_id", :integer, :null =&gt; false
t.column "character_name", :string
t.column "dance_numbers", :integer
end
</code></pre>
<p>Models:</p>
<pre><code>class Appearance &lt; ActiveRecord::Base
belongs_to :dancer
belongs_to :movie
end
class Dancer &lt; ActiveRecord::Base
has_many :appearances, :dependent =&gt; true
has_many :movies, :through =&gt; :appearances
end
class Movie &lt; ActiveRecord::Base
has_many :appearances, :dependent =&gt; true
has_many :dancers, :through =&gt; :appearances
end
</code></pre>
<p><code>has_many :through</code> associations are pretty easy to set up for the simple case, but can get tricky when using other features like polymorphism. The table for the join model has a primary key and can contain attributes just like any other model.</p>
<h2>Checking out the moves</h2>
<p>Here's the basic feature comparison of the two options.</p>
<table style="text-align: left">
<tr><th>Association</th>
<td><code>has_and_belongs_to_many</code></td>
<td><code>has_many :through</code></td></tr>
<tr><th>AKA</th>
<td>habtm</td>
<td>through association</td></tr>
<tr><th>Structure</th>
<td>Join Table</td>
<td>Join Model</td></tr>
<tr><th>Primary Key</th>
<td style="color: red">no</td>
<td style="color: green">yes</td></tr>
<tr><th>Rich Association</th>
<td style="color: red">no</td>
<td style="color: green">yes</td></tr>
<tr><th>Proxy Collection</th>
<td style="color: green">yes</td>
<td style="color: red">no</td></tr>
<tr><th>Distinct Selection</th>
<td style="color: green">yes</td>
<td><span style="text-decoration: line-through">no</span> <span style="color: green">yes</span></td></tr>
<tr><th>Self-Referential</th>
<td style="color: green">yes</td>
<td style="color: green">yes</td></tr>
<tr><th>Eager Loading</th>
<td style="color: green">yes</td>
<td style="color: green">yes</td></tr>
<tr><th>Polymorphism</th>
<td style="color: red">no</td>
<td style="color: green">yes</td></tr>
<tr><th>N-way Joins</th>
<td style="color: red">no</td>
<td style="color: green">yes</td></tr>
</table>
<p>There's a lot of good stuff packed into that table, so let's break it down and see what it's all about.</p>
<h4>Structure</h4>
<p><code>has_and_belongs_to_many</code> uses a simple join table where each row is just two foreign keys. There's no model class for the join as the join table records are never accessed directly.</p>
<p><code>has_many :through</code> upgrades the join table to a full-fledged model. It uses a model class to represent entries in the table.</p>
<h4>Primary Key</h4>
<p>Join tables have no primary key. I've heard some people like to create a primary key from the pair of foreign keys, but Rails won't use that primary key for anything. I'm not sure what you'd get from creating that key, though it might give you a performance benefit depending on your database. (I'm not a DBA so I have nothing more to say about that.)</p>
<p>Join models have primary keys, just like every other model. This means you can access and manipulate records directly.</p>
<h4>Rich Association</h4>
<p>Way back before Rails 1.1, you could use <code>push_with_attributes</code> to store extra attributes in your habtm join table. There were all sorts of problems with doing that, including not being able to update the attributes later on. <code>push_with_attributes</code> has now been deprecated. If you want a rich association with extra attributes, use a join model.</p>
<h4>Proxy Collection</h4>
<p>One of the advantages of using habtm is that associations are proxy collections. That means you can create entries in the join table using the association's <code>&lt;&lt;</code> method, just like with <code>has_many</code> associations. Since join model records have those extra attributes, it is more complicated to create them automatically the same way join table entries can be. Rails punts on this, so you have to create the join model entries manually. (For a full explanation, see my <a href="http://blog.hasmanythrough.com/2006/4/17/join-models-not-proxy-collections">Why aren't join models proxy collections?</a> article.)</p>
<h4>Distinct Selection</h4>
<p>Sometimes join table (or model) can have multiple references between the same records. For example, a person may contribute to a book as a writer and an illustrator. If you have multiple references, the database will happily return you all those multiple copies in response to your query. The option <code>:uniq</code> tells the association to filter out duplicate objects so you only get a single copy of each. This is similar to using the DISTINCT keyword in SQL, though the removal of duplicates happens in Ruby instead of the database. When this article was first written only habtm supported <code>:uniq</code>, but now through associations do as well.</p>
<h4>Self-Referential</h4>
<p>Both habtm and through associations can be self-referential. Users being friends with users is an example of a self-referential relationship. You can do that with habtm using the <code>:foreign_key</code> and <code>:association_foreign_key</code> options on the association. You can do the same thing with through associations, though it's obscure how to do it so I'll have to write up how to manage it soon.</p>
<h4>Eager Loading</h4>
<p>Both habtm and through associations support eager loading of associated objects with the <code>:include</code> option.</p>
<h4>Polymorphism</h4>
<p>Join models and through associations can work with polymorphic model types. At least in one direction they do. (c.f. <a href="http://blog.hasmanythrough.com/2006/4/3/polymorphic-through">The other side of polymorphic :through associations</a>)</p>
<h4>N-way Joins</h4>
<p>A habtm association can only join two models. But sometimes you need to represent an association of multiple models. For example, a booking might represent a flight, a passenger, and a seat assignment. Using a through association, you can create a join model that joins as many models as you need. The tricky part is building the queries to get at the associated objects conveniently.</p>
<h2>And the winner is...</h2>
<p><code>has_and_belongs_to_many</code> is light on his feet and has some smooth moves. But <code>has_many :through</code> is versatile, even if he has to work harder and his moves are a bit rough in places.</p>
<p>Seriously, there's no way to pick a winner here. Like any engineering decision, choosing a join table or a join model is a matter of picking the right tool for the job (or the right dancer for the part). Now that you've seen our players go head to head, you can make a better choice about who should get that part.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/282006-04-17T21:26:00-07:002008-01-24T00:19:30-08:00Why aren't join models proxy collections?Josh Susser<p>Who hasn't created a <code>has_many :through</code> association and then tried to use <code>&lt;&lt;</code> to add an object to the association? It seems like the obvious thing to do, but it doesn't work. Well, it works until you save the model and find that the new record in the join model didn't get saved into the database. It took me a bit of head scratching, but I finally figured out <em>why</em> it doesn't work, and what to do about it.</p>
<p>To understand the problem, you have to dig into how ActiveRecord implements the convenience methods for working with associated objects. If you don't have a thing for the inner workings of Rails you might want to skip ahead.</p>
<p>As usual, I'll start with an example so we have something concrete to discuss. Consider a many-to-many mapping that matches technicians with their certified skills.</p>
<pre><code>class Certification &lt; ActiveRecord::Base
belongs_to :technician
belongs_to :skill
end
class Technician &lt; ActiveRecord::Base
has_many :certifications
has_many :skills, :through =&gt; :certifications
end
class Skill &lt; ActiveRecord::Base
has_many :certifications
has_many :technician, :through =&gt; :certifications
end
</code></pre><p>Who hasn't created a <code>has_many :through</code> association and then tried to use <code>&lt;&lt;</code> to add an object to the association? It seems like the obvious thing to do, but it doesn't work. Well, it works until you save the model and find that the new record in the join model didn't get saved into the database. It took me a bit of head scratching, but I finally figured out <em>why</em> it doesn't work, and what to do about it.</p>
<p>To understand the problem, you have to dig into how ActiveRecord implements the convenience methods for working with associated objects. If you don't have a thing for the inner workings of Rails you might want to skip ahead.</p>
<p>As usual, I'll start with an example so we have something concrete to discuss. Consider a many-to-many mapping that matches technicians with their certified skills.</p>
<pre><code>class Certification &lt; ActiveRecord::Base
belongs_to :technician
belongs_to :skill
end
class Technician &lt; ActiveRecord::Base
has_many :certifications
has_many :skills, :through =&gt; :certifications
end
class Skill &lt; ActiveRecord::Base
has_many :certifications
has_many :technician, :through =&gt; :certifications
end
</code></pre>
<p>Given these models (and the implied database schema), it's easy to map technicians to skills by creating new certification objects.</p>
<pre><code>pat = Technician.find(1)
pat.skills.size # =&gt; 4
macosx = Skill.find(10)
cert = Certification.new(:technician =&gt; pat,
:skill =&gt; macosx)
cert.save
pat.skills.size # =&gt; 4
pat.skills(true).size # =&gt; 5
</code></pre>
<p>After the call to <code>cert.save</code>, the <code>skills</code> accessor on <code>pat</code> still returns the cached collection of skills, so we need to call it passing <code>true</code> to force a reload. All that's great, but what if we try the following?</p>
<pre><code>pat.skills.size # =&gt; 5
winxp = Skill.find(13)
pat.skills &lt;&lt; winxp
pat.skills.size # =&gt; 6
pat.save
pat.skills(true).size # =&gt; 5
</code></pre>
<p>The above shows that Pat's WinXP certification didn't get saved. Perhaps Pat will have to take the training again. But what happened? The <code>&lt;&lt;</code> method works fine for regular <code>has_many</code> associations, and for <code>has_and_belongs_to_many</code> too. Why doesn't it work for <code>has_many :through</code> associations?</p>
<p>If you look at the API documentation for the <a href="http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#M000530">has_many</a> method, you can see it adds a collection attribute for managing the objects in the association. That attribute behaves mostly like an Array of associated objects, but it includes some extra methods that make working with those objects more convenient. That old Rails magic can make your life much easier, but it can also be confusing. If you evaluate <code>pat.skills.class</code>, the result will be <code>Array</code>. So how does an Array know how to <code>build()</code> and <code>create()</code> associated objects, or have <code>&lt;&lt;</code> fix up their foreign keys?</p>
<p>Let me pretend I'm Penn Jillette for a moment and explain the secret behind the magic trick: It's not really an Array. In actuality it's an instance of AssociationCollection, which is a subclass of AssociationProxy. These classes follow the well-known <a href="http://c2.com/cgi/wiki?ProxyPattern">Proxy pattern</a>. They front a target object, provide some extra behavior on top of that object, and delegate all other methods to the target object. AssociationProxy delegates to a single model object, as for <code>has_one</code> or <code>belongs_to</code>. AssociationCollection delegates to an Array of model objects. Since the delegated methods include the <code>class()</code> method, if you ask the <code>skills</code> object what its class is, it will say it's an Array even though it's not. It will also do everything else an Array does, and then some.</p>
<p>The final piece of the puzzle can be found in the inheritance hierarchy of the association classes.</p>
<pre><code>AssociationProxy
AssociationCollection
HasAndBelongsToManyAssociation
HasManyAssociation
BelongsToAssociation
HasOneAssociation
HasManyThroughAssociation
</code></pre>
<p>This hierarchy shows that HasManyAssociation is a subclass of AssociationCollection, but HasManyThroughAssociation is a subclass of AssociationProxy. That means that HasManyThroughAssociation doesn't get the collection management methods that HasManyAssociation does, including <code>&lt;&lt;</code>. But if the association doesn't have a <code>&lt;&lt;</code> method, why doesn't <code>pat.skills &lt;&lt; winxp</code> throw an exception? Because the <code>&lt;&lt;</code> method is delegated to the target array! The <code>Array#&lt;&lt;</code> method adds the <code>winxp</code> object to the target array, but doesn't do any ActiveRecord magic to fix its foreign key or make sure it gets saved. Well, why not?</p>
<p>Think for a moment about what <code>pat.skills &lt;&lt; winxp</code> would imply. Since skills are associated via a join model, that method would have to create a new Certification object to hold the mapping. The <code>&lt;&lt;</code> method for <code>has_and_belongs_to_many</code> associations does something very close to that, creating a record in the appropriate join table. If it's good enough for <code>has_and_belongs_to_many</code>, why not for <code>has_many :through</code>?</p>
<p>The main difference between a simple <code>has_and_belongs_to_many</code> join table and a <code>has_many :through</code> join model is that the join model can have attributes other than the foreign keys for the records it is joining. In fact, if you didn't have those other attributes you probably wouldn't use a join model and would settle for a join table.</p>
<p>Let's say our Certification object has some extra attributes, like the date and level of the certification. Now let's get Pat certified for a new skill.</p>
<pre><code>pat.skills &lt;&lt; winvista
</code></pre>
<p>For that to do the right thing Rails would have to create a new Certification object. Alright, what values should it use for the certification date and level? Care to guess? Neither does Rails, so you can't do it that way.</p>
<pre><code>cert = Certification.new(:technician =&gt; pat,
:skill =&gt; macosx,
:date =&gt; Date.new(2006,4,15),
:level =&gt; 1)
pat.skills(true)
</code></pre>
<p>The above example works fine for certifying Pat in a new skill. Not as convenient as <code>&lt;&lt;</code>, but it has the advantage of working correctly.</p>
<p>In closing, I think that this all points out how different <code>has_many :through</code> associations are from regular <code>has_many</code> associations. I think it would probably have been better to coin a new name for the association rather than confuse things by reusing <code>has_many</code> for something that's not really a <code>has_many</code> anymore. At the very least, there's some work needed to fix the <code>has_many</code> documentation to describe the differences when using the <code>:through</code> option.</p>
<p>I do actually have some ideas about how to add some more magic and make join models easier to work with. However this article is long enough already, so I'll have to save that for next time.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/252006-04-10T19:15:00-07:002008-01-24T00:19:30-08:00My first Ruby on Rails patchJosh Susser<p>My first Ruby on Rails patch was just committed to the trunk. I'm now officially a Rails contributor. Yay me!</p><p>My first Ruby on Rails patch was just committed to the trunk. I'm now officially a Rails contributor. Yay me!</p>
<p><a href="http://dev.rubyonrails.org/changeset/4206">Changeset 4206</a> is my patch for enhancing the <code>:extend</code> option to allow multiple named extension modules on associations.</p>
<p>I've wanted this ever since I started using extension modules. I quickly ran into a situation where I wanted to use two named modules for the same association. Here's the hacked way of doing that:</p>
<pre><code>has_many :friends, do
include PeopleExtension
include FriendsExtension
end
</code></pre>
<p>The change in my patch allows this more compact syntax:</p>
<pre><code>has_many :friends, :extend =&gt; [PeopleExtension, FriendsExtension]
</code></pre>
<p>So much better!</p>
<p>This patch was a bit of a trial run for me to learn the whole process for putting a patch together. The actual change was only one line of Ruby code (but don't forget the 20 or so lines of tests and documentation!). Now that I seem to have the process down I can think about doing something a bit bigger.</p>
<p>As I get more confident in the process I'll write up a better description of how to contribute changes to Rails. The description on the Rails Trac is pretty bare-bones and takes a bit of muddling through if you've never worked on an open source project before.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/222006-04-03T14:20:00-07:002008-01-24T00:19:29-08:00The other side of polymorphic :through associationsJosh Susser<p>Here's a gotcha: <code>has_many :through</code> associations do not support polymorphic access to the associated object. In this article I'll show the reasons for this limitation, and also provide an approach that lets you work with polymorphic associations without too much trouble.</p>
<p>Let's start with some example code so I have something concrete to talk about. Here's some models for representing that an author can write either articles or books, and that each article or book can have multiple authors.</p>
<pre><code>class Authorship &lt; ActiveRecord::Base
belongs_to :author
belongs_to :publication, :polymorphic =&gt; true
end
class Author &lt; ActiveRecord::Base
has_many :authorships
has_many :publications, :through =&gt; :authorships
end
class Article &lt; ActiveRecord::Base
has_many :authorships, :as =&gt; :publication
has_many :authors, :through =&gt; :authorships
end
class Book &lt; ActiveRecord::Base
has_many :authorships, :as =&gt; :publication
has_many :authors, :through =&gt; :authorships
end
</code></pre>
<p>That's all by the book (no pun intended) and follows the examples in the Rails 1.1 release. So where is the gotcha?</p><p>Here's a gotcha: <code>has_many :through</code> associations do not support polymorphic access to the associated object. In this article I'll show the reasons for this limitation, and also provide an approach that lets you work with polymorphic associations without too much trouble.</p>
<p>Let's start with some example code so I have something concrete to talk about. Here's some models for representing that an author can write either articles or books, and that each article or book can have multiple authors.</p>
<pre><code>class Authorship &lt; ActiveRecord::Base
belongs_to :author
belongs_to :publication, :polymorphic =&gt; true
end
class Author &lt; ActiveRecord::Base
has_many :authorships
has_many :publications, :through =&gt; :authorships
end
class Article &lt; ActiveRecord::Base
has_many :authorships, :as =&gt; :publication
has_many :authors, :through =&gt; :authorships
end
class Book &lt; ActiveRecord::Base
has_many :authorships, :as =&gt; :publication
has_many :authors, :through =&gt; :authorships
end
</code></pre>
<p>That's all by the book (no pun intended) and follows the examples in the Rails 1.1 release. So where is the gotcha?</p>
<p><strong>The problem</strong> </p>
<p>Given our example, we can ask an article or book for its authors:</p>
<pre><code>my_article.authors # =&gt; [author1, author2, ...]
my_book.authors # =&gt; [author1, author2, ...]
</code></pre>
<p>However, we can't ask an author for all its publications!</p>
<pre><code>an_author.publications # =&gt; ERROR
</code></pre>
<p>So what's going on? Why does traversing the join model only work in one direction? Well, lets take a look at what is in the join model table.</p>
<pre><code>create_table "authorships" do |t|
t.column "author_id", :integer
t.column "publication_id", :integer
t.column "publication_type", :string
end
</code></pre>
<p>The reference to the publication is essentially a compound foreign key. The <code>_id</code> field holds the id of the object's record in its own table, and the <code>_type</code> field holds the name of the Ruby class of that object. That information is combined to do the join to find a publication's authors. Look at the SQL Rails generates for <code>article_1.authors</code></p>
<pre><code>SELECT authors.* FROM authors
INNER JOIN authorships ON authors.id = authorships.author_id
WHERE (authorships.publication_id = 1
AND authorships.publication_type = 'Article')
</code></pre>
<p>You can see in the WHERE clause how the join uses both the id and type fields to match the authors to the article. That's quite straightforward and works fine.</p>
<p>So what about the other direction? Why doesn't <code>author_1.publications</code> do the right thing? Let's try and write the SQL for it. First off, what about the SELECT clause?</p>
<pre><code>SELECT publications.* FROM publications
</code></pre>
<p>That's not right, because <em>there is no <code>publications</code> table</em>. The author's publications are scattered among some number of tables: articles, books, and possibly other types we may decide to add later. I'm not an SQL god so I don't know if there is some way to indirect the name of the table within a query, or a way to return non-homogeneous results, but I'm betting even if there were it would be pretty gross. Well, maybe not much grosser than the SQL for <code>:include</code>, but still gross. In short, you can't traverse a <code>:through</code> association to a polymorphic data type because you can't tell what table it's in.</p>
<p><strong>Another approach</strong></p>
<p>Where does that leave us? Are polymorphic :through associations useless? Not at all. There are two ways you can make use of them to get to the polymorphic object. First, if you really need to get a polymorphic collection of the associated objects, you can roll your own.</p>
<pre><code>def publications
self.authorships.collect { |a| a.publication }
end
</code></pre>
<p>There may be some performance issues with doing a query for each publication, but at least you can do it.</p>
<p>The other option is to create associations for each of the polymorphic types. Like so:</p>
<pre><code>class Authorship &lt; ActiveRecord::Base
belongs_to :author
belongs_to :publication, :polymorphic =&gt; true
belongs_to :article, :class_name =&gt; "Article",
:foreign_key =&gt; "publication_id"
belongs_to :book, :class_name =&gt; "Book",
:foreign_key =&gt; "publication_id"
end
class Author &lt; ActiveRecord::Base
has_many :authorships
has_many :articles, :through =&gt; :authorships, :source =&gt; :article,
:conditions =&gt; "authorships.publication_type = 'Article'"
has_many :books, :through =&gt; :authorships, :source =&gt; :book,
:conditions =&gt; "authorships.publication_type = 'Book'"
end
</code></pre>
<p>This technique provides associations that let you access all the objects of a particular class. It's not totally polymorphic, but at least you can grab lots of objects in a single query. Speaking of which, lets use these new associations to improve the <code>publications()</code> method in class Author.</p>
<pre><code>def publications
self.articles + self.books
end
</code></pre>
<p>Now we're down to needing only one query for each class of object in the polymorphic association, which scales much better than one query for each object.</p>
<p><strong>Whew!</strong></p>
<p>So that's it. Polymorphic :through associations can only be traversed from the polymorphic side. However, you can use special associations with conditions to limit the association to a particular class of object and restore the use of association access by collection.</p>
<p>Exercise for the reader: Use the approach in this article to create a join model where both associated objects are polymorphic. Have fun with that.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/202006-04-02T14:25:00-07:002008-01-24T00:19:29-08:00Rich associations: OUT, join models: INJosh Susser<p>I noticed this comment in the Rails trac timeline <a href="http://dev.rubyonrails.org/changeset/4123">[4123]</a></p>
<blockquote>
<p><code>* DEPRECATED: Using additional attributes on has_and_belongs_to_many associations. Instead upgrade your association to be a real join model [DHH]</code></p>
</blockquote>
<p>Couple that with the change to make habtm records with additional attributes <code>:read_only</code>, and it looks like the time to switch to <code>has_many :through</code> is now.</p>
<p>By the way, I've got a little list of articles I want to write showing various ways of using join models. If you have a request for something you want to see, leave a comment and I'll add it to the list.</p><p>I noticed this comment in the Rails trac timeline <a href="http://dev.rubyonrails.org/changeset/4123">[4123]</a></p>
<blockquote>
<p><code>* DEPRECATED: Using additional attributes on has_and_belongs_to_many associations. Instead upgrade your association to be a real join model [DHH]</code></p>
</blockquote>
<p>Couple that with the change to make habtm records with additional attributes <code>:read_only</code>, and it looks like the time to switch to <code>has_many :through</code> is now.</p>
<p>By the way, I've got a little list of articles I want to write showing various ways of using join models. If you have a request for something you want to see, leave a comment and I'll add it to the list.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/172006-03-28T20:25:00-08:002008-01-24T00:19:29-08:00hosts upgrade to 1.1 - DOOM!Josh Susser<p>Seems like a lot of shared hosts upgraded to Rails 1.1 today, breaking many apps including every Typo blog installation. DreamHost (my host), TextDrive, Site5, etc.</p>
<p>Unfortunately the DreamHost upgrade was so broke that the rails command won't work (so no generating a new app), and neither will rake. Looks like someone forget to test things! But the upshot is you can't use the rake command to get back on a frozen 1.0 release.</p>
<p>Here's a workaround that should let your app run:</p>
<pre><code>cd [RAILS_ROOT]/vendor
svn export "http://dev.rubyonrails.org/svn/rails/tags/rel_1-0-0" rails
</code></pre>
<p>This will create a rails directory in vendor that holds the 1.0 release, and should let your app operate again. Worked for me.</p>
<p><strong>UPDATE:</strong> Sorry about the previous tip that used a tarball of the 1.0 release. The tarball was actually of a project with the Rails release in the vendor directory, so it was slightly ugly to unpack. The svn recipe above does work for me - I just verified it to make sure.</p><p>Seems like a lot of shared hosts upgraded to Rails 1.1 today, breaking many apps including every Typo blog installation. DreamHost (my host), TextDrive, Site5, etc.</p>
<p>Unfortunately the DreamHost upgrade was so broke that the rails command won't work (so no generating a new app), and neither will rake. Looks like someone forget to test things! But the upshot is you can't use the rake command to get back on a frozen 1.0 release.</p>
<p>Here's a workaround that should let your app run:</p>
<pre><code>cd [RAILS_ROOT]/vendor
svn export "http://dev.rubyonrails.org/svn/rails/tags/rel_1-0-0" rails
</code></pre>
<p>This will create a rails directory in vendor that holds the 1.0 release, and should let your app operate again. Worked for me.</p>
<p><strong>UPDATE:</strong> Sorry about the previous tip that used a tarball of the 1.0 release. The tarball was actually of a project with the Rails release in the vendor directory, so it was slightly ugly to unpack. The svn recipe above does work for me - I just verified it to make sure.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/162006-03-28T15:47:00-08:002008-01-24T00:19:29-08:00The other ways :throughJosh Susser<p>Rails 1.1 has finally been <a href="http://weblog.rubyonrails.com/articles/2006/03/28/rails-1-1-rjs-active-record-respond_to-integration-tests-and-500-other-things">released</a>, and it includes a bunch of yummy new features.
Included in those are some changes to <code>has_many :through</code> associations that were added since I started writing about them.</p>
<p>The old stuff:</p>
<ul>
<li>A <code>has_many :through</code> association lets you use a join model instead of a <code>has_and_belongs_to_many</code> association and a join table. The advantages are that you can easily read and update attributes on the association, and you can use eager loading (<code>:includes</code>) to pre-fetch associated model data.</li>
</ul>
<p>The new stuff:</p>
<ul>
<li>You can use the <code>:through</code> option on a <code>has_many</code> association without a join model. This allows you to collapse two has_many relationships into one.</li>
<li>Use the <code>:source</code> option instead of <code>:class_name</code> to specify the associated model when the default name isn't what you want.</li>
</ul><p>Rails 1.1 has finally been <a href="http://weblog.rubyonrails.com/articles/2006/03/28/rails-1-1-rjs-active-record-respond_to-integration-tests-and-500-other-things">released</a>, and it includes a bunch of yummy new features.
Included in those are some changes to <code>has_many :through</code> associations that were added since I started writing about them.</p>
<p>The old stuff:</p>
<ul>
<li>A <code>has_many :through</code> association lets you use a join model instead of a <code>has_and_belongs_to_many</code> association and a join table. The advantages are that you can easily read and update attributes on the association, and you can use eager loading (<code>:includes</code>) to pre-fetch associated model data.</li>
</ul>
<p>The new stuff:</p>
<ul>
<li>You can use the <code>:through</code> option on a <code>has_many</code> association without a join model. This allows you to collapse two has_many relationships into one.</li>
<li>Use the <code>:source</code> option instead of <code>:class_name</code> to specify the associated model when the default name isn't what you want.</li>
</ul>
<p><strong>The other kind of join model</strong></p>
<p>In addition to reaching through a <code>belongs_to/belongs_to</code> join model, you can also use <code>has_many :through</code> where the join model is <code>belongs_to/has_many</code>. Here's an example:</p>
<pre><code>class Meal &lt; ActiveRecord::Base
has_many :dishes
has_many :ingredients, :through =&gt; :dishes
end
class Dish &lt; ActiveRecord::Base
belongs_to :meal
has_many :ingredients
end
class Ingredient &lt; ActiveRecord::Base
belongs_to :dish
end
</code></pre>
<p>If you want to go shopping for your meal, <code>@meal.ingredients</code> gets you your shopping list.</p>
<p><strong>Use the :source</strong></p>
<p>By default, a <code>has_many :through</code> association looks for an association in the join model with the same name as the base association. In our example above, the <code>:ingredients</code> looks for an <code>:ingredients</code> association in the Dish model. Sometimes that won't work, and in those cases use the <code>:source</code> option to specify which association to use.</p>
<pre><code>class Meal &lt; ActiveRecord::Base
has_many :dishes
has_many :ingredients, :through =&gt; :dishes
has_many :meats, :through =&gt; :dishes, :source =&gt; :ingredients,
:conditions =&gt; "ingredients.kind = 'meat'"
has_many :veggies, :through =&gt; :dishes, :source =&gt; :ingredients,
:conditions =&gt; "ingredients.kind = 'vegetable'"
has_many :dry_goods, :through =&gt; :dishes, :source =&gt; :ingredients,
:conditions =&gt; "ingredients.kind = 'dry'"
end
</code></pre>
<p>This is a change from using <code>:class_name</code> to specify the non-default model.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/142006-03-23T13:47:00-08:002008-01-24T00:19:29-08:00Dirt simple .rcss templatesJosh Susser<p>In my Rails app I have a need to dynamically generate stylesheets based on user settings. Using ERb to process a template into CSS is the obvious way to go, so I wanted to have .rcss templates in my app. I even found a <a href="http://rubyforge.org/projects/rcss/">RCSS project</a> on RubyForge that claims to do just that, but it actually doesn't do ERb at all anymore.</p>
<p>I thought I'd have to create a new template system to handle .rcss templates using the <code>ActionView.register_template_handler</code> call and making a handler class, etc., but that way lies madness. The docs are rather vague and the source code is twisted and (like most of Rails) mostly lacking anything like useful comments.</p>
<p>Anyway, I managed to figure out an incredibly simple way to process .rcss templates with ERb that doesn't involve making a new template system.</p><p>In my Rails app I have a need to dynamically generate stylesheets based on user settings. Using ERb to process a template into CSS is the obvious way to go, so I wanted to have .rcss templates in my app. I even found a <a href="http://rubyforge.org/projects/rcss/">RCSS project</a> on RubyForge that claims to do just that, but it actually doesn't do ERb at all anymore.</p>
<p>I thought I'd have to create a new template system to handle .rcss templates using the <code>ActionView.register_template_handler</code> call and making a handler class, etc., but that way lies madness. The docs are rather vague and the source code is twisted and (like most of Rails) mostly lacking anything like useful comments.</p>
<p>Anyway, I managed to figure out an incredibly simple way to process .rcss templates with ERb that doesn't involve making a new template system.</p>
<p>The key is using <code>render(:file =&gt; ...)</code> to grab the .rcss file. When rendering a file, Rails will do ERb processing on the file just as with normal .rhtml templates. So I created a Stylesheets controller to handle the CSS file requests. My solution looks like this:</p>
<p>The stylesheet URL</p>
<pre><code>http://domain.com/stylesheets/style.css
</code></pre>
<p>The route:</p>
<pre><code>map.connect 'stylesheets/:rcss', :controller =&gt; 'stylesheets', :action =&gt; 'rcss'
</code></pre>
<p>The controller:</p>
<pre><code>class StylesheetsController &lt; ApplicationController
layout nil
session :off
def rcss
if rcss = params[:rcss]
file_base = rcss.gsub(/\.css$/i, '')
file_path = "#{RAILS_ROOT}/app/views/stylesheets/#{file_base}.rcss"
@color = '#f77' # example setting
render(:file =&gt; file_path, :content_type =&gt; "text/css")
else
render(:nothing =&gt; true, :status =&gt; 404)
end
end
end
</code></pre>
<p>The .rcss templates go into <code>app/views/stylesheets/</code>, just as if they were view templates for the Stylesheets controller. I guess you could make this part of an existing controller and put the .rcss files into its view directory too, but I wanted to keep things separate for my app.</p>
<p>Obviously, my example action above is simplistic as it doesn't use model data to set instance variables to communicate settings to the .rcss template. I have a variant that uses another URL param to grab a model from the database and get style settings to use in the template, but that's standard Rails and not very interesting so I'm not going to show it here. For now, pretend that the line that sets <code>@color</code> actually does something useful.</p>
<p>So given all that, lets see how it works.</p>
<p>The template, style.rcss:</p>
<pre><code>p { color: &lt;%= @color %&gt;; }
</code></pre>
<p>The result, style.css:</p>
<pre><code>p { color: #f77; }
</code></pre>
<p>Just for grins, here's what my log shows for that request:</p>
<pre><code>Processing StylesheetsController#rcss (for 127.0.0.1 at 2006-03-23 12:47:07) [GET]
Parameters: {"action"=&gt;"rcss", "controller"=&gt;"stylesheets", "rcss"=&gt;"style.css"}
Rendering script/../config/../app/views/stylesheets/style.rcss
Completed in 0.00977 (102 reqs/sec) | Rendering: 0.00654 (66%) | DB: 0.00000 (0%) | 200 OK [http://localhost/stylesheets/style.css]
</code></pre>
<p>A slightly ranty footnote: This experience typifies working with Ruby on Rails for me. The solution I eventually figured out took less time to implement than it took me to write this article about doing so. However, it took me several days of investigation to understand how Rails was doing things well enough to figure out the solution. While the Rails source code is the ultimate authority on how things work, digging through those sources for answers can be extremely frustrating. There are hardly any comments in the code itself, and when there are comments they often aren't maintained and say something that is obviously inconsistent with the code itself. If the maintainers of Ruby on Rails want more people using Rails and contributing to it, they need to start commenting the code so that working with it isn't so hard.</p>
<p><strong>UPDATE:</strong>
This article is out of date and needs some tweaking to work with Rails 1.2 and up (see comments below). An updated Rails 2.0 compatible approach is described in <a href="http://blog.hasmanythrough.com/2007/10/18/simpler-than-dirt-restful-dynamic-css">Simpler than dirt: RESTful Dynamic CSS</a>.</p>
<p>Also, Chris Abad has created a <a href="http://agilewebdevelopment.com/plugins/simple_rcss">simple_rcss plugin</a> based on the recipe in this article.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/102006-03-15T10:46:00-08:002008-01-24T00:19:29-08:00DRYing up parameter checksJosh Susser<p>Here is a very simple way to check that the parameters coming into an action are useful. Remember, it's important to guard against both coding errors and people constructing bogus URLs.</p>
<pre><code>class RecipeController &lt; ApplicationController
def recipe
return bogus("Unknown Recipe") unless @recipe = Recipe.find_by_id(params[:id])
end
def bogus(message)
flash[:warning] = message
redirect_to :action =&gt; "home"
end
end
</code></pre>
<p>The cool thing is that <code>find_by_id(params[:id])</code> returns nil if there is no id param, or if there is one but it is invalid. I've been putting this one-liner in all my actions and now I feel much safer.</p><p>Here is a very simple way to check that the parameters coming into an action are useful. Remember, it's important to guard against both coding errors and people constructing bogus URLs.</p>
<pre><code>class RecipeController &lt; ApplicationController
def recipe
return bogus("Unknown Recipe") unless @recipe = Recipe.find_by_id(params[:id])
end
def bogus(message)
flash[:warning] = message
redirect_to :action =&gt; "home"
end
end
</code></pre>
<p>The cool thing is that <code>find_by_id(params[:id])</code> returns nil if there is no id param, or if there is one but it is invalid. I've been putting this one-liner in all my actions and now I feel much safer.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/92006-03-07T17:02:00-08:002008-01-24T00:19:28-08:00Symbol to Proc shorthandJosh Susser<p>Rails 1.1 adds a conversion method <code>Symbol#to_proc</code> that allows specifying a mapping by using a symbol instead of a block. Here's an example:</p>
<pre><code>[1, 2, 3].map(&amp;:to_s)
</code></pre>
<p>is equivalent to</p>
<pre><code>[1, 2, 3].map { |i| i.to_s }
</code></pre>
<p>and of course can also be written</p>
<pre><code>message = :to_s
[1, 2, 3].map(&amp;message)
</code></pre>
<p>Here's what this does.</p>
<pre><code>[1, 2, 3].map(&amp;:to_s) # =&gt; ["1", "2", "3"]
</code></pre>
<p>My first reaction to seeing this was to wonder what the heck that odd ampersand syntax was. I did some digging, but wasn't able to figure it out entirely...</p><p>Rails 1.1 adds a conversion method <code>Symbol#to_proc</code> that allows specifying a mapping by using a symbol instead of a block. Here's an example:</p>
<pre><code>[1, 2, 3].map(&amp;:to_s)
</code></pre>
<p>is equivalent to</p>
<pre><code>[1, 2, 3].map { |i| i.to_s }
</code></pre>
<p>and of course can also be written</p>
<pre><code>message = :to_s
[1, 2, 3].map(&amp;message)
</code></pre>
<p>Here's what this does.</p>
<pre><code>[1, 2, 3].map(&amp;:to_s) # =&gt; ["1", "2", "3"]
</code></pre>
<p>My first reaction to seeing this was to wonder what the heck that odd ampersand syntax was. I did some digging, but wasn't able to figure it out entirely...</p>
<p>The <a href="http://www.pragmaticprogrammer.com/titles/ruby/">Pickaxe book</a> says:</p>
<blockquote>
<p>If the last argument to a method is preceded by an ampersand, Ruby assumes that it is a Proc object. It removes it from the parameter list, converts the Proc object into a block, and associates it with the method.</p>
</blockquote>
<p>In fact it repeats that in several places throughout the book. But the converse notation or preceding an argument with an ampersand in a method call is only implied in one place. In Chapter 23, "Duck Typing", section "Standard Protocols and Coercions", it describes the <code>to_proc</code> coercion as "Used to convert an object prefixed with an ampersand in a method call." (Thanks to Chad Fowler for pointing me in the right direction to find that rather obscure reference.)</p>
<p>Discovering that was one of those moments where I really had to sit back and just admire the Ruby language for a moment. The coercion operation is completely polymorphic! Any object preceded by an ampersand in a method call will be converted to a Proc by sending it the <code>to_proc</code> message. Of course, the object must implement a <code>to_proc</code> method, but Rails 1.1 adds just that to Symbol.</p>
<pre><code>class Symbol
def to_proc
Proc.new { |obj, *args| obj.send(self, *args) }
end
end
</code></pre>
<p>Pretty nifty.</p>
<p>The new syntax for mapping seems to be nicer for chaining calls. Compare:</p>
<pre><code>[1, 2, 3, 4].find_all(&amp;:even?).map(&amp;:to_s)
[1, 2, 3, 4].find_all {|i| i.even?}.map { |i| i.to_s }
</code></pre>
<p>I think I like it.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/62006-03-01T19:23:00-08:002008-01-24T00:19:28-08:00New association goodness in Rails 1.1, part 2Josh Susser<p>In <a href="http://blog.hasmanythrough.com/articles/2006/02/28/association-goodness">part 1</a> I covered the basics of how to set up a <code>has_many :through</code> association and simple queries using the default accessors. In this installment, I'll show how setting up association extensions can make using through associations easier.</p><p>In <a href="http://blog.hasmanythrough.com/articles/2006/02/28/association-goodness">part 1</a> I covered the basics of how to set up a <code>has_many :through</code> association and simple queries using the default accessors. In this installment, I'll show how setting up association extensions can make using through associations easier.</p>
<p>If you recall from last time, I created some model classes to represent books, the people who contributed to them, and the nature of their contribution. Now I'm going to enhance those models with <em>association extensions</em>, one of the niftier features in Rails 1.1.</p>
<p>First I'll add some more information to the join model. I'll include some information about the kind of contribution made to the book. I'm not going to get cute and fake up the migration code here, as that's not the point of this article. Here's the generic schema definition with the new data added.</p>
<pre><code>create_table "contributions" do |t|
t.column "book_id", :integer
t.column "contributor_id", :integer
t.column "royalty", :float
t.column "role", :string
end
</code></pre>
<p>I've added a <code>role</code> field to indicate the kind of contribution. It will hold values like "author", "editor", "illustrator", etc. With that addition we could do queries using dynamic finders, like so:</p>
<pre><code>josh = Contributor.find_by_name("Joshua Susser")
books = josh.books.find(:all, :conditions =&gt; ["contributions.role = ?", "author"])
</code></pre>
<p>If you look at the log you'll see this generates a query that looks like:</p>
<pre><code>SELECT books.* FROM contributions, books
WHERE (books.id = contributions.book_id
AND contributions.contributor_id = 1
AND (contributions.role = 'author'))
</code></pre>
<p>Here I'm taking advantage of the fact that the find on the association includes a join on contributions to bind back to the contributor, so I can use other fields of that table in the query. Nifty, but ugly to type and read. How can we make that better? That's where association extensions come in.</p>
<pre><code>class Contributor &lt; ActiveRecord::Base
has_many :contributions, :dependent =&gt; true
has_many :books, :through =&gt; :contributions do
def by_role(role)
find(:all, :conditions =&gt; ["contributions.role = ?", role])
end
end
end
</code></pre>
<p>This lets us do a query like so:</p>
<pre><code>books = josh.books.by_role("author")
</code></pre>
<p>What goes on in the extension is that finders are bound to the association just as if they were sent to it as in the earlier example. The advantage is they make things easy to read and write, and can encapsulate more business logic into the queries too.</p>
<p>The documentation for extensions provides a nice way to reuse code. Put the extension definition into a module then bind it with this syntax:</p>
<pre><code>has_many :books, :through =&gt; :contributions,
:extend =&gt; BooksExtension
</code></pre>
<p>Here is a trick for using more than one extension module for an association:</p>
<pre><code>has_many :books, :through =&gt; :contributions do
include BooksExtension
include ContributionsExtension
end
</code></pre>
<p>I didn't see that documented anywhere, but I figured it was worth trying and the experiment worked great. And if you're wondering where to put the extension modules, they do just fine in the lib directory.</p>
<p>That's it for association extensions today. Tomorrow (I hope) I'll show how to do a 3-way join and really put extensions through their paces.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/52006-03-01T06:10:00-08:002008-01-24T00:19:28-08:00DreamHost: getting to stats pagesJosh Susser<p>I use DreamHost for my webhosting and so far I've been generally pretty pleased with their package and support. I've yet to try a major Rails app, but I have this Typo blog running and a couple toy apps I built for friends.</p>
<p>Minor annoyance: I discovered that deploying a Rails app on a domain blocks access to the Analog stats pages. I pinged tech support and they came back with this quick fix. Just add the following lines to the <code>.htaccess</code> file, immediately after the <code>RewriteEngine on</code> directive.</p>
<pre><code># enable analog stats pages
RewriteCond %{REQUEST_URI} ^/stats/(.*)$ [OR]
RewriteCond %{REQUEST_URI} ^/failed_auth.html$
RewriteRule ^.*$ - [L]
</code></pre>
<p>Now go to <code>yourdomain.com/stats</code> (you'll need to authenticate) and you can see your site's statistics again. And your Rails app should still work just fine (which is the important part). Wasn't that easy?</p><p>I use DreamHost for my webhosting and so far I've been generally pretty pleased with their package and support. I've yet to try a major Rails app, but I have this Typo blog running and a couple toy apps I built for friends.</p>
<p>Minor annoyance: I discovered that deploying a Rails app on a domain blocks access to the Analog stats pages. I pinged tech support and they came back with this quick fix. Just add the following lines to the <code>.htaccess</code> file, immediately after the <code>RewriteEngine on</code> directive.</p>
<pre><code># enable analog stats pages
RewriteCond %{REQUEST_URI} ^/stats/(.*)$ [OR]
RewriteCond %{REQUEST_URI} ^/failed_auth.html$
RewriteRule ^.*$ - [L]
</code></pre>
<p>Now go to <code>yourdomain.com/stats</code> (you'll need to authenticate) and you can see your site's statistics again. And your Rails app should still work just fine (which is the important part). Wasn't that easy?</p>tag:blog.hasmanythrough.com,2006-02-27:Article/42006-02-28T20:00:00-08:002008-01-24T00:19:28-08:00New association goodness in Rails 1.1Josh Susser<p>It looks like the release of Rails 1.1 is just days away, so I thought I'd share some of what I've learned about some of the new features and how they work together. Given the name of this blog, I'm going to start with the <code>has_many :through</code> association. Just to make things interesting, I'll show how association extensions can make queries on :through associations incredibly easy to use.</p>
<p>This is looking to be a fair amount of material for a blog entry, so I'm going to split it into installments. I think it should take about 3 parts to cover, unless I get sidetracked and start writing about something cool but obscure.</p><p>It looks like the release of Rails 1.1 is just days away, so I thought I'd share some of what I've learned about some of the new features and how they work together. Given the name of this blog, I'm going to start with the <code>has_many :through</code> association. Just to make things interesting, I'll show how association extensions can make queries on :through associations incredibly easy to use.</p>
<p>This is looking to be a fair amount of material for a blog entry, so I'm going to split it into installments. I think it should take about 3 parts to cover, unless I get sidetracked and start writing about something cool but obscure.</p>
<p>The <code>has_many :through</code> association allows you to specify a one-to-many relationship indirectly via an intermediate join table. In fact, you can specify more than one such relationship via the same table, which effectively makes it a replacement for <code>has_and_belongs_to_many</code>. The biggest advantage is that the join table contains full-fledged model objects complete with primary keys and ancillary data. No more <code>push_with_attributes</code>; join models just work the same way all your other ActiveRecord models do.</p>
<p>Let's try an example. The typical example uses Authors and Books, but I'm going to shift that around a bit to show you some new things later on.</p>
<p>First, the tables. Remember <code>create_table</code> creates a primary key named <code>id</code> by default.</p>
<pre><code>create_table "books" do |t|
t.column "title", :string
t.column "isbn", :string
t.column "year", :integer
end
create_table "contributors" do |t|
t.column "name", :string
end
create_table "contributions" do |t|
t.column "book_id", :integer
t.column "contributor_id", :integer
t.column "royalty", :float
end
</code></pre>
<p>Then the model classes...</p>
<pre><code>class Book &lt; ActiveRecord::Base
has_many :contributions, :dependent =&gt; true
has_many :contributors, :through =&gt; :contributions
end
class Contributor &lt; ActiveRecord::Base
has_many :contributions, :dependent =&gt; true
has_many :books, :through =&gt; :contributions
end
class Contribution &lt; ActiveRecord::Base
belongs_to :book
belongs_to :contributor
end
</code></pre>
<p>Using <code>habtm</code>, the join table would have been named <code>books_contributors</code>. Here I've named it <code>contributions</code> as it's now a real model. To prove it's more than just a join table, I've added a <code>royalty</code> field to the contribution to indicate the percentage royalty the contributor will earn from sales of the book. I've also made the contribution dependent on both the book and contributor, as if either book or contributor is deleted there's no point in preserving the object that represents the relationship between them.</p>
<p>Now let's play with this a bit. (Please excuse the shameless use of a book I actually had published. It never sold a darn copy because OpenDoc was cancelled a week before it hit the stores, so this is the most use I'll ever get out of having worked on it.)</p>
<pre><code>bgod = Book.create(:title =&gt; "BYTE Guide to OpenDoc",
:isbn =&gt; "0078821185", :year =&gt; 1996)
drew = Contributor.create(:name =&gt; "Andrew MacBride")
josh = Contributor.create(:name =&gt; "Joshua Susser")
Contribution.create(:book =&gt; bgod, :contributor =&gt; drew,
:royalty =&gt; 0.20)
Contribution.create(:book =&gt; bgod, :contributor =&gt; josh,
:royalty =&gt; 0.10)
</code></pre>
<p>Time to do some queries. Notice we can use the <code>:through</code> association to get right at the data on the other side, just as if it was an <code>habtm</code> association.</p>
<pre><code>Contributor.find(1).books.first.year # =&gt; 1996
Book.find_by_year(1996).contributors.first.name # =&gt; "Andrew MacBride"
</code></pre>
<p>But we can also get at data stored in the join model.</p>
<pre><code>bgod.contributions.size # =&gt; 2
drew.contributions.first.royalty # =&gt; 0.20
</code></pre>
<p>And more interestingly...</p>
<pre><code>josh.books.find_by_year(1996).title # =&gt; "BYTE Guide to OpenDoc"
josh.contributions.find_by_book_id(bgod.id).royalty # =&gt; 0.10
</code></pre>
<p>That last expression found the join model object for the contribution between contributor <code>josh</code> and book <code>bgod</code>, then retrieved the royalty amount from that. The next logical thing to expect would be this:</p>
<pre><code>josh.contributions.find_by_book_id(bgod.id).royalty = 0.30
</code></pre>
<p>However this is either a bug or a design limitation, and you can't write to fields obtained by chained accessors and finders. I'm not sure what the actual problem is but I've seen it mentioned a few times on various blogs and mailing lists. But at least you can still do this:</p>
<pre><code>author = josh.contributions.find_by_book_id(bgod.id)
author.royalty = 0.30
</code></pre>
<p>Not ideal, but at least it works.</p>
<p>That's enough about <code>has_many :through</code> associations for this installment. Next time I'll show you how to make pretty queries using association extensions.</p>
<p>I hope this has been interesting or useful information for someone. I'm still learning how these new features work, so please let me know if you find I've got something wrong.</p>tag:blog.hasmanythrough.com,2006-02-27:Article/22006-02-28T09:24:00-08:002008-01-24T00:19:28-08:00test modified rake taskJosh Susser<p>When I took the wonderful Pragmatic Rails Studio in January, Dave Thomas described the <code>rake recent</code> task that runs tests for all app files that have been modified in the last 10 minutes. I asked Dave if there was a rake task for running tests on all files that had been modified since last subversion update/commit. He said I should write one, so I did.</p>
<p>You can grab it from the goodies collection: <a href="http://hasmanythrough.com/goodies/test_modified.rake">test_modified.rake</a>. Let me know if it has any issues.</p><p>When I took the wonderful Pragmatic Rails Studio in January, Dave Thomas described the <code>rake recent</code> task that runs tests for all app files that have been modified in the last 10 minutes. I asked Dave if there was a rake task for running tests on all files that had been modified since last subversion update/commit. He said I should write one, so I did.</p>
<p>You can grab it from the goodies collection: <a href="http://hasmanythrough.com/goodies/test_modified.rake">test_modified.rake</a>. Let me know if it has any issues.</p>