Posts tagged as:

Rails Metal

Last month, I posted 9 Ways to Use Rails Metal. This is the seventh way to use Rails Metal.

A week ago, I posted Ruby on Rails: Polymorphic Associations with Mixin Modules which included an example of tracking impressions on different objects.

[click to continue…]

{ 0 comments }

Serving static pages with Rails Metal is actually very simple. Here are the assumptions we’re making.

  • Each static page’s content is made up of valid HTML.
  • Each static page has a path and content stored in a StaticPage object as defined by the StaticPage model.
  • If the path browsed matches the path in a StaticPage object, the content is what is to be delivered.

Here’s the code: [click to continue…]

{ 2 comments }

A few weeks ago, I wrote 9 Ways to Use Rails Metal. The third way to use Rails Metal was implementing a simple API.

Before I provide the code and an explanation, I’d like to cover a few things. First, this API only requires an API key. If you want an authentication token or some other identifier, look at Rails Metal Example #1: Authentication for some ideas of how to manage authentication for your API. Second, I didn’t filter any contents on the User model, so the password is passed back by the API. A simple delete call for the keys you want to omit from the hash returned by the find_by_sql call should clear up any data you don’t want to pass back.

Here’s the code:

# Allow the metal piece to run in isolation
require(File.dirname(__FILE__) + "/../../config/environment") unless defined?(Rails)

class ApiHandler
  def self.call(env)
    req = Rack::Request.new(env)
    @params = req.params
    if (env["PATH_INFO"] =~ (/^/api/(xml|json)/user//)) && ApiKey.find_by_key(@params["key"]) && env["REQUEST_METHOD"] == "POST"
      format = env["PATH_INFO"].split("/")[2]
      return [200, {"Content-Type" => "application/xml"}, [User.find_by_sql(["SELECT * FROM users WHERE id = ?", @params["id"]]).to_xml]] if format == "xml"
      [200, {"Content-Type" => "application/json"}, [User.find_by_sql(["SELECT * FROM users WHERE id = ?", @params["id"]]).to_json]] if format == "json"
    else
      [404, {"Content-Type" => "text/html"}, ["Not Found"]]
    end
  end
end

The code here is pretty simple. You check the path to make sure it matches the api path for XML or JSON, then find the record, convert it, and send it off to the user.

A few things to note are that the Rails methods to_json and to_xml are available in Rails Metal. I also had to explicitly return on the xml format because it isn’t the last execution and therefore isn’t returned.

{ 1 comment }

A week and a half ago, I posted 9 Ways to Use Rails Metal. The fourth way I listed was “Redirecting Affiliate Links.” The basic idea is that you can set up http://mydomain.com/hosting to go to the link you were given by the hosting company you have an affiliate account with.

The first thing I did was set up a model to manage affiliate redirects.

script/generate model affiliate_redirect path:string location:string

Then I ran my migrations, to set up the database.

rake db:migrate

Then I set up a Rails Metal instance called affiliate_redirects.

script/generate metal affiliate_redirects

From there, programming Rails Metal was pretty simple. You just have to find an AffiliateRedirect with the path visited and redirect to the AffiliateRedirect’s location. Here’s the code:

require(File.dirname(__FILE__) + "/../../config/environment") unless defined?(Rails)

class AffiliateRedirects
  def self.call(env)
    redirect = AffiliateRedirect.connection.select_all("SELECT * FROM affiliate_redirects WHERE path = '#{env["PATH_INFO"]}'").first
    if redirect && (location = redirect["location"])
      [302, {"Content-Type" => "text/html", "Location" => location}, ["You are being redirected."]]
    else
      [404, {"Content-Type" => "text/html"}, ["Not Found"]]
    end
  end
end

This is the quick solution to the Rails action with nothing but a redirect_to.

{ 0 comments }

I’ve had a few requests on how to access the session from Rack and Rails Metal. In the Rack environment that is passed to the call method, the session is stored at the ‘rack.session’ index. You can use this to both read from and write to the session. Here are some examples:

session = env['rack.session']
User.find_by_id(session["user_id"])
session = env['rack.session']
session["user_id"] = 1

{ 0 comments }

Last week, I wrote a post listing 9 ways to use rails metal. This is an explanation of the first way to use Rails Metal: Check Authentication.

We’re setting up this Rails Metal to handle two scenarios: requiring authentication, and logging the user in. First, it verifies that requests to any path beginning with /admin have a user logged in by checking that a valid user id is stored in the session. Second, accepts an HTTP POST to /authenticate with a username, password, and target path for redirect upon

Before we get too deep into things, let me share the code. You can check out a working version of the application on GitHub.

Here’s the Metal.

require(File.dirname(__FILE__) + "/../../config/environment") unless defined?(Rails)

class Authentication
  def self.call(env)
    session = env["rack.session"]
    request = Rack::Request.new(env)
    params = req.params
    if env["PATH_INFO"] =~ /^/admin.*/ && (session["user_id"].nil? || (session["user_id"] && User.connection.select_all("SELECT * FROM users WHERE id = #{session["user_id"]}").empty?))
      [302, {"Content-Type" => "text/html", "Location" => "/login?target_path=#{env["REQUEST_URI"]}"}, ["You must be logged in to view this page. You are being redirected."]]
    elsif env["REQUEST_METHOD"] == "POST" && env["PATH_INFO"] =~ /^/authenticate/
      users = User.connection.select_all("SELECT * FROM users WHERE username='#{params["user"]["username"]}' AND password='#{params["user"]["password"]}'")
      unless users.empty?
        session["user_id"] = users.first["id"]
        [302, {"Content-Type" => "text/html", "Location" => "#{params["target_path"] || "/"}"}, ["You have been logged in. You are being redirected."]]
      else
        [302, {"Content-Type" => "text/html", "Location" => "/login"}, ["Authentication failed. You are being redirected."]]
      end
    else
      [404, {"Content-Type" => "text/html"}, ["Not Found"]]
    end
  end
end

Bases for Understanding the Code

There are a few methods and structures here that are important to note in how this all works.

First, the self.call(env) is the entry point for the Rails Metal application. env refers to the Rack environment.

Next, Rack::Request is a class that provides a convenient interface to the Rack environment. In this case, we’re using it to extract the GET and POST parameters from the request. You can see the full Rack::Request API here.

env[‘rack.session’] returns the session as a hash. In this case, keys are accessed as strings, not symbols. So, in our case, session[:user_id] won’t work, but session[“user_id”] will work.

Finally, we do direct SQL calls through the ActiveRecord object rather than using the find method because it’s faster and we only need a few fields like the id of the user that’s found.

Overview of the Code

Checking Authentication

Our authentication code creates a session variable called user_id and places the current user’s id into it. So, to make sure someone is logged in, we need to make sure that a valid user id is in the session.

To do this we check if the path begins with /admin. env[“PATH_INFO”] contains the path without any GET parameters. So, we do a regular expression match and them move on to checking if there’s a valid user id in the session.

There are two scenarios that we need to check. There is no user id stored in the session and the user id stored is not the id of a valid user.

If both conditions are met, we redirect the user to /login and pass along the path the user was trying to access as a GET parameter called “target_path.”

Logging the user in

If a valid username and password are passed to the path /authenticate, we need to store the user’s id in the session and redirect them to where they were trying to go. To check the username and password, we query the database. If no matches are found in the database, then we send them to the login page.

Overall the code is pretty simple. Feel free to use it in any of your applications. I’ll post the benchmarks when I get a chance, but several people were looking for this content, so I’m putting it out now.

{ 7 comments }

I wrote a quick overview of Rails Metal earlier and started thinking that it would be nice to provide some examples of how you could use it in your Rails application. Here are 9 ways I thought of off the top of my head.

I’ll provide a quick explanation of each one and then post the code as I get each one done.

1. Authentication checking

Many sites have two parts to them. The administrative side—which is protected by an authentication system—and the public side—which anyone can access. If a user must be logged in to access the admin/authenticated part of the site, then why not take advantage of the performance boost Rails Metal gives you and redirect before the Rails even gets loaded.

UPDATE: I just posted the an explanation of how to do Rails Metal Authentication.

2. OpenID checking

Similar to the Authentication checking, you can do a quick check of the user’s authentication status with their OpenID and let them through if they are already authenticated. It’s kind of like a convenient before_filter call on that section of your website.

3. Simple API’s

API’s are extremely simple to implement with Rails Metal. If you have a convenient method on the Model to convert a given instance into the format the API user expects—to_json for example—then you could simply provide a success response with the converted object as the response text.

UPDATE: I just posted the code and explanation on how to do API’s in Rails Metal.

4. Redirecting Affiliate Links

Have you ever built a site that made money from advertising and wished you could send people to http://mydomain.com/hosting for your hosting affiliate? This would be one of the simplest uses of Rails Metal. Check the path and redirect.

UPDATE: I just posted the an explanation of how to do Rails Metal Redirects.

5. Serving Static Content

If you have pages or some other type of content that doesn’t change from one page load to the next and you don’t need the nice templating magic that the Ruby on Rails layouts give you, then this solution is for you. It allows you to make these pages database driven.

UPDATE: I just posted the an explanation of how to serve Static Content and Pages using Rails Metal.

6. Serving Downloads

Regardless of whether the file you’re serving is a file in the filesystem, or has its data stored in the database, all you really need is the file’s mime-type and its content. Then you give a success response with the content type set in the header and the file content in the response text.

7. Tracking Analytics

Rails Metal was designed to be a possible endpoint for your application, meaning that it returns a response and stops execution. However, you can perform some function and then pass the execution on to the remainder of your application. So storing visits to certain paths is a cinch with Rails Metal.

8. Serving Cached Content

Are you caching your content to memcached or the filesystem? For memcached, loading cached content is as easy as loading the memcached client gem and accessing memcached with they key. The rest is caching on the part of your rails application.

9. Triggering Server Side Events (Jobs and Rake Tasks)

Have you ever thought it would be convenient to hit a particular URL and have it kick off a process on your server? Obviously you’d have to secure it, but we can do that with another Rails Metal that checks authentication. (See #1 and #2.)

If you have any other uses that you’re putting Rails Metal to, let us all know in the comments below.

{ 7 comments }

Have you ever wished you could mix Rack or Sinatra into your Ruby on Rails application just to get its raw throughput on certain parts of your application?

Let’s face it, sometimes, the Rails framework is overkill when we’re returning a simple string or an object in JSON as our response. Your answer for these instances is here. Rails Metal.

Performance

Some people have reported huge speed increases in Rails Metal over the Rails MVC framework. This article claimed a 25x increase over Rails. Pratik Naik from the Rails Core team benchmarked a more believable increase of 4x. Whatever the case, the performance advantage is worth noting.
[click to continue…]

{ 5 comments }

I’ve been playing with Rails Metal for quite some time now. Under certain circumstances, one Metal would catch something I intended for another metal deeper in the middleware stack. Reordering became the order of the day. The only problem was that the Rails Guide was in the works and I didn’t want to hack my metal files to put them in alphabetical order to match the order I wanted them in.

So, without further ado, here is how you reorder your Rails Metal middleware stack:

Open you environment.rb file:

vi config/environment.rb

Then add this line inside the initialization block:

config.metals = ["FirstMetal", "ThirdMetal", "SecondMetal", "FinalMetal"]

The strings in the array are the names of the metal files in camel case.

{ 3 comments }

One of the greatest features in Rails 2.3 is the Rails Metal piece. It’s part of the goodness that came out of the Rails/Merb merge.

Metal provides a layer of functionality that is executed before or in the place of your rails application. The common term for this type of software is middleware. There is a terrific presentation that was given at Mountain West RubyConf 2009. You can actually view the video of it here.

Here’s a simple example of how to use metal to require authentication before accessing pages on the site.
[click to continue…]

{ 1 comment }