Mark Gandolfo's Blog
Rails Plugin: Associated With?
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
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
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
Another Cheat Sheet (PDF WARNING)
And Another Cheat Sheet (PDF WARNING)
Good luck with testing!
Restart Your Rails Server Within Textmate
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
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
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
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
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:
- Create business test account (done in prerequisite above)
- Log into PayPal Sandbox with test business account details created earlier.
- Go to Profile Tab
- Go to Email
- Choose the email address and click 'Confirm'
- Go back to the Sandbox Test Environment and click the Test Email tab
- Open the email with subject line 'Activate Your PayPal Account'
- From the email body, select the confirmation URL and paste into new browser window - it will load the PayPal site (in sandbox mode).
- Log in with business account.
- From the landing page, click the 'confirm email' link in the 'To Do List' box.
- Click the 'click here if a link doesn't appear in the email' link.
- Paste in the confirmation number from the email in step 8, and click confirm. A confirmation of success should be displayed.
Click Continue.
Create Bank Accounts For Test Business Account:
Click the Profile tab
- Click Bank Accounts link
- 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.
- Confirm the bank account by clicking the 'confirm' link after the step above.
- Step 17 loads another page where you need to click 'Submit'.
- Step 18 loads another page where you need to click 'I Agree'.
- The account should now be verified.
- Click the Profile Tab again.
- Click API Setup
- Click Request API Credentials
- Select API Signature
- Click I Agree, Click Submit.
Copy the details to a safe place for later reference.
Developer Central Sandbox
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).
- The Test business account should now be verfied.
- Click API Credentials tab.
- The API credentials obtained in step 26 should appear.
Using To Param Ftw
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
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
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