Rendering custom tags with Liquid on Rails

Posted by Dan Sosedoff on January 31, 2011

Liquid is a flexible and safe template engine that allows you to write code inside the actual template. It was extracted from Shopify and is pretty awesome if you need your templates be editable by non-programmers. This is a most important part, as such users might break the whole application. In this case Liquid is perfect. Also, its kind of similar to Blitz template engine, which i was using on PHP.

The documentation is not the perfect, but enough to understand the concepts, so i’ll just jump straight to the matter.

Subject

Lets assume that all our templates are stored in database and we dont store anything in filesystem. And we need to use small parts as snippets to embed into actual template.

Implementation

The model where we store all snippets with be named Snippet. Fields:

  • ID – Primary key
  • Name – Actual name of the snippet used in tempates
  • Body – Content of the snippet
  • Created/Updated timestamps – DateTime stamps for internal use

And the active record model:

class Snippet < ActiveRecord::Base
  NAME_FORMAT = /^[a-z\d\_\-]{2,64}$/i    # Define your own name format
 
  validates_presence_of   :name, :body
  validates_length_of     :name, :within => 2..64
  validates_format_of     :name, :with => NAME_FORMAT
  validates_uniqueness_of :name, :case_sensitive => false
end

Now, lets define the custom liquid tag “snippet”:

class SnippetTag < Liquid::Tag
  def initialize(tag_name, snippet_name, tokens)
    super
    @name = snippet_name
  end
 
  def render(context)
    # You might want to define caching mechanism here
    snippet = Snippet.find_by_name(@name)
    unless snippet.nil?
      Liquid::Template.parse(snippet.body).render
    else
      "Error: Snippet #{@name} was not found!"
    end
  end
end

Now, register the tag:

Liquid::Template.register_tag('snippet', SnippetTag)

And use it within your template:

This is a custom tag that renders a widget: {% snippet sample_snippet %}

If the requested snippet was not found or does not exists renderer will replace the tag with error message, which might be adjusted for your personal needs or hidden. Also, you can use nested snippets, but in this case you have to think about right snippet template caching approach.

Using Liquid drops

A drop in liquid is a class which allows you to use predefined data collection methods in the template. If you would like to make data available to the web designers which you don’t want loaded unless needed then a drop is a great way to do that.

Lets make a drop for a basic dataset for Post model (blog posts for example):

class PostsDrop < Liquid::Drop
  # fetch all posts
  def all
    Post.all
  end
 
  # fetch N most popular posts
  def popular(num=10)
    Post.all(:order => 'pageviews DESC', :limit => num)
  end
end

Define a template for this collection:

Popular posts:
{% for post in posts.popular %} Post: {{ post.title }} {% endfor %}

Load a drop into the template:

template = Liquid::Template.parse(YOUR_TEMPLATE).render('posts' => PostsDrop.new)

With such scheme you can hook up custom snippet that renders a list of objects from your drop.
Pretty cool. Works on Rails 3.

Delete last Git commit

Posted by Dan Sosedoff on January 26, 2011

One in a while everybody makes a wrong commit. If you didnt push this commit yet you can use the following command to revert changes:

git reset --hard HEAD~1

HEAD~1 is just an alias for the commit before head. Alternatively, you can refer to the SHA-1 of the hash you want to reset to. Note that when using “–hard” any changes since the commit before head are lost. To keep changes you can use softmode “–soft” that will delete the commit but it will leave all your changed files.

In case if the commit was already pushed you can use revert command, which will create a new commit that reverses everything introduced by the accidental commit.

git revert HEAD

Discover hidden API services with Proxie

Posted by Dan Sosedoff on January 25, 2011

Need to figure out closed/private API? Not a problem. Its always fun and challenging. There are few tools i use for that:

Proxie is another project of mine, which i finally extracted from a script written a while ago and published it as a ruby gem.
I used it to track Grooveshark API and it worked out great. Here is the list of features:

  • Bind proxy server to any port (default is 8080)
  • Define output database. It uses SQLite3 by default, but you can extend it with any other types
  • Sinatra-base web interface to browse all your collected data

Installation:

sudo gem install proxie

Start a server:

proxie -d DATABASE_NAME

After data collection just run:

proxie --web

The web interface runs on localhost:4567 by default.

Usage summary:

Usage: proxie [options]
    -i, --info                       Display this information.
    -p, --port PORT                  Listen on port (8080 default)
    -d, --db NAME                    Store results to database
    -w, --web                        Start a Web UI for databases
    -f, --flush                      Delete all local databases

Project on GitHub: http://github.com/sosedoff/proxie

Fetching single resources with ActiveResource

Posted by Dan Sosedoff on January 23, 2011

ActiveResource is a perfect tool to consume API’s based on Rails. Most examples are pretty much simple and understandable. But i got really confused with the way to get a single resource.

Example use case

I was working with Unfuddle API recently, and i needed to fetch account information. So, basically, core url is like this:

http://subdomain.unfuddle.com/api/v1

And for account:

http://subdomain.unfuddle.com/api/v1/account

Resources:

# api base for all resources
class APIBase < ActiveResource::Base
  self.site     = "http://subdomain.unfuddle.com/api/v1"
  self.format   = :json
  self.user     = 'YOUR_USERNAME'
  self.password = 'YOUR_PASSWORD'
 
  # for debug purposes you can setup logger
  self.logger = LOGGER_CLASS
end
 
class Account < APIBase ; end
class Repository < APIBase ; end

To get all repositories you call as usual:

repos = Repository.all
# this will fetch URL_BASE/repositories.json

But account resource is a single resource, to fetch it you need to make a call like this:

# this will produce invalid url: /accounts
acc = Account.find(:one)
 
# and this is a correct way
acc = Account.find(:one, :from => '/account')

I wasted around 20 mins to figure out how to do that. This should be mentioned in the docs.

Mapster – the right place for your travel feed.

Posted by Dan Sosedoff on January 22, 2011

Hey folks,

My friend and co-worker Roman Efimov has released his app for photo-sharing and geo-taggin. It is a perfect app if you’re looking for an alternative of iphone’s places feature. It lets you upload all your photos and will automatically geo-tag them with locations if you have GPS feature enabled in your photo settings.

Check out Mapster


Screen shot 2011-01-22 at 8.47.44 PM

It is still in early stage, but you’re welcome to check it out!

Basecamp authentication with Rails and Authlogic

Posted by Dan Sosedoff on January 22, 2011

If you’re building a service that works directly with Basecamp you might want your users to login with their existing basecamp account. That gives you ability to use your app as if it was build-in into basecamp. I’ll just write a small example how to use it. I know about openID but this is a little bit different story.

Potential use-case could be an app where you store all your private data without storing it to basecamp. Like configuration files, maybe passwords, etc.

The task

- Allow existing basecamp users use the same login without being redirected to signup page
- Any desired app functionality

Basecamp API

Take a look at the API documentation – http://developer.37signals.com/basecamp/ for more detailed information.

Authentication wrapper (uses RestClient and ActiveSupport)

module Basecamp  
  @@config = {}
 
  class WrongCredentials < Exception ; end
  class WrongUserType < Exception ; end
 
  # Configure Basecamp account
  # account should be a subdomain of your url
  def self.configure(account)
    @@config[:subdomain] = account
  end
 
  # Authenticate user
  # Options:
  #   :client => true|false - Check if client of the company
  def self.authenticate(user, password, opts={})
    url = "https://#{@@config[:subdomain]}.basecamphq.com/me.xml"
    res = RestClient::Resource.new(url, :user => user, :password => password)
    begin
      person = Hash.from_xml(res.get)['person']
      if opts.key?(:client) && opts[:client]
        raise WrongUserType, 'Invalid user type!' if person['client_id'] == 0
      end
      person
    rescue RestClient::Unauthorized
      raise WrongCredentials, 'Invalid username or password'
    end
  end
end

So, the wrapper has only 2 methods – configuration and authentication.
To configure wrapper you need to provide your account name, which is a part of your url, like ACCOUNT_NAME.basecamphq.com

Basecamp.configure('ACCOUNT_NAME')

User authentication (P.S it does not support openID logins):

begin
   me = Basecamp.authenticate('username', 'password')
   me['user_name'] # => person username
   me['email_address'] # => person email address
   # more details are described in api documentation
rescue Basecamp::WrongCredentials
  # if username or password is wrong
rescue Basecamp::WrongUserType
  # will trigger if one of your clients is trying to login to this tool.
  # all members of the account have parameter client_id = 0 
  # so thats the protection from unauthorized access
end

To give a client ability to login into your basecamp-affiliated app just use additional key :client

user = Basecamp.authenticate('user', 'password', :client => true)

Integration with Authlogic

I’ll just use the default authlogic model for this example:

class User < ActiveRecord::Base
  acts_as_authentic
 
  validates_presence_of :login, :email, :first_name
  validates_uniqueness_of :login, :email
 
  def name
    "#{self.first_name} #{self.last_name}".strip
  end
 
  # Import user from Basecamp
  def self.import(h)
    u = User.new(
      :login => h['user_name'],
      :email => h['email_address'],
      :password => h['password'],
      :password_confirmation => h['password'],
      :first_name => h['first_name'],
      :last_name => h['last_name']
    )
    u.save
    u
  end
end

And controller code (UserSessions#create):

def create
  fields = params[:user_session]
  @user_session = UserSession.new(fields)
 
  user = User.find_by_login(fields[:login])
  if user.nil?
    begin
      me = Basecamp.authenticate(fields[:login], fields[:password], :client => false)
      user = User.import(me.merge('password' => fields[:password]))
    rescue Basecamp::WrongCredentials
      @user_session.errors.add(:base, "Sorry, your username or password wasn't recognized.")
    rescue Basecamp::WrongUserType
      @user.session.errors.add(:base, "Sorry, you're not allowed here.")
    end
  end
 
  if @user_session.save
    return redirect_to root_path
  else
    @user_session.errors.add :base, "Sorry, your username or password wasn't recognized."
  end
 
  render :action => :new
end

And then your application has the original behavior and basecamp authentication support. Just add more logic :)

Fast and easy password generation

Posted by Dan Sosedoff on January 22, 2011

I used to generate a lot of passwords and usually it would be an online service found by googling “generate password online”. It worked fine until i got tired of it and decided to find something else, much easier and faster. Something that will give me the results right away from terminal while doing server setup.

Here we go, bash script (found online and modified by my needs):

#!/bin/bash
 
charspool=('a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p'
'q' 'r' 's' 't' 'u' 'v' 'w' 'x' 'y' 'z' '0' '1' '2' '3' '4' '5' '6' '7'
'8' '9' '0' 'A' 'B' 'C' 'D' 'E' 'F' 'G' 'H' 'I' 'J' 'K' 'L' 'M' 'N' 'O'
'P' 'Q' 'R' 'S' 'T' 'U' 'V' 'W' 'X' 'Y' 'Z' '-' '_');
 
len=${#charspool[*]}
 
if [ $# -lt 1 ]; then
  num=20;
else
  num=$1;
fi
 
randomnumbers=$(head -c $num /dev/urandom | od -t u1 | awk '{for (i = 2; i <= NF; i++) print $i}')
echo -n "password: "
 
for c in $randomnumbers; do
  echo -n ${charspool[$((c % len))]}
done
echo

Installation

Just create a script called something like “genpassword” in your bin dir and make it executable.

sudo nano /usr/local/bin/genpassword
sudo chmod +x /usr/local/bin/genpassword

Works on any unix-like machine with bash installed.

Usage

Usage is pretty straight-forward. Just type “genpassword” in terminal and you’ll get the password.

genpassword # This will give a default length (20 chars) password
genpassword 64 # This will give you a 64 chars long password

Scaling images with RScale

Posted by Dan Sosedoff on January 22, 2011

There are few different image processing libraries out there right now:
- Paperclip
- DragonFly
- CarrierWave

But sometimes you need just a tool that does the simple job – scale and save to fs as easy as possible. Here is the library i made for this specific role: RScale. It is a simple image processing library for ruby scripts based on ImageMagick terminal tool. Allows you to define a set of image formats and its exact dimensions and generate thumbnails just with one call. It does not have any other features than making thumbnails, neither it keeps the original source. You also can use it with Rails 2/3, Sinatra or any other framework.

Installation

Make sure you have ImageMagick installed on your system.
You can install it using aptitude or compile from source.

sudo apt-get install imagemagick

Install Rscale as ruby gem:

sudo gem install rscale

Getting Started

First, we need to setup the actual store folder. In rails it would be Rails.root + “/public”.
Make sure this folder is writable.

RScale.configure do |c|
  c.public = "PATH_TO_YOUR_OUTPUT_DIR"
end

Now, we need to define formats. Format is a holder of different image styles.
Here is ‘avatar’ format with 3 styles (64×64, 128×128, 256×256).

 RScale.format :avatar do |f|
   f.url = '/static/:format/:style/:uuid_dir/:uuid.jpg' # optional
   f.style :small,       :size => '64x64', :sharp => true, :q => 50
   f.style :medium,  :size => '128x128'
   f.style :large,       :size => '256x256'
 end

Style options:

  • :size – Exact image size in pixels as follows: ‘Width x Height’
  • :sharp – Sharpen image after processing (true/false)
  • :q – Output image quality (0..100)

URL parameter is just a path to store generated thumbnails, relative to public path defined in configuration block. Available URL parameters:

  • :uuid – 32-byte UUID string
  • :uuid_dir – /xx/xx directory structure generated from uuid string
  • :md5 – 32-byte source image MD5 checksum
  • :time – Unix timestamp
  • :extension – Original extension of source image
  • :filename – Original filename of source image
  • :format – Name of user-defined format
  • :style – Name of user-defined format style (ex. :small, :medium, :large)
  • Usage example

    path = '/tmp/.....' # path to the source/uploaded image
    result = RScale.image_for :avatar, path
     
    # If source file cannot be processed result will always be null
    unless result.nil?
      # result will contain processed thumbnails with path relative to public path
      result[:small]           # 64x64
      result[:medium]      # 128x128
      result[:large]           # 256x256
     end

    RScale does not support uploads to any remote storage systems like AmazonS3, CloudFiles, etc.
    Maybe it will support it later, but i dont think it needs that due to its purpose.

    Source

    Feel free to extend the library: RScale on Github

    New year – new updates!

    Posted by Dan Sosedoff on January 08, 2011

    I didn’t write anything to this blog for a while so i just drop a couple of lines per each small project i was working on. First of all i’ve published new gems, mostly API wrappers for a few popular websites: Grooveshark API, Goodreads API, Pastie API.

    Grooveshark does not provide any public api’s so i just used http monitoring tool to collect all the required data and build up a set of objects. Which turned out to be not bad – i search for songs, directly stream them, work with favorits and playlists. Library management is also coming, i
    have some troubles figuring out why it does not work sometimes.

    Pastie.org also does not provide any api’s, in fact it does not have one. So i just used html parser to get the job done. Also, command-line tool allows you to keep history of all pastes in case you loose link. In new version you’ll be able to send email with paste link directly. Only Gmail supported by now.

    All gems and documentation you can find on my github: http://github.com/sosedoff

    Generate unescaped HTML with Rails 3

    Posted by Dan Sosedoff on November 24, 2010

    Since i switched to Rails 3 i figured out that you dont need to escape html contents (using h method in Rails 2), its been done automatically. So, for now if you have any plugins or methods that produce raw html you have to use method “html_safe” to unescape it.

    Before:

    <%= your_helper_method %>

    Now:

    # your helper method
    def your_helper_method
        # .... content generation
        return content.html_safe
    end

    In in view:

    <%= your_helper_method %>