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.

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 :)

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 %>

    Debugging email output in Rails

    Posted by Dan Sosedoff on November 05, 2010

    Need to troubleshoot email output? No problem. Just need to configure your development environment with the following lines:

    # RAILS_ROOT/config/environments/development.rb
    config.action_mailer.delivery_method = :sendmail 
    config.action_mailer.sendmail_settings = {:location => "/usr/bin/fake-sendmail.sh"}
    config.action_mailer.default_url_options = { :host => "YOUR HOST" }

    And here is the fake-sendmail utility i wrote: (save as “/usr/bin/fake-sendmail.sh”)

    #!/usr/bin/ruby
     
    dir = "/tmp/fake-sendmail"
    headers = ''; $stdin.each_line { |l| headers << l ; break if l.strip.length == 0 }
    body = '' ; $stdin.each_line { |l| body << l }
    format = body.match(/html/i) ? "html" : "txt"
    filename = Time.now.to_i
     
    Dir.mkdir(dir) unless File.exists?(dir)
    File.open("#{dir}/#{filename}_headers.txt", "w") { |f| f.write(headers) }
    File.open("#{dir}/#{filename}.#{format}", "w") { |f| f.write(body) }

    Paste: http://pastie.org/private/er61gafxb6fzowjo4chjvq

    As the result you’ll see 2 files created for each outgoing email. One has all email headers and another one has the body.

    Also, it works for Sinatra and Merb. Woot!

    Some fun with Pastie

    Posted by Dan Sosedoff on September 30, 2010

    I really like public paste service – pastie.org. Too bad it does not have public API.
    The most common user case for me – drop some files. 1 or 2 usually. So i have to copy and paste file contents into the form, select private and then send link to someone. Too many actions i think.

    I decided to spend some time and create console tool and simplified api based on html extractions.

    Source: http://github.com/sosedoff/pastie

    Just install it:

    $ sudo gem install pastie-api

    And use it (multiple files are supported):

    $ pastie file
    $ pastie file1 file2 ... fileN

    Also, API access:

    require 'rubygems'
    require 'pastie-api'
     
    # Create a new private paste
    p = Pastie.create('Test string')
     
    # Create a new public paste
    p = Pastie.create('Hello!', false)
     
    # View paste details
    puts "Paste ID: #{p.id}"
    puts "Paste Key: #{p.key}"
    puts "URL: #{p.link}"
    puts "Raw link: #{p.raw_link}"
     
    # Find existing paste
    p = Pastie.get(1234567) # find by paste's ID
    p = Pastie.get('abcdefabcdef') # find by paste's private code

    Planning to add local pastes history and auto-paste from clipboard.

    Why rapid development is the best way to make web apps

    Posted by Dan Sosedoff on September 17, 2010

    What do you know about rapid development?

    I guess something. Almost every developer experienced moments when you or someone have really interesting idea and wanted to jump on it right away. One of the obstacles – tools you use to achieve it. And the web framework is the first of them. Why? Because if you want to create something cool without being stuck on technology level you should use things that you dont really notice while making your app. Of course it varies from person-to-person, but just imagine yourself in traffic jam driving stick-shift car, switching gears like crazy all the time.

    Back to webdev, with my experience i can only say that the best tool for rapid development is Sinatra. Its so small and powerful that i got hooked on it since first day i found it. Almost all my personal/work projects on early stages have been developed with Sinatra. Another big plus – small and usefull extensions, big players like AR or DataMapper. It was extracted from Merb project, another small and powerful web framework i like to work with. Its like a one-file web application. Perfect for super-fast development. Wanna do simple music downloads page? No problem. API – again, no problem. Deployment is simple and similar to most of frameworks, as it built on top of Rack: thin, passenger (nginx/apache).

    What do you know about rapid development?
    I say – i love it.

    Custom SQL queries in Rails

    Posted by Dan Sosedoff on August 17, 2010

    Sometimes ORM just cant hand some complicated query, especially when the results of such query does not refer to any table.

    For Rails ActiveRecord:

    class Item < ActiveRecord::Base
       def self.some_heavy_stuff(param)
         sql = self.sanitize_sql(['.... SQL ....', param)
         self.connection.execute(sql)
       end
    end
     
    # usage
    data = Item.some_heavy_stuff(1)
    data.each do |row|
       puts row['id']
    end

    For DataMapper its almost the same (they`ve replaced query method with select not a long time ago):

    class Item
      include DataMapper::Resource
     
      property :id, Serial
      property :foo, String
      property :bar, String
     
      def self.some_heavy_stuff(param)
         repository(:default).adapter.select('... SQL ...')
      end
    end

    Disable auto-incremental field in Rails Migrations

    Posted by Dan Sosedoff on August 12, 2010

    Since i`ve been using both DataMapper (Merb/Sinatra) and ActiveRecord (Rails) a lot i noticed that AR acts weight when i manually set PK key, particularly ID field, which you dont have to define by default. In DM you have to define it as ‘Serial’.

    So, the task is to create/update records in your database which is supposes to represent data from primary database. In such cases all ID`s should be unique and equal to each other.

    To disable autoincremental ID field in your AR models just use this option:

    create_table :foo, :id => false do |t|
      t.integer   :id, :options => 'PRIMARY KEY'
      # .... rest of columns
    end