Close and Go BackBack to Viget

Ruby on Rails

Rails, Internationalization, and Tú

Clinton R. Nixon
Clinton R. Nixon, Development Director, July 02, 2009 2

Internationalization (or I18n if you’re hep) is a bear of a problem to deal with in software development. I’ve had to work with a multi-lingual site in PHP before. It wasn’t painful, but it was constantly annoying. I just got a chance to work with internationalization in Rails for the first time, and I was pretty excited to see how it’s been handled.

The good news that I found is that Rails’ I18n support is pretty great. The bad news is that it is overly integrated with the rest of Rails, making changing it in isolation difficult.

Continue reading "Rails, Internationalization, and Tú"

Rack Support in Rails: Why It Matters

Ben Scofield
Ben Scofield, Technology Director, May 19, 2009 2

As I’ve mentioned elsewhere, my RailsConf session on Rack support in Rails went very well. I’m not planning on giving this talk again, however, so I thought I’d write a quick summary of it for people who are interested in a little more detail than the slides can provide.

My main motivation in giving this talk was to call attention to what I feel has been the most important change in Rails over the past year – its move to become Rack-compatible. The initial work on this was done by Ezra almost a year ago, but it only made it into a stable release with 2.3. This delay meant that it stayed a bit under the radar, and even the addition of Rails Metal didn’t really pull it into the spotlight. Then, in December of 2008, the announcement of the Merb-Rails merger pretty well decided the story of the year for Rails.

So What?

So, why is Rack support so important? Why should it be recognized as more vital to the success of Rails than the merger of the two most popular Ruby web frameworks? Rack is just a common API for Ruby web frameworks, after all, and as such isn’t as flashy as, say, Merb’s router.

The answer is just that Rack is a common API for Ruby web frameworks. It’s essentially the Ruby version of Python’s WSGI, specifiying a simple interface for handling HTTP requests in Ruby. The simplest Rack-compatible web application would look something like this:

class SimpleRackApp
  def call(env)
    [
      
      status,  # 200
      headers, # {"Content-Type" => "text/html"}
 
      body     # ["..."]

    ]

  end
end

To be Rack-compatible, an application need only have a call method that accepts a hash of environment data (including, for instance, the HTTP headers included in the request) and returns an array of an HTTP status, a hash of headers, and a body that responds to .each.

Standardizing the interface for web applications means that you can link them together in interesting and useful ways – leading us to Rack middleware. Middlewares are just Rack-compatible applications that intercept and manipulate a request or response; a middleware could, for example, rescue any unhandled exceptions from your application and display a friendly error page.

Rack in Rails

As one of the results of the integration of Rack into Rails, many of the functions of ActionController have been refactored into middlewares, which means they can be required into non-Rails applications and used. As of today, these middlewares live in the ActionDispatch module, and include classes like ActionDispatch::Failsafe, ActionDispatch::ShowExceptions, and ActionDispatch::ParamsParser (among others). Normally, these will be automatically required when you run your Rails application through script/server or Passenger, but you can control their inclusion or replace them entirely with environment.rb; see the Rack guide for more details.

Rails Metal is another of the results of the move to Rack. Metal provides a way to bypass some of the normal overhead of the Rails stack, allowing a developer to specify certain routes and code to execute when those routes are hit. Metal actions avoid the entirety of ActionController, and so are somewhat faster than standard Rails actions (though this performance benefit may not always be significant).

Tools and Techniques

All of this is just background, though; the real argument in favor of the importance of Rack is found when you look at what you can now do. In the session at RailsConf, I presented two such examples: Rack::Bug and progressive caching. Rack::Bug is a middleware, and some great things have been written on it recently. Progressive caching is a technique that uses Rails Metal to respond to AJAX requests, making page caching a more generally useful strategy (it is also one of the other talks I give, and I’ve written on it in a couple of places).

During the final part of the section, I showed how to write a couple of middlewares that might be generally useful: Rack::Embiggener and Rack::Hoptoad. The latter is obviously a port of the hoptoad_notifier plugin to a Rack middleware (and indeed, this is a direction that the thoughtbot team is considering – something I’m really looking forward to!). The former, however, may be a more interesting example. Rack::Embiggener is a middleware that intercepts the response from a Rack application, detects any TinyURLs embedded in it, and replaces them with the expanded URL. An obvious use-case for this is Twitter, where tweets could be stored with TinyURLs in place (remaining under the 140 character limit), while they are displayed with the full URL visible regardless of the length1.

module Rack
  class Embiggener
    def initialize(app)
      @app = app
    end
    
    def call(env)
      status, headers, body = @app.call(env)
      headers.delete('Content-Length')
    
      response = Rack::Response.new(
        Rack::Embiggener.embiggen_urls(body),
        status,
        headers
      )
    
      response.finish
      return response.to_a
    end
        
    def self.embiggen_urls(original_body)

     new_body = []
      
      original_body.each { |line|
        tinys = line.scan(/(http:\/\/(?:www\.)?tinyurl\.com\/(.{6}))/)
  
     new_body << tinys.uniq.inject(line) do |body, tiny|
    
     original_tiny = tiny[0]
    
     preview_url = "http://preview.tinyurl.com/#{tiny[1]}"
    
     response = Net::HTTP.get_response(URI.parse(preview_url))
    
     embiggened_url = response.body.match(/redirecturl" href="(.*)">/)[1]
    

    body.gsub(original_tiny, embiggened_url)
  
     end

     }

     
      new_body

   end
  end

end

Walking through the code:

  • #initialize stores the Rack application object in a variable for later use.
  • #call passes the request to the application stored in @app, and records its responses. It then deletes the Content-Length header (since we’ll be enlarging some URLs), and creates a new Rack response with the same information, passing the original body through the embiggen_urls method. The response.finish call ensures that the appropriate headers (including a new Content-Length) are set.
  • embiggen_urls accepts a body (that, per the Rack specification, responds to .each), and (inefficiently) scans over it to find and replace the TinyURLs2.

With this middleware, then, any Rack-compatible application – be it Twitter, some other Rails 2.3 application, a Sinatra microapp, or even something written in Camping – can replace TinyURLs with their expansions.

1 More interestingly, Twitter or another service might be able to replace the full ‘http:/tinyurl.com/[foo]’ with just ‘[foo]’ and a single-character flag of some sort, providing even more storage savings.

2 This code was just meant to inspire people by showing something that could be done, and isn’t meant to be particularly efficient or good. Feel free to fork and improve it!

Wrapup

One of my favorite sayings comes from Socrates: “A list is not a definition.” My presentation (and this post) argue for the importance of Rack in Rails, but all they really do is give a list of examples of useful tools and techniques. My final point, then, is this: Rack compliance makes Rails a first-class member of the community of Ruby web frameworks (contrast that with Django, which implements a non-standard version of WSGI). This means that work done for Rails, such as the refactoring of parts of ActionController into ActionDispatch’s middlewares, benefits both Rails developers and users of other frameworks, and vice versa. Rack unites the fragmented Ruby web framework ecosystem, which means that no matter the community in which we work, our efforts improve the whole rather than just a single piece. As we’ve seen in open source time and again, the more people working on a set of problems, the better.

Pain-Free Pretty URLs in Rails

Brian Landau
Brian Landau, Web Developer, April 07, 2009 5

When working recently on a client project in a later stage of development, it became clear that the client wanted “pretty” URLs for specific models in the application. In particular, they wanted some URLs without an id, meaning the normal Rails to_param trick was out:

def to_param
  "#{self.id}-#{self.name.parameterize}"
end

We needed this type of URL on the User model. We already had a field that was both unique and “URL safe,” the login field, so for this model we could just do

def to_param
  self.login
end

On another model, I decided to create a slug field just for the purpose of pretty URLs. There was already a name field that had to be unique, so I just created a before_validation method to handle the creation and updating of the slug:

before_validation :update_or_create_slug

def to_param
  slug
end

private
def update_or_create_slug
  if self.new_record? || self.name_changed? || self.slug.blank?
    self.slug = self.name.try(:slugize)
  end
end

I used my own “slugize” method, as it’s a little different from the Rails built-in parameterize, and I believe it offers a few minor advantages. I also check for three conditions where we need to assign a new slug:

  1. If it's on a new record.
  2. If the name has changed.
  3. If the slug currently happens to be blank.

You’ll also notice I use the “.try(:slugize)” in case the model is invalid and has nil for the name.

With all this in place, you’d think we’d be all set -- or at least I did.

There was one problem left, though. Let’s say you have a user who is editing an existing model object in the system, and they try to update it with a blank name. This will result in a user with a blank slug field. This wouldn’t seem too bad, but it results in the edit page not being able to render because to_param returns a blank string to the named route URL helper on the form, which raises an ActionController::RoutingError exception.

The solution is to return the old slug value for to_param:

def to_param
  slug_changed? ? slug_was : slug
end

With that solved, we’re all set to go with pretty, id-free URLs.

If you’re in a situation where you don’t have a required unique field like we did on this project, you might want to look at Patrick’s solution from FeedStitch.

Conditionally Customize Your URLs in Rails

Patrick Reagan
Patrick Reagan, Development Director, March 31, 2009 2

Our primary goal when building FeedStitch was to encourage users to share their feeds by making the process both simple and easily customizable. In the interest of simplicity, we went with OpenID (using RPX through the rpx_now plugin) to combine the registration and login processes so that users could quickly start creating feeds.

As part of encouraging sharing, we wanted to allow users to customize the URL that they could give out to others. The challenge here was keeping this functionality from interrupting the sign-in process while understanding that users may opt out of the customization step altogether.

We implemented this using a quick to_param trick - here's how you can do the same in your Rails application.

Continue reading "Conditionally Customize Your URLs in Rails"

Rails Nested has_many :through With SQL Views

Tony Pitale
Tony Pitale, Web Developer, February 17, 2009 2

Discovery

At a recent code review for a Pointless Corp project, FeedStitch, we came across this:

  has_many :group_feeds, :dependent => :destroy
  has_many :feeds, :through => :group_feeds

  has_many :entries, 
    :finder_sql =>  "SELECT * 
                    FROM entries
                    WHERE feed_id IN
                    (SELECT feed_id
                    FROM group_feeds
                    WHERE group_id=#{id})
                    ORDER BY published_at DESC",
    :counter_sql => "SELECT COUNT(*) FROM ...

Seeing that much SQL, and duplicated, we took a moment to diagram and discuss possible improvements. In the example above we were attempting to get at Entry through Feed which was already associated via the group_feeds.

At some point I suggested using an SQL view after the discussion had led us through creating a new table to de-normalize the link between a group and an entry. The initial reaction from the group was no. I chose to press the issue because I felt the table that would be created in de-normalizing would require management by the application. On the other hand, a view would appear the same to the application while being managed by a simple SQL select. After some discussion, we chose to give views a chance as an alternative solution.

How We Did It

Generate a migration. For our application it looks like this:

  class CreateGroupEntriesView < ActiveRecord::Migration
    def self.up
      sql = <<-EOS
        CREATE VIEW group_entries AS
        SELECT group_feeds.group_id, entries.id AS entry_id
        FROM group_feeds, entries
        WHERE group_feeds.feed_id = entries.feed_id
      EOS
    
      execute sql
    end

    def self.down
      sql = "DROP VIEW group_entries"
      execute sql
    end
  end

Update Note: I include the full SQL here to make the usage a bit more clear - our project is using the rails_sql_views plugin to aid in generating the database view. If you're writing tests for your application (and you should!), this plugin is required to create a valid  schema.rb file.

Finally, in our associated model classes we set up the has_many :through as if group_entries was a full-fledged table.

  has_many :group_entries
  has_many :entries, :through => :group_entries

Why NOT to Use Views

If your database schema changes, the fear is that the SQL defining the view would need to change; but, the developer or application would be left completely unawares. In addition, the temptation exists to create a view that collects a large amount of fields from a variety of tables to save the time of having to actually build associations. Be warned! Views are not replacements for proper relationships and good database design!

Finale

The specific circumstances surrounding this application lend themselves to a very simple use for a view. I feel this simple solution is appropriate for this type of problem. Please comment with your thoughts, especially on your solutions to similar problems or if you've used views with Rails before.

Check out the Pointless Corp introduction of FeedStitch, or get right into the thick of it on FeedStitch!

We're the Developers

at Viget Labs. We write about web development trends, tips, best practices, industry events, and our projects — all with an emphasis on Ruby on Rails.

Recent Comments

For translating strings you can use Rails I18n backend instead of using inflectors.

The `typus_human_name` is a patch to fix a problem in `human_name` [1].

[1] https://rails.lighthouseapp.com/projects/8994/tickets/2120-humanize-and-human_name-dont-separate-words

Contact Us

Have any questions, comments, ideas, or secrets to share? Let us know.


Sorry, you need to have Javascript enabled to use this form. (Don't blame us, blame the spammers!) If you'd like to contact us, please visit our Contact page.