Skip to content

Commit

Permalink
Fixed and suggestion for Improving your App chapter
Browse files Browse the repository at this point in the history
  • Loading branch information
erebor committed Feb 18, 2010
1 parent 3065456 commit f00e164
Showing 1 changed file with 22 additions and 20 deletions.
42 changes: 22 additions & 20 deletions text/04_Improving/01_Improving.mdown
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

## Improving your application with Rails 3

A lot of the refactoring and new few features in Rails can contribute to improving your codebase significantly if you take advantage of them. In this section, we'll take a look at some of those features and changes and how to put them to use in your code today.
A lot of the refactoring and new features in Rails can help you significantly improve your codebase if you take advantage of them. In this section, we'll take a look at some of those features and changes and how to put them to use in your code today.

### Cleaning up controllers

Expand Down Expand Up @@ -44,7 +44,7 @@ Essentially, it takes all the conventional REST logic and wraps it up in a nice
# Your REST methods here...
end

The `respond_to` method will accept any format you've defined a formatter for (defaults are `:html`, `:xml`, and `:json`). Using this mechanism, you can cut about 50% of the code out of most RESTful controllers without sacrificing any of the control you'd have (unlike some of the plugins, which make you sacrifice some control over the logic flow). To override any of the default behavior, simply provide a block to the `respond_with` call and add your overriding behavior. For example, let's say you wanted to change the behavior when a user requests HTML:
The `respond_to` method will accept any format for which you've defined a formatter (defaults are `:html`, `:xml`, and `:json`). Using this mechanism, you can cut about 50% of the code out of most RESTful controllers without sacrificing any of the control you'd have (unlike some of the plugins that do something similar, which make you sacrifice some control over the logic flow). To override any of the default behavior, simply provide a block to the `respond_with` call and add your overriding behavior. For example, let's say you wanted to change the behavior when a user requests HTML:

def create
@user = User.new(params[:user])
Expand Down Expand Up @@ -100,7 +100,7 @@ Now any markup returned from the `join_and_bold` method will be marked as raw.

#### Better JavaScript with Rails 3

As mentioned previously, the JavaScript helpers in Rails 3 have been totally rebuilt to facilitate a framework agnostic approach. This change makes it dead easy to switch out your JavaScript frameworks without impacting your existing helper-driven code. Unfortunately, though, this rewrite means that existing code that uses the JavaScript helpers is out of luck.
As mentioned previously, the JavaScript helpers in Rails 3 have been totally rebuilt to facilitate a framework-agnostic approach. This change makes it dead easy to switch out your JavaScript frameworks without impacting your existing helper-driven code. Unfortunately, though, this rewrite means that existing code that uses the JavaScript helpers is out of luck.

> **PROTIP:** You're not *totally* out of luck. The Rails team have extracted the previous Prototype helpers into their own plugin available at http://github.com/rails/prototype\_legacy\_helper
Expand All @@ -118,9 +118,11 @@ Now looks like this in Rails 3:

Essentially, AJAX forms use the same API as non-AJAX forms except for the extra `:remote => true` parameter.

Not only is the API different, but their operation is, too. Previously, a helper like `remote_form_for` would have emitted a `<form>` tag with some JavaScript attached to it. This technique was not only bad practice by modern standards, it also tied it to one JavaScript framework unless you wanted to write a plugin to support another one, like jRails did with jQuery. This process was annoying and, again, wasn't good practice.
TODO : Wait, should the second code example be `form_for(@user, :remote => true)` ? TODO

Not only is the API different, but its operation is, too. Previously, a helper like `remote_form_for` would have emitted a `<form>` tag with some JavaScript attached to it. This technique was not only bad practice by modern standards, it also tied it to one JavaScript framework unless you wanted to write a plugin to support another one, like jRails did with jQuery. This process was annoying and, again, wasn't good practice.

So now these helpers emit HTML 5 markup with special data attributes which the framework drivers pick up on. For example, a simple `form_for` with `:remote => true` now emits this:
So now these helpers emit HTML 5 markup with special data attributes which the framework drivers detect. For example, a simple `form_for` with `:remote => true` now emits this:

<form action="/things" class="new_thing" data-remote="true" id="new_thing" method="post">

Expand All @@ -129,11 +131,11 @@ The `data-remote` attribute tells the JavaScript driver that this form is an AJA
![Unobtrusive JavaScript](js.png)
{.img}

If you have calls to `link_to_function` or the other helpers, you'd be better served by turning those into proper JavaScript powered links, writing custom JavaScript to power links sort of like the new Rails helpers do, or downloading the aforementioned plugin to carry you over.
If you have calls to `link_to_function` or the other helpers, you'd be better served by turning those into proper JavaScript-powered links, writing custom JavaScript to power links sort of like the new Rails helpers do, or downloading the aforementioned plugin to carry you over.

### Building better routes

As you saw in Section 3.2.1, the Rails router DSL has changed significantly, and as part of the refactoring, the Rails core team has also added a number of new features. One of the best new router features that Rails 3 brings is optional segments; this means that you now have control over what route segments are not required to match the route (whereas before they were hardcoded names like `id`). So, for example, let's say you had an auction site with items that are categorized in categories and subcategories. You might have routes like this in Rails 2.x:
As you saw in Section 3.2.1, the Rails router DSL has changed significantly, and as part of the refactoring, the Rails core team has also added a number of new features. One of the best new router features in Rails 3 is optional segments; this means that you now have control over what route segments are not required to match the route (whereas before they were hardcoded names like `id`). So, for example, let's say you had an auction site with items that are categorized in categories and subcategories. You might have routes like this in Rails 2.x:

map.connect ':category/items', :controller => 'items', :action => 'index'
map.connect ':category/items/:subcategory', :controller => 'items', :action => 'index'
Expand All @@ -155,23 +157,23 @@ In this route, the `action` and `id` segments are optional; if they are not give

#### Routing to Rack applications

The new router is yet another example of Rails commitment to Rack. My favorite new feature of the router is the ability to map routes to Rack endpoints other than your main application. So, for example, if you had an extra little Sinatra*http://sinatrarb.com*{.fn} application to handle simple API calls, you would have previously had to run the app in a separate process. This setup is nice for scalability, but it makes maintenance a pain and requires more infrastructure than necessary in most cases.
The new router is yet another example of Rails' commitment to Rack. My favorite new feature of the router is the ability to map routes to Rack endpoints other than your main application. So, for example, if you had an extra little Sinatra*http://sinatrarb.com*{.fn} application to handle simple API calls, you would previously have had to run the app in a separate process. This setup is nice for scalability, but it makes maintenance a pain and requires more infrastructure than necessary in most cases.

In Rails 3, though, you can route directly to these extra Rack applications through the router. Let's say you have a Rack application class named `ApiApplication`; if you wanted to route any requests to `api/*` to that application, you would write a route like the following:
In Rails 3, though, you can route directly to these extra Rack applications through the router. Let's say you have a Rack application class named `ApiApplication`. If you wanted to route any requests to `api/*` to that application, you would write a route like the following:

YourApp::Application.routes do
match "/api/:action", :to => ApiApplication
end

You could then have a Sinatra or bare Rack app that would respond to that route; this is an extremely powerful tool for building service based applications that are easily scalable. Building it in this manner, you could easily bust the pieces of the application out on to other servers, making your application massively scalable.
You could then have a Sinatra or bare Rack app that would respond to that route. This is an extremely powerful tool for building service-based applications that are easily scalable. Building it in this manner, you could easily bust the pieces of the application out onto other servers, making your application massively scalable.

### Improving your model logic

Models also got a nice boost in features from their refactoring; the addition of Active Relation's power opens up a wide world of possibilities.

#### Better query composition

A common problem for developers who build apps that use a database is programmatically composing SQL queries intelligently. It's easy to naively slap some SQL together, but as your schema and domain get more complex, this sorts of solutions fall down. Active Record's old API made it fairly easy to compose queries, but if you needed to apply a lot of complex logic to them, it got sticky fast.
A common problem for developers who build apps that use a database is programmatically composing SQL queries intelligently. It's easy to naively slap some SQL together, but as your schema and domain get more complex, this sort of solutions fall down. Active Record's old API made it fairly easy to compose queries, but if you needed to apply a lot of complex logic to them, it got sticky fast.

Fortunately the new API alleviates a lot of these problems. For example, let's say you were working on a tumblog and had this `Post` model:

Expand All @@ -188,7 +190,7 @@ Fortunately the new API alleviates a lot of these problems. For example, let's
belongs_to :user
end

Let's you needed a filtering mechanism that allowed you to filter this tumblog's posts based on what the content of post was (e.g., quote, picture, text, etc.), who wrote it, and whether it has any comments. First, you'd create a form that would pass something like the following to the controller parameters:
Let's say you needed a filtering mechanism that allowed you to filter this tumblog's posts based on the content of the post (e.g., quote, picture, text, etc.), who wrote it, and whether it has any comments. First, you'd create a form that would pass something like the following to the controller parameters:

{
:filter => {
Expand All @@ -198,7 +200,7 @@ Let's you needed a filtering mechanism that allowed you to filter this tumblog's
}
}

The typical pattern in Rails 2.x would be to assemble a conditions hash based off of this then pass it back to the model to find what you need. The problem with that is that if you need to further refine the query at all, it was difficult. The addition of named scopes in 2.3 made this considerably easier, and since Active Relation works off the same ideas, it's even easier to add this sort of logic all the way around.
The typical pattern in Rails 2.x would be to assemble a conditions hash based off of this, then pass it back to the model to find what you need. But that made it difficult to refine the query any further if you needed it. The addition of named scopes in 2.3 made this considerably easier, and since Active Relation works off the same ideas, it's even easier to add this sort of logic all the way around.

So let's add a `filtered_relation` method to our model:

Expand Down Expand Up @@ -297,9 +299,9 @@ So if we're given a `value` of `true`, then filter the records based on whether
end
end

Let's break down this code a little. First, we check the value and act if it's `true`, return the relation untouched if it's `false`. Then we tell Active Record that when the query is executed, it should preload associated comments, then select our specific data (basically adding the comment count as an alias), from these tables (we have to add `comments`), and give us records having these attributes (we use having here since we're using an alias, which requires a postselect comparison). If you `rake` now, all your tests should be passing.
Let's break down this code a little. First, we check the value and return the relation untouched if it's `false`. If `value` is `true`, then we tell Active Record to preload associated comments when the query is executed. Then we select the specific data we want (basically adding the comment count as an alias) from these tables (we have to add `comments`), asking for only records having these attributes (we use `having` here since we're using an alias, which requires a postselect comparison). If you `rake` now, all your tests should be passing.

This is great, but the real power in this approach is being able to chain even more things on to the returned relation. So, write some tests to make sure that works:
This is great, but the real power in this approach is being able to chain even more things onto the returned relation. So, write some tests to make sure that works:

test "given a content and comment filter, gives us filtered records" do
@base.update_attribute(:content, "picture")
Expand Down Expand Up @@ -339,7 +341,7 @@ Validations also received a nice little lift in Rails 3. The old API is still a
validates :login, :presence => true, :length => {:minimum => 4},
:uniqueness => true, :format => { :with => /[A-Za-z0-9]+/ }

This new form is excellent since you can compress what would have previously been 4 lines of code into 1, making it dead simple to see all the validations related to a single attribute all in one place. The valid keys/value types for this form are:
This new form is excellent, since you can compress what would have previously been 4 lines of code into 1, making it dead simple to see all the validations related to a single attribute, all in one place. The valid keys/value types for this form are:

* `:presence => true`
* `:uniqueness => true`
Expand All @@ -351,7 +353,7 @@ This new form is excellent since you can compress what would have previously bee
* `:acceptance => true`
* `:confirmation => true`

As I mentioned previously, you can still use the old API, but it makes sense to switch to this form since, when scanning your code, you're rarely looking for what sort of validation it is rather than the attribute that's being validated.
Even though you can still use the old API, it makes sense to switch to this form since, when scanning your code, you're rarely looking for what sort of validation it is rather than the attribute that's being validated.

Another great new validation feature is the ability to have a custom validation class. It's fairly common for Rails developers to develop their own validation methods that look something like this:

Expand All @@ -363,7 +365,7 @@ Another great new validation feature is the ability to have a custom validation
end
end

These methods are really useful, especially if you use this validation in a lot of different classes, but they often add a bit of ugly code. Fortunately, in Rails a lot of that nastiness can go away. Those old methods should still work, but you could make them look like this instead:
These methods are really useful, especially if you use this validation in a lot of different classes, but they often add a bit of ugly code. Fortunately, in Rails 3 a lot of that nastiness can go away. Those old methods should still work, but you could make them look like this instead:

class ProperCategoryValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
Expand All @@ -373,7 +375,7 @@ These methods are really useful, especially if you use this validation in a lot
end
end

Basically, create a class that inherits from `ActiveModel::EachValidator` and implements a `validate_each` method; inheriting from this class will make it available to all Active Record classes. Not only is the code a bit cleaner, it also makes these validations easily testable without much hassle, and you can also integrate them into the short form validations like this:
Basically, create a class that inherits from `ActiveModel::EachValidator` and implements a `validate_each` method; inheriting from this class will make it available to all Active Record classes. Not only is the code a bit cleaner, it also makes these validations easily testable without much hassle. Best of all, you can also integrate them into the short-form validations like this:

validate :category_id, :proper_category => true

Expand Down Expand Up @@ -410,7 +412,7 @@ This pattern is nice for wrapping up a lot of unruly validation code, but a more

### Building better data classes

When working with data classes other than database models (e.g., building objects based on API data for some remote service), building validation and attribute tracking logic can be a pain (especially if you end up doing it over and over again). Rails 3 extracts much of this sort of logic from Active Record into the new Active Model module, which you can include in your own data classes.
When working with data classes other than database models (e.g., building objects based on API data for some remote service), building validation and attribute tracking logic can be a pain (especially if you end up doing it over and over again). Rails 3 extracts much of this sort of logic from Active Record into the new Active Model module (TODO : is this actually the ActiveModel::Validations module? Or are you just using a subset of the capability to which you're referring here in the example? TODO ), which you can include in your own data classes.

So, for example, let's say you had a data class that represented a simple microblog (e.g., Twitter) message:

Expand Down

0 comments on commit f00e164

Please sign in to comment.