Mark Gandolfo's Blog

Rails Plugin: Associated With?

Posted on January 16, 2010

I've just released a new plugin called associated_with, that allows you to check if two objects are associated.

Check out the write up: http://markgandolfo.com/pages/associated-with

Or the GitHub Repo: http://github.com/markgandolfo/associated_with


How To Integrate Delayed Job Into Your App

Posted on January 10, 2010

So a quick how to integrate delayed job into your app, since i've found a lot of the tutorials out there leave out some crucial information.

Firstly, I used Tobi's delayed_job since it comes with a few extra bits a pieces, like generators and the like.

Install the plugin

You can either install it using the rails command, or use git submodules (which i won't cover here)

script/plugin install git://github.com/tobi/delayed_job.git

Generate and run the migration

We'll run the generator script to generate our migration and then migrate the database up

script/generate delayed_job_migration

Which will generate a migration that looks like this. Have a read through the file. You're probably asking yourself where the object to be run is stored. Its actually stored in the "handler" field as a sexy yaml field.

  create_table :delayed_jobs, :force => true do |table|
    table.integer  :priority, :default => 0      # Allows some jobs to jump to the front of the queue
    table.integer  :attempts, :default => 0      # Provides for retries, but still fail eventually.
    table.text     :handler                      # YAML-encoded string of the object that will do work
    table.string   :last_error                   # reason for last failure (See Note below)
    table.datetime :run_at                       # When to run. Could be Time.now for immediately, or sometime in the future.
    table.datetime :locked_at                    # Set when a client is working on this object
    table.datetime :failed_at                    # Set when all retries have failed (actually, by default, the record is deleted instead)
    table.string   :locked_by                    # Who is working on this object (if locked)
    table.timestamps
  end

How to queue mail in your app

Lets say for example we have a AccountsMailer

class AccountMailer < ActionMailer::Base  
  def confirm(user)
    subject       'Your account has been created'
    recipients    user.email
    from          'you@email.com'
    sent_on       Time.zone.now
    content_type  'text/html'
    body "Welcome Aboard"
  end
end

We would normally send an email by doing something like this in our controllers

AccountMailer.deliver_confirm(@user)

With delayed job, we can simply do a

AccountMailer.send_later(:deliver_confirm,@user)

So delayed job adds a send_later class method, which takes an AccountMailer class method as its first param, and then the methods params as the next.

How to send the mail

Setting up actionmailer is outside of the scope of this post. So i'm assuming you have it set up already.

The jobs at this stage are being queued in your database, but we haven't started any sort of job to process the queue. To start the queue simple run

rake jobs:work

You should see the queue starting to process, or at idle if its empty.

This is great, but what happens if the rake task dies for some reason???? Daemons to rescue!!!

Daemonising delayed job queue processor

First install the daemons gem

sudo gem install daemons

Next create a script in your script/ directory.. I called mine delayed_job

#!/usr/bin/env ruby
require 'rubygems'
require 'daemons'
dir = File.expand_path(File.join(File.dirname(__FILE__), '..'))
 
daemon_options = {
  :multiple   => false,
  :dir_mode   => :normal,
  :dir        => File.join(dir, 'tmp', 'pids'),
  :backtrace  => true
}
 
Daemons.run_proc('job_runner', daemon_options) do
  if ARGV.include?('--')
    ARGV.slice! 0..ARGV.index('--')
  else
    ARGV.clear
  end
  
  Dir.chdir dir
  RAILS_ENV = ARGV.first || ENV['RAILS_ENV'] || 'development'
  require File.join('config', 'environment')
  
  Delayed::Worker.new.start
end

Save that, and you can now simple do the below to start the queue.

script/delayed_job start

I would recommend reading up on the delayed_job and daemons extensions to understand the inner workings, but at least this is enough to get you up and running!

notes

You can also clear the queue by running

rake jobs:clear

How To: Rspec And Factory Girl

Posted on December 20, 2009

I thought i'd add a quick how to, on setting up Factory Girl and RSpec

First lets install some gems

The below command will install rspec and factory girl as well as a bunch of dependencies if you don't have them already.

sudo gem install rspec rspec-rails thoughtbot-factory_girl

And configure your app

Now in your rails app, add the following to your environment/test.rb file (you can leave the version symbol out to always fetch the latest).

# /config/environment/test.rb
config.gem "rspec",                   :lib => false,          :version => ">=1.2.6"
config.gem "rspec-rails",             :lib => 'spec/rails',   :version => ">=1.2.6"
config.gem "thoughtbot-factory_girl", :lib => "factory_girl", :version => ">=1.2.2", :source => "http://gems.github.com"

Lets generate the rspec skeleton and configure factories

Now that we have our gems set up ready to roll, in your rails app run the below command, This will generate a /spec directory and create a spec_helper.rb file.

script/generate rspec

Now in the spec directory create a factories folder. This will be the place you can store the definitions for each factory.

mkdir spec/factories

In your spec_helper.rb file, add the following require's to ensure everything is loaded when your tests run.

# spec/spec_helper.rb
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path(File.join(File.dirname(__FILE__),'..','config','environment'))
require 'spec/autorun'
require 'spec/rails'
require 'factory_girl'

Dir[File.expand_path(File.join(File.dirname(__FILE__),'support','**','*.rb'))].each {|f| require f}

Spec::Runner.configure do |config|
  config.use_transactional_fixtures = true
  config.use_instantiated_fixtures  = false
end

Factory girl should magically find all of your factories in that folder, but in one of my applications it didn't. I'm not sure why, and i didn't have the time to check, so i manually included them. To do this, in your spec_helper.rb file, add the following line, right above the "Spec::Runner".

Dir[File.expand_path(File.join(File.dirname(__FILE__),'factories','**','*.rb'))].each {|f| require f}

An example of how it all comes together

And you should be good to roll.. I'll give you a quick example of a factory and a simple spec, just to get you running. So if I had a Company model, I could test that model as such

# spec/factories/companies.rb
Factory.sequence :name do |n|
  "company name #{n}" 
end

Factory.define :company do |c|
  c.name { Factory.next(:name) }
  c.add_attribute :description, 'company description' 
  c.add_attribute :subdomain, 'company_name '
end

So I'm defining the factory called :company, and it has three fields. A name, description and subdomain. You'll notice I've defined name a little differently than description and subdomain. Thats because I want my name to incremental (i.e. I don't want two company names to be the same). Now each time i create a company factory, the name will increment by 1.

For a full break down on all of the magick of factories, check out the Factory Girl Github page.

Next, lets do some rspec magick.

So in my spec/models/ directory create a new file called company_spec.rb and here is a basic company test, using Factories

require 'spec_helper'

describe Company do
  before(:each) do
    @company = Factory(:company)
  end

  it "should create a new instance given valid attributes" do
    @company.should be_instance_of(Company)
  end
end

You can see by doing Factory(:company) I'm building a new factory and storing it in @company. Its as simple as that, no more messy fixtures.

Here are some resources to help you with rspec

Cheat Sheet

Another Cheat Sheet (PDF WARNING)

And Another Cheat Sheet (PDF WARNING)

Good luck with testing!


Restart Your Rails Server Within Textmate

Posted on December 19, 2009

I tend to find myself needing to restart my rails server in development mode quite often, and found that mongrel like passenger also restarts when tmp/restart.txt is present. So I created a simple command for Textmate which will easily restart the server for my current app i'm working in.

To install:

cd ~/Library/Application Support/TextMate/Bundles/Ruby on Rails.tmbundle/Commands
wget http://markgandolfo.com/system/files/2/original/Restart_Server.tmCommand

Then restart Textmate/Reload Bundles.

To use it simple press ^⌥⌘ R buttons, and your app will restart


How To Install Imagemagick On Snow Leopard

Posted on December 18, 2009

There are so many blog posts out there detailing the many many steps needed to install imagemagick on snow leopard, i thought I'd set the record straight.

sudo port install imagemagick
sudo gem install rmagick

Get a coffee while imagemagick installs, because it takes quite a while. But it works at the end of the day, and we can continue being developers, rather than sysadmins!


Firefox Double Page Request

Posted on December 17, 2009

For the life of me I couldn't work out why every time I clicked on a new advert in my new classifieds section I'm developing it would count as two clicks in firefox.

After a bit of research I've found that if firefox finds an empty img src, i.e. img src="", then it reloads the page hoping it'll automatically appear.

I wonder why the moz devs decided this was a smart idea, is this part of the w3c? Anyway, to resolve, simply remove all of the empty src tags.

Update

Christopher Blizzard emailed me last night explaining that the mozilla team has rectified this in firefox 3.5. He did point out that 3.0 suffers from it, as well as Safari & IE.

He also was kind enough to submit a link Empty Image Tags Can Destroy Your Site. Its a good read, with some possible solutions to the double page request issue.


Ordered Hashes In Ruby 1.8

Posted on December 17, 2009

So I came across a situation where I needed an ordered hash. After doing some research and talking to the #rubyonrails guys I've found I'm far from the only one who needs this, and in Ruby 1.9 there is a OrderedHash class. But until then I had to create my own, and with the help of the guys in #rubyonrails I finally got something working

# lib/ordered_hash.rb
class OrderedHash < Hash
  def initialize
    @keys = []
    super
  end

  def []=(key, value) 
    @keys << key unless member?(key)
    super
  end

  def each
    @keys.each {|key| yield key, self[key]}
  end

  def delete(key)
    @keys.delete key
    super
  end
end

Usage is as follows!

# Usage:
h = OrderedHash.new
h[2] = 3
h["x"] = "y"
h.delete(3)
h.each {|key| p key}

Yea its pretty simple, but I'm still pretty new at all of this, so I chalk this as another milestone!


Paypal Sandbox: How To Verify A Bank Account And Get The Api

Posted on December 17, 2009

After about 6 hours today of pulling my hair out and trying to verify my bank account on my sandbox, I finally stumbled across a post that helped me do this

For everyone else that is having a hard time getting the bank account to be verified, the secret is the BSB number. Do they tell you this anywhere? Well maybe, but do we look, hell no! Here are some step by step instructions on getting it working!

Prerequisites:

  • Create a user account to access the Developer Central Sandbox.
  • Log in to the Sandbox account
  • Create a Test Account of type 'seller'.
  • This will throw an error message on saving, but it will still create the account.
  • Click the Test Accounts tab to see the account it created.

Create Confirm the email address:

  1. Create business test account (done in prerequisite above)
  2. Log into PayPal Sandbox with test business account details created earlier.
  3. Go to Profile Tab
  4. Go to Email
  5. Choose the email address and click 'Confirm'
  6. Go back to the Sandbox Test Environment and click the Test Email tab
  7. Open the email with subject line 'Activate Your PayPal Account'
  8. From the email body, select the confirmation URL and paste into new browser window - it will load the PayPal site (in sandbox mode).
  9. Log in with business account.
  10. From the landing page, click the 'confirm email' link in the 'To Do List' box.
  11. Click the 'click here if a link doesn't appear in the email' link.
  12. Paste in the confirmation number from the email in step 8, and click confirm. A confirmation of success should be displayed.
  13. Click Continue.

    Create Bank Accounts For Test Business Account:

  14. Click the Profile tab

  15. Click Bank Accounts link
  16. Create a fake bank account. Test Bank. Use BSB 242-200 for Australia. Any random integer for account number, so long as it's not already being used by another PayPal account. I ended up having to mash the keypad until I found an unused number.
  17. Confirm the bank account by clicking the 'confirm' link after the step above.
  18. Step 17 loads another page where you need to click 'Submit'.
  19. Step 18 loads another page where you need to click 'I Agree'.
  20. The account should now be verified.
  21. Click the Profile Tab again.
  22. Click API Setup
  23. Click Request API Credentials
  24. Select API Signature
  25. Click I Agree, Click Submit.
  26. Copy the details to a safe place for later reference.

    Developer Central Sandbox

  27. Go back to the Developer central sandbox and login with your account details created in the prerequisite section of this document (i.e., not your test business account, but your actual Developer Central Sandbox account).

  28. The Test business account should now be verfied.
  29. Click API Credentials tab.
  30. The API credentials obtained in step 26 should appear.

Using To Param Ftw

Posted on December 17, 2009

I have a client who draws comics. He wanted the url to be /comics/12-02-2008 (where 12-02-2008 was the date the comic was published). I tried for a long while, and finally got my routes to do :controller/:action/:published_on instead of the standard :id. I did this only for the comics controller, and all others had :controller/:action/:id.

I kicked myself when I found to_param!

class Comic < ActiveRecord::Base
    def to_param
        "#{id}-#{published_on}"
    end 
end

This allows me to convert the :id symbol to :published_on. Much nicer than the old crusty routes!


Active Resource To A Php App

Posted on December 17, 2009

Lately I've been getting into rails, what can I say its a nice clean language. I also run a forum, its a forum written in php. Now I want to build apps for my php (vbulletin) forum, but I didn't want to do cross site database calls. I wanted my plug ins to be as independent as possible from the main vbulletin app..

In another website I've been writing, there are three components that interconnect to each other using activeresource! Since it was so easy to implement I thought I'd give it a go cross programming language! Heres how I did it!

First in PHP create your restfull program! I called mine rest.php :) I know very original!

Using a sexy XML library I created for php. From this simply class, I can easily create from a select from a database my xml!

<?php
    $userid = $_GET['user'];
    $result = $db->query_read("select userid, username, email from user where userid = '$userid' ");
    $count = mysql_num_rows($result);

    $xml = new XmlBuilder();
    $xml->filename("user.xml");

    for($i=0; $i<$count; $i++) {
        $user = $db->fetch_array($result);
        $xml->openTag("user");      

        foreach ($user as $tag => $value) {
            $xml->addfield($tag, $value);
        }
        $xml->closeTag();
    }
    echo $xml->build();
?>

So far so good! So if we go to file.php?user=1 we should get the user with id 1.. You could add all sorts of cool things here. But this is just an example to get you guys going.

Now finally ensure you have mod_rewrite working in apache! now if your php file is called rest.php, you'll need this rewrite rule to make the magic happen. Just add it to a .htaccess file, or in your vhost

RewriteRule ^/rest/users/(.*) /rest.php?user=$1 

In your rails app, now create a model named User, e.g. models/user.rb, and add

class User < ActiveResource::Base
  self.site = "http://domain.com/rest/"
end

and don't forget your corresponding controller. This will now fetch all requests from our rest.php file. If you need any help, please drop me a line!

Now for our rails app

in our model if we have set up

class User < ActiveResource::Base
  self.site = "http://domain.com/rest/"
end

And we can simply do this in our rails app! You'll see below the find method on the user model, and the resulting url it'll call.

User.find(:all)      # http://domain.com/rest/users.xml
User.find(1)        # http://domain.com/rest/users/1.xml
User.find(100)    # http://domain.com/rest/users/100.xml

So if your doing an :all, the app will look for model.xml. If your doing any other call, it'll look for model/?.xml. I hope this helps when building external active resource communicators.


Ruby On Rails In Gedit

Posted on December 17, 2009

There are a few tutorials out there to make gedit act like textmate..I usually use textmate, but there are some occasions where it just isn't possible, so I go back to gedit while on linux.

Heres how to rubify your gedit!

First Install the plugins for gedit

sudo apt-get install gedit-plugins

Next, visit Insane Terminology as he has a good explanation on how to get syntax highlighting working in gedit! I'll summarize for the impatient.

If you have any installed mime packages in Override.xml remove them

rm ~/.local/share/mime/packages/Override.xml 

Next we go to where the real mime packages/definitions are stored and download the mime type definitions courtesy Insane Terminology

cd /usr/share/mime/packages
sudo wget http://robzon.kapati.net/rails/rails.xml 
sudo update-mime-database /usr/share/mime

Now to finish it up, we'll get syntax highlighting corrected for .rhtml (.html.erb) files.

cd /usr/share/gtksourceview-2.0/language-specs/
sudo wget http://robzon.kapati.net/rails/rhtml.lang

Theres a heap of plugins for gedit, search the net for them, they transform gedit from windows notepad to a real ide. One thing I will mention, I have created shortcut on my top panel (taskbar, or whatever you want to call it). I've edited the gedit properties (i.e. right click --> properties) and changed

gedit %U

to

gedit %U --new-window

This allows multiple gedit windows