Lightweight Ruby Web Applications

Published on August 17, 2009

Lightweight Ruby Web Applications

    Fast development


    Inspired by posts on Western blogs like “ Clone TinyURL with 40 lines of Ruby ” or “ Clone Pastie in 15 Minutes with Sinatra & DataMapper ”, I decided to try to go through and at the same time describe the whole process of implementing a lightweight ruby ​​web application, from design to deployment.



    Instruments


    For Ruby, there are a huge number of different tools for quick development. I settled on the following:

    Sinatra  - DSL for the web. A lightweight convention over configuration framework. Allows you to quickly and easily develop web applications, and is easily complemented by everything that you might need. The basis of our application.

    DataMapper  - ORM, the main competitor of ActiveRecord. It is inferior in something, surpasses the aforementioned in something, works perfectly with different databases, is easy to configure and integrate.

    HAML  - HTML for programmers. A markup language that is slightly more beautiful than traditional erb generates clean and valid xhtml. It contains the equivalent for CSS - SASS .

    Heroku - Allows you to conveniently and even free (of course, with restrictions) place the resulting application. An optional tool, you can deploy anywhere.

    What will we write?


    Having chosen the tools, I thought, but what, in fact, to write? And I decided that this would be a tool for organizing my comic strip. In such an application, there is a client functionality, an admin panel, and the generation of an rss feed for subscription, which will allow us to touch on various aspects of development and bring it closer to real tasks. Well, I also love web comics :)

    Code parsing


    So we come to the most interesting. Almost all of the resulting code is easy to understand and you can find it on  github . I recommend opening it to present the big picture, and I would like to dwell on the most important code fragments, and those fragments that caused me some difficulties in implementation.

    To begin with, we will analyze the structure of the project, it is very simple:
    comics.rb
    config.ru
    models.rb
    public
    views


    The models.rb file contains models, database configuration, and everything related to working with it. comics.rb contains all the code for the sinatra. Also, by default, Sinatra picks up the views folder  containing the views in haml and  public with files accessible from the web (javascript, images).

    Let's start with the models.
    models.rb
    1. DataMapper.setup(:default, ENV['DATABASE_URL'] || "sqlite3:///#{Dir.pwd}/comics.db")

    The database parameters on heroku are contained in ENV ['DATABASE_URL'], if there is no such variable, then create a sqlite database in the directory with the project. You don’t have to edit anything in the source.

    models.rb
    1. class DateTime
    2. def rfc822
    3. self.strftime "%a, %d %b %Y %H:%M:%S %z"
    4. end
    5. end

    The RSS 2.0 specification requires a date in RFC # 822 format. To do this, we add the rfc822 method to the objects of the DateTime class, which will format the timestamp as needed, and we will use it in future in the presentations.

    comics.rb
    1. def protected!
    2. response['WWW-Authenticate'] = %(Basic) and \
    3. throw(:halt, [401, "Not authorized]) and \
    4. return unless authorized?
    5. end
    6. def authorized?
    7. comics = Comics.first
    8. @auth ||= Rack::Auth::Basic::Request.new(request.env)
    9. @auth.provided? && @auth.basic? && @auth.credentials && @auth.credentials == [comics.login, comics.password]
    10. end

    Simple authentication implementation for Sinatra. Almost entirely taken from the  FAQ , the difference is that the username and password are taken from the database, instead of being wired into the source. It is extremely simple to use: it is enough to enter protected in an action requiring authentication !

    comics.rb
    1. get '/rss.xml' do
    2. content_type 'application/rss+xml', :charset => 'utf-8'
    3. @comics = Comics.first
    4. @strips = Strip.all :limit => 10
    5. haml(:rss, :layout => false)
    6. end

    Return of the rss feed. Change the Content-Type, and add: layout => false so that the feed does not render in layout.

    Now a few hints in the views.
    layout.haml
    1. %title= "#{@comics.title} — #{@strip.title}" rescue @comics.title

    If you do not use an exception mechanism, when an empty variable @strip we stop error  NoMethodError , since the class  nil no method  title . In rub, such things must always be kept in mind.

    models.rb
    1. class Strip
    2. # объявляем property
    3. def next
    4. Strip.first(:created_at.gt => self.created_at, :order => [:created_at.asc])
    5. end
    6. def previous
    7. Strip.first(:created_at.lt => self.created_at)
    8. end
    9. def get_id
    10. self.id
    11. end
    12. default_scope(:default).update(:order => [:created_at.desc])
    13. end

    layout.haml
    1. - tonext = "/#{@strip.next.get_id}" rescue "#"
    2. - toprevious = "/#{@strip.previous.get_id}" rescue "#"

    The mechanism is understandable - search for the next and previous strip for the one that we are looking at now, and displaying links to them in the view. Why did you have to make a separate get_id method instead of directly using an existing id ? The fact is that if we look at the strip that is currently last, the next method will return nil . And  nil, in turn, has an id method , which I think for a long time will return "4". You can verify this yourself by experimenting with irb.

    You can end this with a parsing, I’ll be happy to answer any questions about the code, and I will respond to criticism in the comments.

    Deployment


    The written application will easily start like any other application on the Sinatra with the ruby comics.rb team . But we want to show it to the world, and Heroku will help us with this. We register on heroku, and install gem heroku on our local machine. Now we write a config for Rack:
    config.ru
    1. require 'comics'
    2. run Sinatra::Application

    The next step is to create an application on heroku, and push the code there. Let's agree that the application is already in your git repository:
    heroku create comics
    git push heroku master

    It remains only to fill the database with initial data, for this in  models.rb there is an install method :
    heroku console
    install

    That's all, you can go to the address that gem issued when creating the application, and if everything is done correctly, then enjoy the result.

    References


    Comics on GitHub
    Demo on Heroku ( Admin , password on request in Habrahta).