CNU CODE

Metal Session

Posted in Ruby on Rails by Srinivas Reddy on July 25, 2009

In my previous post there is a simple metal application, implementation fast metal paste kind of feature.

Metal classes bypass routing and Action Controller to give you raw speed (at the cost of all the things in Action Controller, of course). This builds on all of the recent foundation work to make Rails a Rack application with an exposed middleware stack.

Note: Rails version required is 2.3

For example I was trying to leverage a resource via metal, but the resource shouldn’t be accessed by other and it is locked by current user. So I need to check the session for logged-in user before giving that resource. Can I access session in metal?

Try this in rails application root “rake middleware”, it shows all the middleware stack in current application

srinivas@Azr_CLE9:~/work/metal_app$ 
srinivas@Azr_CLE9:~/work/metal_app$ rake middleware
(in /home/srinivas/work/metal_app)
use Rack::Lock
use ActionController::Failsafe
use ActionController::Reloader
use ActionController::Session::CookieStore, #
use Rails::Rack::Metal
use ActionController::RewindableInput
use ActionController::ParamsParser
use Rack::MethodOverride
use Rack::Head
use ActiveRecord::ConnectionAdapters::ConnectionManagement
use ActiveRecord::QueryCache
run ActionController::Dispatcher.new
srinivas@Azr_CLE9:~/work/metal_app$
srinivas@Azr_CLE9:~/work/metal_app$ 

Shows that the ActionController::Session::CookieStore is included before Rails::Rack::Metal middleware(in rack stack session middleware takes precedence), so its clear that for metal middleware, session are accessible via rack session store. 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 is the code examples:

class Myresource
   # get the current_user from the rack session and check the user has locked the record
  def initialize(env)
    @session = env["rack.session"]
    @db_connection = ActiveRecord::Base.connection()
    if !@session.nil? && @session[:user_id]
      @current_user = @db_connection.execute(
        "SELECT * FROM users WHERE (users.id = #{@session[:user_id]}) LIMIT 1"
      ).fetch_row # returns logged-in user
    end
  end

  def self.call(env)
    if env["PATH_INFO"] =~ /^\/myresource\/(\w+.pdf)$/
      Myresource.new(env).send_pdf($1)
    else
      [404, {"Content-Type" => "text/html"}, ["Not Found"]]
    end
  ensure
    # Release the connections back to the pool.
    ActiveRecord::Base.clear_active_connections!
  end

  def send_pdf(pdf_name)
    @wip = @db_connection.execute(
      "SELECT uesr_id, is_locked FROM pdf WHERE
       pdf_file = pdf_name LIMIT 1"
    ).fetch_row # returns [user_id, is_loked]
    unless @session.nil? || @current_user.nil?
      if @pdf.nil? || ( @pdf[0].to_i != @session[:user_id] && @pdf[1].to_i == 0 )
        [200, {"Content-Type" => "text/html"}, "Permission Denied"]
      else
        [200, {"Content-Type" => "application/pdf"}, File.read( MESSAGES_ROOT + pdf_name )]
      end
    else
      [302, {'Location'=> '/login' }, []]
    end
  end
end

Thanks
Srinivas Reddy

Advertisements

Pastie Implementation with Rails Metal

Posted in Ruby on Rails by Srinivas Reddy on June 20, 2009

Rails Metal:
Rails Metal is a way to cut out the fat of the MVC request processing stack, to get the quickest path to your application logic. This is especially useful when you have a single action that you need to run as quickly as possible and can afford to throw away all the support Rails normally provides in exchange for a very fast response time.

Though its really easy for any Rails developer to start building metal applications, it is a very sharp tool. Rails developers should still continue to use Rails as they normally do, but when they are sure that a specific action requires extra performance, the tools are available.

Writing a Rails Metal app can make you realize just how spoiled we’ve become with all the convenience that comes with Rails. Without the controller and view helpers, it can become a painful experience. Here’s a guide to help make it a better experience.

Requirement:
rails 2.3
Syntax converter: Syntax is, first and foremost, a lexical analysis framework. It supports pluggable syntax modules, and comes with modules for Ruby, XML, and YAML
Install using the command: sudo gem install syntax
syntax more…

Start:
create rails app:

srinivas@srinivas:~/work# rails pastie –database=mysql

move to app directory:

srinivas@srinivas:~/work# cd pastie

model:

srinivas@srinivas:~/work/pastie# ruby script/generate model snippet code:text

run migration:

srinivas@srinivas:~/work/pastie# rake db:migrate

create metal:

srinivas@srinivas:~/work/pastie# ruby script/generate metal pastie

Lets edit the metal file pastie.rb which is created in app/metal directory to the code snipped for quick start working metal.

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

require 'syntax/convertors/html'      

class Pastie
  def self.call(env)
    if env["PATH_INFO"] =~ /^\/\d+$/

      @snippet = Snippet.find(env["PATH_INFO"].gsub!(/\D/,'').to_i)

      convertor = Syntax::Convertors::HTML.for_syntax 'ruby'

      html = convertor.convert(@snippet.code)
      [200, {"Content-Type" => "text/html"}, [html]]

    elsif env["PATH_INFO"] =~ /^\/new$/
      new_form_html = "<html><body><h1> Paste Your Code </h1>

 <form action='/create' class='new_snippet' id='new_snippet' method='post'>
 <textarea cols='40' id='snippet_code' name='snippet' rows='20'></textarea>
 <input name='commit' type='submit' value='Create' />
 </form></html></body>"
      [200, {"Content-Type" => "text/html"}, [new_form_html]]

    elsif env["PATH_INFO"] =~ /^\/create$/
      request = Rack::Request.new(env)

      if request.post?
        params = request.params['snippet']

        snippet = Snippet.new(params)
        snippet.save

        response = Rack::Response.new()
        response['Content-Type'] = 'text/html'

        response['Location'] = snippet.id.to_s
        response.status = 301

        response.finish
      end
    else
      [404, {"Content-Type" => "text/html"}, ["Not Found"]]

    end
  ensure
    <span class="comment" style="color:#919191;"# Release the connections back to the pool.
    ActiveRecord::Base.clear_active_connections!
  end

end
  • env["PATH_INFO"] =~ /^\/new$/ check for the new pastie path (http://localhost/new) and renders the form to ruby code.
  • env["PATH_INFO"] =~ /^\/create$/ checks the request for save the pastie (http://localhost/create) and saves into the database. which creates a unique id for the current entered pastie
  • env["PATH_INFO"] =~ /^\/\d+$/ checks the particular pastie to display (http://localhost/1 #unique digit), it just queries the snippets from the database using the unique id given in the path info
    @snippet = Snippet.find(env["PATH_INFO"].gsub!(/\D/,'').to_i)
    parse the output to the convertor object more...

Rack::Response provides a convenient interface to create a Rack response, so write the response body with the pastie that returned by the syntax converter and finish the response. This will return the status, headers and response body(html)

Imp:
Rails doesn’t take care of certain things in Rails Metal pieces, including the releasing of connections back to the database connection pool.
Moral of the story: Don’t forget to release your database connections if you’re using ActiveRecord in your Rails Metal. Or even better, don’t use ActiveRecord in Rails Metal – you’re aiming for raw speed anyway right?
Rails Metal piece to always ensure it clears any active database connections with ActiveRecord::Base.clear_active_connections!
Which was actually found in rails bug tracker

Please find the similar but well refactored implementation of metal here by Gautam Chekuri, and it shows the benchmark difference between wtih/without ActiveRecord.

Thanks
Srinivas