Posts Tagged ‘developers’

Many-to-Many Relationships in Ruby on Rails

January 16, 2010

Many-to-many relationships in Rails can be confusing at first. There are a couple different ways to handle them, depending on what you need. Let’s say you want to organize your mp3 collection. Artists obviously have multiple songs, but songs also have multiple artists.

has_and_belongs_to_many

The simpler way to do this is using HABTM (has_and_belongs_to_many) to define your relationships. Here are the models:

# app/models/song.rb
class Song < ActiveRecord::Base
  has_and_belongs_to_many :artists
end

# app/models/artist.rb
class Artist < ActiveRecord::Base
  has_and_belongs_to_many :songs
end

And here are the migrations:

# db/migrate/create_artists.rb
class CreateArtists < ActiveRecord::Migration
  def self.up
    create_table :artists do |t|
      t.string :name
      t.timestamps
    end
  end

  def self.down
    drop_table :artists
  end
end

# db/migrate/create_songs.rb
class CreateSongs < ActiveRecord::Migration
  def self.up
    create_table :songs do |t|
      t.string :name
      t.timestamps
    end
  end

  def self.down
    drop_table :songs
  end
end

# db/migrate/create_artists_songs.rb
class CreateArtistsSongs < ActiveRecord::Migration
  def self.up
    create_table :artists_songs, :id => false do |t|
      t.integer :artist_id
      t.integer :song_id
    end
  end

  def self.down
    drop_table :artists_songs
  end
end

Notice a couple things about the migrations. First, I had to manually create a join table migration. Its name must be a combination of the two tables being joined, in alphabetical order. This is how Rails is able to figure out the join table’s name automatically. Also notice that I had to specify :id => false in the join table definition. Primary keys in join tables cause big problems.

Now, if I have an object song that is an instance of the Song class, I can say song.artists and get a list of the artists associated with this song. I can do the same for artists. Yay!

has_many :through

What if we wanted to track what instrument the artist played on a given song? This doesn’t go in the artists table, because it’s different for every song. It doesn’t go in the songs table either, because each artist probably played something different. It needs to go in the join table, but there’s a problem. There’s no way in your Rails code to access the information stored in the join table itself.

What we really have here is more than just a join table – it’s another model all its own. Let’s call it a contribution. Each artist’s contribution to a song is unique. I might play nose flute in a rendition of Herbie Hancock’s Watermelon Man, but I play the slide whistle when recreating the theme song from the Krusty the Klown show.

Recreating the models from scratch looks like this:

# app/models/artist.rb
class Artist  < ActiveRecord::Base
  has_many :contributions
  has_many :songs, :through => :contributions
end

# app/models/contribution.rb
class Contribution < ActiveRecord::Base
  belongs_to :artist
  belongs_to :song
end

# app/models/song.rb
class Song < ActiveRecord::Base
  has_many :contributions
  has_many :artists, :through => :contributions
end

The migrations look like this:

# db/migrate/create_artists.rb
class CreateArtists < ActiveRecord::Migration
  def self.up
    create_table :artists do |t|
      t.string :name
      t.timestamps
    end
  end

  def self.down
    drop_table :artists
  end
end

# db/migrate/create_contributions.rb
class CreateContributions < ActiveRecord::Migration
  def self.up
    create_table :contributions do |t|
      t.references :artist
      t.references :song
      t.string :instrument
      t.timestamps
    end
  end

  def self.down
    drop_table :contributions
  end
end

# db/migrate/create_songs.rb
class CreateSongs < ActiveRecord::Migration
  def self.up
    create_table :songs do |t|
      t.string :name
      t.timestamps
    end
  end

  def self.down
    drop_table :songs
  end
end

I didn’t have to manually create the join table this time. I created the three models calling script/generate, and the tables were created automatically. Now, with an object artist that is an instance of the Artist class, I can get a list of contributions with artist.contributions. I can get a list of songs just as easily with artist.songs, and Rails will find them through the contributions table. If I want the instrument the artist used on the first song, I can say artist.songs[0].contribution.instrument, and so on.

Deciding Which to Use

If two models have a many-to-many relationship and you need to track info about the relationship itself, use has_many :through. In fact, sometimes it’s wise to do this anyway, if you anticipate needing that later. HABTM should only be used when the relationship itself will never need to track data of its own.

Faster Git with Aliases (or is it Aliii?)

January 15, 2010

With git’s easy branching, I’ve gotten into a pretty good rhythm. Every time I start a task (more than just a quick fix) I do this:

git branch iliketurtles
git push origin iliketurtles
git branch -f iliketurtles origin/iliketurtles
git checkout iliketurtles

I’m basically starting a new local branch, pushing it to the remote repository, connecting the two for tracking, and checking out the local copy to work on. You can see that this is a lot of repetition, regardless of your feelings toward turtles. That’s where git’s aliasing comes in. Add these lines to the .gitconfig file in your home directory:

[alias]
    launch = !sh -c 'git branch $1 && git push origin $1 && git branch -f $1
 origin/$1 && git checkout $1' -

It’s a long line, granted. It’s basically doing all the things listed above, in order. It’s like a slightly more complicated version of a shell alias, but with the benefit of…I guess being able to tell people you’ve written custom git aliases. Anyhow, I can now replace the four lines of git commands above with this:

git launch iliketurtles

The local and remote branches have been created and connected, and I’m now on the new branch. When I’m done with my changes I merge back into the main branch. From there, I need to wipe the temporary branch out of existance, like so:

git push origin :iliketurtles
git branch -r iliketurtles

I was using "git branch -dr iliketurtles" to remove the remote branch, until I realized it still leaves the branch on the remote repository. It only removes your ability to see it. This is the same approach taken when directors use softer lighting on Madonna. "git push origin :iliketurtles" removes the old branch completely. This is the same approach taken when MTV starts replacing Madonna videos with Lady GaGa. Still painful, but with less baggage.

Restful Controller Tests with Shoulda

December 30, 2009

View the Source Code

This is part 1 of a 5 part series on restful controller tests, using Shoulda as the foundation. Here are all of them so far:

  1. The Basics
  2. Stubbing for Speed

After porting eight MVC stacks from an older application to its newer home, and revamping all the tests along the way, I developed a great rhythm in cranking out functional tests for restful resources. I took the time to use the best that modern shoulda has to offer, and came up with (what I feel) is a great set of functional tests.

First, each controller action gets its own context, with several tests executed in each. But wait, there’s more! Do you have multiple roles? I usually have visitor (not logged in), member (logged in, normal user) and admin (logged in, superuser). That means triple the testing, because I want to confirm that the application is behaving the correct way under each circumstance, and the behavior is often very different.

Next, a little background. For authentication this app is using restful_authentication and role_requirement. My basic testing suite consists of shoulda, factory_girl, and woulda. I’m also using autotest, mocha, test_benchmarker, and redgreen to speed up my tests and development cycle, but I’ll cover those in a different article. This is about the basics.

Let’s begin (finally) with a restful controller for site settings – admins have all access, but members and visitors have none. This will allow me to demonstrate how I handle testing each role, and it’s easy to grant more privileges to a role by copying over the tests from say, admin to member.

Admin Tests

I find it’s easier TDD to start with the least restrictive role and work backward, adding restrictions to my controller as I go along. That said, here are my admin tests. I first setup the admin context like so:

  context "as admin" do
    setup do
      @setting = Factory.create(:setting)
      @valid = Factory.build(:setting).attributes
      login_as :admin
    end

    ...
  end

I’ve created a basic setting (using factories), and a hash of valid attributes I can use for create and update actions. I’ve also logged in as admin. If you don’t understand contexts and setup, a context is a group of tests, and the setup for that context is done before every test within. Contexts can also be nested – and they will be.

Index

  context "getting index" do
    setup do
      get :index
    end
      
    should_assign_to(:settings){[@setting]}
    should_respond_with :success
    should_render_with_layout
    should_render_template :index
    should_not_set_the_flash
  end

Using shoulda’s awesome macros, I setup a simple get :index which will run before each shoulda macro, then go nuts testing that @settings is assigned an array with my one and only existing setting. The page responds with success, it renders the default layout with the index template, and doesn’t set the flash. These five tests are what you should think about for every action, under every role. This is the foundation, and you add stuff from here as you add more complicated functionality to the basic scaffold.

New

  context "getting new" do
    setup do
      get :new
    end
      
    should_assign_to :setting, :class => Setting
    should_respond_with :success
    should_render_with_layout
    should_render_template :new
    should_not_set_the_flash
  end

This is very similar to index above. Now, I’m testing that @setting (singular) is set, with a single object of class Setting. This is as specific as I can test right now, because this was a newly created object in the action – so I can’t test that a specific setting was called. The rest is nearly identical to the index action, expect the template I expect to be rendered.

Create

  context "posting create" do
    context "with valid data" do
      setup do
        post :create, :setting => @valid.merge('name' => 'Slappy')
      end
        
      should_assign_to :setting, :class => Setting
      should_redirect_to("index page"){settings_path}
      should_set_the_flash_to "Your setting was successfully saved."

      should "create the record" do
        assert Setting.find_by_name('Slappy')
      end
    end
      
    context "without valid data" do
      setup do
        post :create, :setting => {}
      end
        
      should_assign_to :setting, :class => Setting
      should_respond_with :success
      should_render_with_layout
      should_render_template :new
      should_not_set_the_flash
    end
  end

Now we’re getting a little more complex. We have two outcomes to think about: a successful create, and a failed one. We have to react differently, so we have to test for both.

The first sub-context tests for success. We’re passing in a valid hash of attributes. Notice our tests look different this time – and easier on the fingers. Instead of 5 basic tests, we have 3. We still check that a Setting object was created/assigned, but instead of checking response codes and rendered content, we’re expecting the action to redirect to the index page. And this time, we are expecting a flash message to be set. Finally, I’m checking that the record was actually created. I forced the name attribute to be ‘Slappy’ because no matter how my test suite changes in the future, that name is unlikely to already be in the database.

The next sub-context tests for failure – most likely from invalid input on the part of the user. It happens. We’ve passed an empty attribute list to the action, which should ensure failure if we have at least one required attribute with validates_presence_of in the model. In my next article I’ll cover mocks and stubs for a cleaner way. Our tests are back to the familiar – a setting object should be assigned, and we should expect an HTTP response of success. Our create wasn’t successful, but that’s not what we’re testing here. We’re testing the HTTP response, and success indicates there wasn’t an error at the HTTP level. Next, we’re rendering the new template again, for the user’s second try. We’re not setting the flash though, because you’re probably going to list off the individual errors anyway.

Edit

  context "getting edit" do
    setup do
      get :edit, :id => @setting.id
    end
    
    should_assign_to(:setting){@setting}
    should_respond_with :success
    should_render_with_layout
    should_render_template :edit
    should_not_set_the_flash
  end

This is back to the short-and-sweet familiar. This is almost like our new action, with a couple exceptions. First, we can now specify (by passing a block to the should_assign_to macro) the specific setting we’re expecting, because we specified it in our request. Also, we’re expecting the edit template to be rendered.

Update

  context "putting update" do
    context "with valid data" do
      setup do
        put :update, :id => @setting.id, :setting => {:name => 'Slappy'}
      end
      
      should_assign_to(:setting){@setting}
      should_redirect_to("index page"){settings_path}
      should_set_the_flash_to 'Your knowledge base was successfully updated.'

      should "update the record"
        assert Setting.find_by_name('Slappy')
      end
    end
    
    context "with invalid data" do
      setup do
        put :update, :id => @setting.id, :setting => {:name => ""}
      end
      
      should_assign_to(:setting){@setting}
      should_respond_with :success
      should_render_with_layout
      should_render_template :edit
      should_not_set_the_flash
    end
  end

You can see we’re doing the same things here that we did with our create action. When calling update however, we can pass only the attributes we want to change. I check that it *has* changed, too.

We have to pass a hash with a piece of bad data to get a failure, and in this case just assume that name is an attribute that can’t be blank.

Destroy

  context "deleting" do
    setup do
      delete :destroy, :id => @setting.id
    end
    
    should_assign_to(:setting){@setting}
    should_redirect_to("index page"){settings_path}
    should_set_the_flash_to "Your knowledge base was successfully deleted."
    
    should "delete the record" do
      assert !Setting.find_by_id(@setting.id)
    end

This is pretty self-explanatory. We’re assigning, redirecting, and spitting out a nice flash message. We’re also doing a hard check to make sure the setting object is gone.

Members

From here, the tests get a lot easier to absorb. I’ll list the whole member context here:

context "as a member" do
  setup do
    login_as :quentin
  end
  
  context "attempting to get index" do
    setup do
      get :index
    end
    
    should_not_assign_to :settings
    should_respond_with 401
    should_render_with_layout false
    should_not_set_the_flash
  end

  context "attempting to get new" do
    setup do
      get :new
    end
    
    should_not_assign_to :setting
    should_respond_with 401
    should_render_with_layout false
    should_not_set_the_flash
  end

  context "attempting to create" do
    setup do
      post :create, :setting => {}
    end
    
    should_not_assign_to :setting
    should_respond_with 401
    should_render_with_layout false
    should_not_set_the_flash
  end

  context "attempting to get edit" do
    setup do
      get :edit, :id => 1
    end
    
    should_not_assign_to :setting
    should_respond_with 401
    should_render_with_layout false
    should_not_set_the_flash
  end

  context "attempting to update" do
    setup do
      put :update, :id => 1, :setting => {}
    end
    
    should_not_assign_to :setting
    should_respond_with 401
    should_render_with_layout false
    should_not_set_the_flash
  end

  context "attempting to delete" do
    setup do
      delete :destroy, :id => 1
    end
    
    should_not_assign_to :setting
    should_respond_with 401
    should_render_with_layout false
    should_not_set_the_flash
  end
end

The only setup I need is to login as a regular user, in this case the default “quentin” that is created by restful_authentication. Notice every action has basically the same four tests: nothing is assigned, a 401 (unauthorized) HTTP error is returned, no layout is rendered, and no flash is set. In my action calls, I can use fake id numbers and empty attribute hashes, because they’ll never be checked anyway. So, no need to create an actual setting object.

By default, role_requirement shows a blank page, but I changed that to display a special 401.html file I created. This is a person who is already logged in, so we know who they are (authentication) but does not have permission to be where they are (authorization).

Visitors

context "as a visitor" do
  context "attempting to get index" do
    setup do
      get :index
    end
    
    should_not_assign_to :settings
    should_redirect_to("login"){new_session_path}
    should_set_the_flash_to "You need to login to do this."
  end

  context "attempting to get new" do
    setup do
      get :new
    end
    
    should_not_assign_to :setting
    should_redirect_to("login"){new_session_path}
    should_set_the_flash_to "You need to login to do this."
  end

  context "attempting to create" do
    setup do
      post :create, :setting => {}
    end
    
    should_not_assign_to :setting
    should_redirect_to("login"){new_session_path}
    should_set_the_flash_to "You need to login to do this."
  end

  context "attempting to get edit" do
    setup do
      get :edit, :id => 1
    end
    
    should_not_assign_to :setting
    should_redirect_to("login"){new_session_path}
    should_set_the_flash_to "You need to login to do this."
  end

  context "attempting to update" do
    setup do
      put :update, :id => 1, :setting => {}
    end
    
    should_not_assign_to :setting
    should_redirect_to("login"){new_session_path}
    should_set_the_flash_to "You need to login to do this."
  end

  context "attempting to delete" do
    setup do
      delete :destroy, :id => 1
    end
    
    should_not_assign_to :setting
    should_redirect_to("login"){new_session_path}
    should_set_the_flash_to "You need to login to do this."
  end
end

Visitors are different from members without access. We want to be nicer and assume visitors just aren’t logged in yet, if they try to access protected actions. So we simply redirect them to the login page with a nice message. Just as with members, there’s no need to create actual objects in the database, or give actual id numbers or attribute hashes, since the action will never get far enough to check.

Conclusion

Once I developed this basic set of tests, I was pleased with how consistent my controller coding became. I don’t have a generator for these, I code them by hand every time. It keeps me sharp, and doesn’t take that long when you get into the swing of it. This isn’t the final draft, however. I’ve since modified this template with mocking and stubbing to greatly improve the speed, cutting runtime to a quarter of what it was. I’ll write about that approach in my next article.

Files

Here are Pastie links to my sample controller test and controller files. My controller is mostly vanilla scaffold with all the format junk stripped out, and a couple of key protected methods at the end.

settings_controller_test.rb
settings_controller.rb

Setting the flash in tests

December 27, 2009

I recently needed to test a thankyou page for a form. The previous action sets a flash value, flash[:email], and then redirects to this page. Testing the page by itself requires that this flash be set. Here’s how to do it:

get :thankyou, {}, nil, {:email => 'test@test.com'}

This will ensure that when the page is called, it has the correct flash elements set. This is important because we’re calling the page directly, not redirecting from a previous action that would have set the flash automatically.

The test methods to call actions (get, post, put, delete) actually take four parameters, even though we typically only use two. The first is the action, and the second is the parameter list. We normally don’t put curly braces around the parameter list because Ruby is smart enough to know that if it sees a bunch of key-value pairs at the end of a method call, it should scoop them up into a single hash. We can’t do that for our example because parameter, session, and flash data need to be passed in different hashes.

For more details visit the same page I did, A Guide to Testing Rails Applications.

undefined local variable or method `should_act_as_list’

December 26, 2009

I got this error today, which was a surprise because I was porting over good, tested code from an old version of an application. To recreate the error after solving, I built a simple test app with a single model, SantasList. The test file looks like this:

require 'test_helper'

class SantasListTest < ActiveSupport::TestCase
  should_act_as_list
end

At first I suspected a missing gem, but environments/test.rb showed this:

  config.gem 'thoughtbot-shoulda', :lib => 'shoulda', :source => 'http://gems.github.com'
  config.gem 'seanhussey-woulda', :lib => 'woulda', :source => 'http://gems.github.com'

I half suspected that woulda wasn’t loading properly, and I was half right. I started to trace the problem in the gem itself, which I’d wisely unpacked into the application. Inside the woulda gem, I found the contents of shoulda_macros/woulda_macros.rb:

require 'woulda'

Simple enough. So in lib/woulda.rb I found the line that loads the acts_as_list macro:

  require "#{woulda_dir}/acts_as_list" if defined? ActiveRecord::Acts::List

Voila!* First, looking at the whole file shows that the author was smart, and only loads the macros that might actually be used. Second, I’ve found my problem: I don’t have the acts_as_list plugin installed! No plugin, no macro. So I was half right – the macro wasn’t loading, but it was my fault. As I said, I’m porting the application to a new code base, and I hadn’t needed this plugin yet. I’m thrilled the problem was so easy to find, and Sean Hussey’s clean code certainly helped.

As a side pitch, if you’re not using woulda yet, I highly recommend it. For shoulda users, it provides macros for the most common ActiveRecord plugins like acts_as_list, acts_as_taggable, will_paginate, paperclip, acts_as_state_machine, and more. And if you’re not familiar with shoulda macros, then why not??

*I like to say “viola” as a joke, but in print it just looks like a typo.

Use Tags in Git!

December 15, 2009

“Awe, for the love of crumbcake!” – Morris Szyslak

Recently I’ve dealt with a handful of newer open-source offerings in the Ruby on Rails community, via GitHub. Newer codebases are more prone to rapid change. Sure enough, I’ve fallen victim to changed code from one download to the next. This can cause a host of problems such as changed syntax, version clash with other plugins, or occasionally just outright bugginess.

If you’re dealing with gems, versioning is built right in, and you can usually get older versions of what you want. Plugins, engines, or base applications are a different matter. My battle cry is this: USE TAGS IN GIT!

First, if you’re using someone else’s code on GitHub, look for the tab that says “All Tags”, and see if there are specifically released versions. This is helpful if you downloaded the latest version and realized it’s totally different from the one you know and love. You can select the version you want, and you have two options from here. Click “Download” to choose the specific version you want, especially if you don’t want the full repository with commit histories, etc. Or, clone the entire app, and switch versions locally. The Spree shopping cart allows this:

bellmyer$ git clone git://github.com/railsdog/spree.git test-spree
Initialized empty Git repository in &lt;...&gt;
remote: Counting objects: 28927, done.
remote: Compressing objects: 100% (10583/10583), done.
remote: Total 28927 (delta 17195), reused 28757 (delta 17040)
Receiving objects: 100% (28927/28927), 13.08 MiB | 1840 KiB/s, done.
Resolving deltas: 100% (17195/17195), done.
bellmyer$ cd test-spree/
test-spree$ git checkout v0.9.0
Note: moving to 'v0.9.0' which isn't a local branch
If you want to create a new branch from this checkout, you may do so
(now or later) by using -b with the checkout command again. Example:
  git checkout -b 
HEAD is now at 78f0b8c... fix typo on option
test-spree$ git branch
* (no branch)
  master

Git will inform you (as it did above) that you can’t actually edit the code since the tag is a static snapshot of the repository. However, it also tells you how to create a new branch based on this tag if you are interested in making changes. Easy!

Now, the more important part of my message. To developers who actively publish open-source code, first: thank you. Second, I offer a new TATFT: Tag All The Fucking Time! It’s easy. I’ll prove it, using my test-driven fork of Dave Currin’s Seed CMS.

seed$ git tag 0.0.1 master
seed$ git status
# On branch master
nothing to commit (working directory clean)
seed$ git push
Everything up-to-date
seed$ git push --tags
Total 0 (delta 0), reused 0 (delta 0)
To git@github.com:bellmyer/seed.git
 * [new tag]         0.0.1 -&gt; 0.0.1

In the two times I’d downloaded this app in the last month, fundamental pieces had changed, which slowed down my development. If it’d had tags already, I could have chosen to download the version I was already familiar with. I forked his repository to add tags, and to fix the test suite. Feel free to use it if you wish.

By the way, I am NOT harping on Dave Currin’s lack of tags or tests. He’s a very cool guy who admits it needs work, but wisely chose to release a very alpha version of his CMS early, rather than wait forever until it had all the polish of a mature app. I’m grateful, because that choice means I got to use it on two projects so far. Well worth it.

In conclusion, tags are so easy in Git and GitHub, both for the publisher and the user, that there’s no reason not to use them.

Reviewing Your Changes Piece-by-Piece Before Commiting, with Git

December 4, 2009

If you’ve ever worked on a tough piece of code, you know that there is some trial and error involved. You add code, try it, tweak it, sometimes remove it altogether and then attempt to change a different part. Sometimes, at the end of a whirlwind session, do you worry you left something in that eventually didn’t need to be there?

You can use the patch option of git add to verify each and every change you’ve made to your code, so you can decide which to keep and which to throw out. Just use the -p option, like so:

mysite$ git add -p
diff --git a/index.html b/index.html
index 4e5050b..da78acf 100644
--- a/index.html
+++ b/index.html
@@ -5,5 +5,8 @@

 
   <h1>Hello, World!</h1>
+  <ul>
+    <li><a href="about.html">About</a></li>
+  </ul>
 
 
Stage this hunk [y,n,q,a,d,/,e,?]? y

When you’re finished, only the changes you’ve approved will be saved at your next commit. The other changes won’t be wiped out – they’ll still be in your code for you to remove as you see fit. But they won’t be “staged” for the next commit, so they won’t make it into your repository.

Another use is when you realize you made changes that should have been committed, but now you’re partway through another feature. You can commit the finished changes, while excluding the changes that are still in progress.

Color Coding in Git

December 3, 2009

I recently started reading Pragmatic Version Control Using Git.  Some of the early stuff will be review, but I don’t have a solid foundation in the server-side aspects of Ruby on Rails, so I’m working on it.  In the first chapter, I stumbled on a real gem: Git can be configured to spit out the familiar red-green color coding that we all know and love in autotest and other red-green-refactor testing.  (You are using Test Driven Development, right? )

Enter this at a command prompt:

git config --global color.ui "auto"

That makes your git output (after making changes) look like this:

mysite$ git status
# On branch master
# Changed but not updated:
#   (use "git add ..." to update what will be committed)
#   (use "git checkout -- ..." to discard changes in working directory)
#
#	modified:   index.html
#
no changes added to commit (use "git add" and/or "git commit -a")
mysite$ git add index.html
mysite$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD ..." to unstage)
#
#	modified:   index.html
#

If this doesn’t seem like much, it’ll get much more important when you’re looking at a long list of files in different states.

Announcing validates_blacklist – a Ruby Gem for Rails

August 31, 2009

New rails gem: validates_blacklist

Last week, I had a client issue – somebody was spamming a very important web form on their site.  This form kicks off a very intricate membership process, and even integrates with SalesForce.  Luckily in this instance, the spambots were using a consistent set of e-mail addresses that we could block.  But how?  Validations seem like the obvious choice, but who wants to gunk up the model with what is essentially config data?

Or maybe you want to block new users from selecting a username that makes them sound like an admin.  You might want to disallow usernames like admin, root, staff, or variations on your own name.  Again, this would gunk up the model.

Enter validates_blacklist, a simple ActiveRecord mixin that makes this process easy.  Setup blacklist values for any model attribute.  These values can be strings, regular expressions, numbers, number ranges, and almost any evaluation you can think of.

For the Impatient

You can watch this screencast where I create a rails app from scratch, install the gem, and use it. This will give you a good idea of what it’s all about, although reading further will give you all the details.

Installation

Stick this little line in your environment.rb:

config.gem 'bellmyer-validates_blacklist', :lib => 'validates_blacklist',
  :source => 'http://gems.github.com'

Then, for fun, run these two rake tasks:

rake gems:install
rake gems:unpack

Run this command to create blacklist files for all your models in the config/blacklists/ folder:

script/generate blacklists

Don’t worry, it won’t overwrite your existing blacklist files, only create the ones you’re missing. This means you can run it after every new model is added, no worries.

Now, call this in your models:

validates_blacklist

And populate the yaml files using the example below to guide you. Have fun!

Example

As an example, we’ll imagine a high school girl named Heather. Although she has mad Ruby skillz (perhaps BECAUSE) she’s a snob. All her potential friends must submit applications for friendship via her Rails website. She uses validates_blacklist to weed out the “undesireables”.

# config/blacklists/user_blacklist.yml
email:
 - greasy_pete@yahoo.com # We kissed once when we were 5. Get over it already.
 - /@aol.com$/           # Like, yeah right, AOL is for losers!

age:
 - <14    # Junior high?  Need not apply.  - >25    # Gross, you perv!

parents_salary:
 - 50_000..150_000    # Middle class is so passe.
# app/models/user.rb
class User < ActiveRecord::Base
  validates_blacklist
end

Explanation

Basically, under each attribute (email, age, parents_salary, etc) you can list as many blacklisted values as you like. They can be a simple strings:

greasy_pete@aol.com

or regular expressions, if you wrap them in forward slashes:

/aol.com$/

or numbers (not very exciting):

5

or ranges of numbers:

5..15

or any other conditionals you can think of:

!= 'sadness'
=~ /happiness/
!~ /sad/
> 12
< 99

The only limits are your imagination, and my forethought. You can even specify
the error message that is generated:

email:
 - [stinky_pete@aol.com, cannot be greasy pete from biology]

age:
 - ['> 25', cannot be a pervert]

parents_salary
 - ['50_000..150_000', cannot be middle class]

Otherwise, the user will receive a generic message like:

Email is not allowed

Of course, you can change that for an entire model with the mixin call:

validates_blacklist :message => 'is not valid'

or per attribute:

validates_blacklist :message => "is not valid',
  :attributes => { :email => 'has been banned', :age => 'is not allowed' }

For the record, I think even a 21 year old hitting on high school girls is a pervert, but I’m trying to approach this thing from Heather’s point of view.

Running your Rails Test Database in Memory (RAM)

August 4, 2009

I recently read a blog post by Amr Mostafa that benchmarked running MySQL databases in memory. I’ve been trying to figure out how to do this, and he had the answer: use the tmpfs filesystem, which runs in memory, to store your database.  I’ll have to figure out just how difficult that is later, since I’m not a super DBA…or even really a DBA at all.

Amr is not a Rails developer, and the purpose of his benchmark was to simulate regular web traffic.  His results seemed ambiguous, but I noticed something missing in his trial: writes to the database.  His benchmark tests only used select statements, which read from the database.  While this is the majority of most database usage, I think the perfomance gain during writes would tip the scales decidedly in favor of running MySQL in memory, if you can afford the RAM.

This has an added benefit to us Ruby on Rails developers: we could potentially use it to dramatically increase the speed of our Test Driven Development, especially for those of use (should be all of us) using autotest!  TDD requires running tests every few minutes, or even seconds.  And writing to the database in tests is a lot bigger piece of the puzzle, since the database is recreated from scratch before every test.

For a lot of us, test suites are manageable.  Autotest only runs tests for changed files during normal development, occasionally running the entire test suite.  This, coupled with judicious mocking, stubbing and unit testing techniques can keep most test suites under control.  But larger apps use increasingly more tests, and higher-level tools like RSpec can be especially resource-intensive.

I tried to contact Mostafa about running some benchmarks for write speed, but my comment was considered spam!  I did get a smaller message through, so hopefully I’ll hear back.  If so, I’ll post a link to his thoughts/results.  Until then, I may dabble with doing this myself, and seeing what amateurish benchmarks I can run myself.


Follow

Get every new post delivered to your Inbox.