Active Record Random

August 30, 2009

Are you looking for a way to select a random record that plays nice with named scope in your Rails app? Throw this into config/initializers/active_record_random.rb:

class ActiveRecord::Base
  def self.random
    if (c = count) > 0
      first(:offset => rand(c)) 
    end
  end
end

Now you can call random the same way you would call first or all. That means you can do Widget.random to get a random widget or Widget.fancy.random, to get a random fancy widget, assuming you have defined the fancy named scope for Widget.

Posted in Technology | Tags NamedScope, RubyOnRails, Ruby, Rails | 6 Comments

Zip Code Proximity Search with Rails

June 27, 2009

So you're building the next big social networking website using Rails and like all the other hip kids you are going to need to allow your users to search for other users near them. The fancy term for this is "Proximity Search". For our search, we just want be able to find other people that are generally within some radius, like 5, 10 or 25 miles. For this, there is no need to geocode the address for each user in our database, we'll just use their zip code. So effectively, in our system, every user's location is just the center point of their zip code.

For starters we want to create a zip code model:

script/generate model zip code:string city:string state:string lat:decimal lon:decimal

That will create a model and a migration. You need to alter the migration to specify the precision and scale for the lat and the lon.

t.decimal :lat, :precision => 15, :scale => 10
t.decimal :lon, :precision => 15, :scale => 10

So to populate this database, luckily the good people over at the US Census Bureau have the data readily available for us. I've created a rake task to download and load that data into your zips table. Simply put the load.rake file from this gist into the lib/tasks directory of your Rails app.

So now when you run rake load:zip_codes you should see something like:

== Loaded 29470 zip codes in ( 1m 40s) ========================================

Next we need a table for our users. So let's generate a model and a migration:

script/generate model user

I'll save you the hassle of typing out all the fields at the command-line and just give them to you here. Paste this into the create_users migration that was generated:

t.string   :username
t.string   :email
t.string   :password
t.string   :password_confirmation
t.string   :first_name
t.string   :last_name
t.string   :address
t.string   :city
t.string   :state
t.integer  :zip_id    

Next you need to hook up the relationship between the zip and the user. This is basic stuff, the zip has many users and the user belongs to a zip.

Now we need some users to play with. A great tool for this is Mike Subelsky's Random Data gem. I've already created a rake task that uses this gem to create some test user accounts. You call it like this:

rake load:random_users[10000]

The 10000 is the number of users we want the rake task to generate for us. Did you know you can pass command-line arguments to a rake task like that? Pretty spiffy. 10000 is a pretty good number because it gives us a fairly large dataset to work with and is still able to load in a reasonable amount of time. 10000 users finished in about 6 minutes and 30 seconds for me.

Next we need to setup our methods to do the querying. For this I basically used Josh Huckabee's Simple Zip Code Perimeter Search method, but re-worked it a little so we can use named scope with it. You can grab the code for both zip.rb and user.rb from the gist.

There are a couple of things we get here. First is a named scope to easily find zip codes. Looking at the output of the loading of the random users, the last one for me was Mr. Steven Moore of Koloa, HI, 96756. So let's see how many other people are in that zip code. Start up script/console and run this:

>> Zip.code(96756).users.count
=> 1

Hmm...I guess it's lonely in Hawaii. Let's find the zip code that randomly ended up with the most inhabitants:

>> Zip.count_by_sql "select zip_id, count(*) as count 
from users group by zip_id order by count desc limit 1"
=> 18177

Ok, so that's the id of the zip record, not to be confused with the actual zip code. So let's find the first person in this zip code:

>> user = Zip.find(18177).users.first
=> #<User id: 1267, username: "cabel1266", ...>

I got Ms. Cheryl Abel of Bloomville, NY. So now for the big moment. What we really want to do is find everyone within 25 miles of Cheryl.

>> user.within_miles(25).count(:all)
49

Looks like Cheryl has 49 people nearby. Let's see who they are:

>> user.within_miles(25).all.each{|u|
?> puts "%.2f %20s, %2s, %5s" %
?> [u.distance, u.city, u.state, u.zip.code]}
0.00           Bloomville, NY, 13739
0.00           Bloomville, NY, 13739
0.00           Bloomville, NY, 13739
0.00           Bloomville, NY, 13739
0.00           Bloomville, NY, 13739
7.04            Worcester, NY, 12197
7.04            Worcester, NY, 12197
7.43             Maryland, NY, 12116
8.09             Meredith, NY, 13753
8.54            De Lancey, NY, 13752
8.71     Livingston Manor, NY, 12758
9.11             Roseboom, NY, 13450
9.88          Jordanville, NY, 13361
...

So there you have it! I'm still trying to work out some kinks with this and get it to work with count and will paginate, so if you have any suggestions, fork the gist, hack away and leave a comment. I'll update this post when I get count and pagination working.

Posted in Technology | Tags Ruby, Rails | 2 Comments

The Busy Rails Developer's Intro To Rake

April 20, 2009

This is just a quick 60 second intro to Rake. If you are doing development with Ruby on Rails, you undoubtedly use Rake on a daily basis. For example, you use rake db:migrate to run your migrations or rake test:units to run your unit tests. But do you know how to write your own Rake tasks? If you never have done that, you might hesitate to write a rake task instead thinking you ran just write a quick ruby script rather than take the time to figure out how to write a Rake task.

So to create a rake task, in an existing Rails app, create a file called lib/tasks/app.rake. You can name it whatever you want as long as it ends in .rake. I'm choosing to use app because we are going to write some app-specific tasks. In the file, put this:

task :hello_world

Now from the command line you can run your task with rake hello_world. Nothing happens, but it runs. Now let's have it print hello world:

task :hello_world do
  puts "Hello, World!"
end

Now when you run your task, it prints "Hello, World!". Let's add a description to our task to let people know what it does:

desc "Prints 'Hello, World!'"
task :hello_world do
  puts "Hello, World!"
end

Now if you run rake -T, you will see your helloworld task in the list of tasks. rake -T only shows tasks that have a description. You can also run `rake -D helloworld` to see the full description of the task. You should give all of your tasks that you expect users to run from the command-line a description.

Now a problem with our task is what happens if someone else wants to write a task named hello_world? Well, we would have a namespace problem. So what we want to do is put all of our tasks into the app namespace:

namespace :app do
  desc "Prints 'Hello, World!'"
  task :hello_world do
    puts "Hello, World!"
  end
end

So now we can run our task as rake app:hello_world.

So this is obviously not a real task. Let's say we want to know what the load path of our app looks like. Easy, we'll just do this:

namespace :app do
  desc "Prints load path of this app"
  task :load_paths do
    Rails.configuration.load_paths.each do |p|
      puts p
    end
  end
end

When you try to run this rake tast, you will get this error:

rake aborted!
uninitialized class variable @@configuration in Rails

The problem is that by default, a rake task doesn't load the Rails environment. It's easy to tell it to do that with this:

namespace :app do
  desc "Prints load path of this app"
  task :load_paths => :environment do
    Rails.configuration.load_paths.each do |p|
      puts p
    end
  end
end

By saying :load_path => :environment, you are saying that the load_path task depends on the environment being loaded. Or more specifically, you are saying "run the environment task before running this task". There is a task called "environment", and it loads the Rails environment. You won't see it under rake -T, because it has no description because it is not a task you should run directly, only as a dependency of other tasks.

Now that we have the Rails environment loading, when you run the rake task, you will get the output you expect. If you make your task depend on environment, you will also be able to access your models from within your task. Now that you know the basics of Rake, you can easily get going making Rake tasks for your Rails app.

Posted in Technology | Tags Ruby, Rails, Rake | 8 Comments

Talk on BrowserCMS from Acts As Conference

February 13, 2009

Last weekend, Patrick Peak and I had the opportunity to speak at Acts As Conference on our experience moving BrowserCMS from Java to Rails. The talk was recorded by Confreaks, check it out!

Posted in Technology | Tags BrowserCMS, Ruby, Java, Rails | 2 Comments

My Git Workflow

January 20, 2009

Inspired by Michael Ivey's recent "My Git Workflow" article, I've decided to write up one of my own. Really this article should be titled "My Git SVN Workflow", because that's how I use it most of the time. At BrowserMedia, we use Subversion (SVN) for version control. I'm not an advanced user of Git by any stretch of the imagination, but here's my experience from switching from SVN to Git.

First off, for small projects that I am doing on my own, I use just straight git, and back it up to my own server as I described in the Quick and Dirty Git Repository article. For stuff that I want to be publicly visible, I push it to github. But for BrowserMedia internal projects, here's what I do.

Assuming it's a rails project, I create the rails app and import it using SVN:

$ rails myproject -d mysql
$ cd rails
$ svn import -m "Initial Rails 2.2.2 App" https://svn.browsermedia.com/myproject/trunk
$ cd ..
$ rm -rf myproject
$ svn co https://svn.browsermedia.com/myproject/trunk myproject
$ svn remove log/*
$ svn commit -m "removing all log files from subversion"
$ svn propset svn:ignore "*.log" log/
$ svn update log/
$ svn commit -m "Ignoring all files in /log/ ending in .log"
$ svn remove tmp/*
$ svn propset svn:ignore "*" tmp/
$ svn update tmp/
$ svn commit -m "Ignoring all files in /tmp/"
$ cd ..
$ rm -rf myproject

Most of this you will recognize from the Rails Wiki article on How To Use Rails With Subversion. So now the project is setup so that anyone can use SVN to work on the project.

One of the cool things about Git is that you can use Git SVN to work with git locally and push your changes to the main SVN repository. I've been using this for months now with no problems. So now let's start working on our project with git:

$ mkdir myproject
$ cd myproject
$ git svn init -s https://svn.browsermedia.com/myproject

Couple of things to note here is that first you have to make the directory for your project first, then change into the directory and run the git command from there. Also, the URL you use is the directory that contains trunk (and possibly branches and tags, if you have created those). The -s means you are using a standard SVN layout. Once you have done this, you run this command to sync up your local repository with SVN:

$ git svn fetch

In this case, this is a fresh project, so if should go pretty quick, but if you are doing this with an existing larger project, this could take quite some time, because this is not just fetching the latest version of the code, but the entire history of the repository.

To make working with git a little easier, I have some aliases defined to make things go a bit quicker:

alias gs='git status'
alias ga='git add .'
alias pull='git pull'
alias push='git push'
alias gc='git commit -m'
alias gca='git commit -a -m'
alias svnpull='git svn rebase'
alias svnpush='git svn dcommit'

And then most of the projects I work on are small teams, so I follow Michael's small team workflow pretty closely:

$ svnpull
Hack some stuff
$ gs
look at what's changed
$ git add .
$ gca "hacked some stuff"
$ svnpush

So the first thing I usually do after git svn fetch is to add a .gitignore file to the project that looks something like this:

.DS_Store
log/*.log
tmp/**/*
db/*.sqlite3
db/schema.rb

One nice thing I've observed about using Git over SVN is the way it handles directories of files. Git puts one .git directory at the top level of your project, which is where it keeps the info about the repository, including the local repository itself. SVN puts a .svn directory in every directory of your project. This only has the info about the repository, which files you have checked out, etc. There is no local repository of course, the repository is on the server.

A case where this makes things easier is managing external dependencies. Let's say you have a gem that you can't or don't want to install on your server. For most gems I recommend installing them on the server, but in some cases that doesn't always work. So in that case, you do rake gems:unpack to put the gem into the vendor directory. Now let's say you need to update that gem. Again, this is not the normal case, but if you are creating your own gems for your project, you might want to do this. Here's what you can do with git:

$ cd important_gem
$ rake install #installs version 1.0.0 of important_gem locally
$ cd ../myproject
$ rm -rf vendor/gems/important_gem-1.0.0
$ rake gems:unpack

The case here is that myproject already has a version of important_gem-1.0.0 checked in, but you've modified important_gem-1.0.0, so you want to get those updates into myproject. You only do this in a situation where you haven't released version 1.0.0 of important_gem yet.

Now if you run gs (short for git status), you will see that git has just figured out what happened. Whether files were removed, renamed, added or modifed, git knows. You can just run git add . and then gca "updated important_gem" and all is well. No need for SVN externals. With SVN, if you wanted to manage your project like that, you are kind of out of luck. The reason is that when you did rm -rf vendor/gems/important_gems-1.0.0, all of the .svn directories got destroyed. After rake gems:unpack, the files are back, but not the .svn directories, hence SVN is confused and frankly, I never did figure out what you are supposed to do about this.

There are other cases where this is useful besides working on a gem. For example, with BrowserCMS were are using FCKEditor, which comes in a zip file that you just unpack into public/fckeditor. To put in a new version of FCKEditor, you can just do:

$ cd public
$ rm -rf fckeditor
$ mkdir fckeditor
$ cd fckeditor
$ unzip ~/Downloads/fckeditor-x-y-z.zip
$ cd ../..
$ gs
$ git add .
$ gca "Upgraded to FCKeditor version x.y.z"

There are probably some errors or inefficiencies in this process and if so, let me know in the comments, but I really like working with Git SVN, which allows me to have my cake and eat it too.

Posted in Technology | Tags Git, SVN, Subversion, Rails | 2 Comments

<< Newer Articles   Older Articles >>