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.
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
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:
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
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
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 %>
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!
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.
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.
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
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