How to drop, create, populate, and reset your rails database

Leave a comment

Getting frustrated with all the commands I needed to type I created the following scripts and tools to simplify destroying and recreating my rails database.

Step 1: Create an ‘all’ task.

lib/tasks/db.rake

namespace :db do

  task :all => [:environment, :drop, :create, :migrate, :populate] do
  end

end

The basic commands for recreating and populating your database from the command line often require some variation of:

Rebuild db
$ rake db:drop
$ rake db:create
$ rake db:migrate
$ rake db:fixtures:load RAILS_ENV=test
$ rake db:test:prepare

This rake task does most of this for us. To get data into the database I create a second task shown above ‘populate’.

Step 2: Create a populate task.


require 'faker'

namespace :db do

  desc "Fill database with sample data"
  task :populate => :environment do

    puts "----------------"
    puts "RAILS_ENV is #{RAILS_ENV}"
    puts "----------------"

    puts "----------------"
    puts "--- populate ---"
    puts "----------------"

    make_cities
    make_users
    make_categories
    make_listings
  end

end

def make_cities

  puts "----------------"
  puts "--- cities   ---"
  puts "----------------"

  City.create!(:name => "Calgary")
  City.create!(:name => "San Francisco")
end

def make_users

  puts "----------------"
  puts "---- users  ----"
  puts "----------------"


  admin = User.create!(:name => "aaa",
                       :email => "aaa@aaa.com",
                       :password => "foobar",
                       :city_id => 1)
  admin.toggle!(:admin)
  2.times do |n|
    name = Faker::Name.name
    email = "example-#{n+1}@railstutorial.org"
    password = "password"
    User.create!(:name => name,
                 :email => email,
                 :password => password,
                 :city_id => 1)
  end
end

This task uses the fake gem to create and load test data into which ever environment we pass in via ‘RAILS_ENV’. It won’t work by itself yet, but we can now run this using our ‘all’ task by going:

> rake db.all
> rake db.all RAILS_ENV=test

Step 3: Put it together in a bash script

db.sh

#!/bin/bash
echo "Reseting database"
rake db:all
rake db:all RAILS_ENV=test

Now when ever I would to scrap and start all over, I rollback my git repository, and rebuild my db.

Voila. Clean slate and I can what ever it was I was trying to do again.

Advertisements

How to add a drop down resource to an existing rails model

3 Comments

For my pet rails project I need to add a category (drop down) to a listings page.

Step 1: Create the category resource.

Using the rails scaffolding, I create a brand new resources and it’s respectful Restful services like this:

$ rails generate scaffold Category name:string

This gives me a simple categories table with an id and name lookup.

Step 2: Create the migration

I now want to connect the listing to the category through the category_id.

$ rails generate migration AddCategoryToListings category_id:int
class AddCategoryToListings < ActiveRecord::Migration
  def change
    add_column :listings, :category_id, :int
  end
end

> rake db:migrate
> rake db:test:prepare

Note: this is not a database enforced foreign key constraint. Just a rails active record connection.

Step 3: Update the models

Now I need to make it clear that a listing belongs_to a category, and that a category has_many listings a the model level.

I also need to make the new category_id variable available for mass assignment by adding it to the attr_accessible.

/models/listing.rb

class Listing < ActiveRecord::Base
  attr_accessible :title, :description, :category_id

  belongs_to  :category

/models/category.rb

class Category < ActiveRecord::Base
  has_many :listings
end

Step 4: Setup some test factories.

To make sure things are working at database level, I need some factories to give me some test data.

Here I define a factory for a user, listing, and category.

/spec/factories.rb

Factory.define :user do |user|
  user.name                  "Michael Hartl"
  user.email                 "mhartl@example.com"
  user.password              "foobar"
end

Factory.define :listing do |listing|
  listing.title "Title"
  listing.description "Description"
  listing.association :category
  listing.association :user
end

Factory.sequence :category_name do |n|
  "#{n}"
end

Factory.define :category do |c|
  c.name  Factory.next :category_name
end

Step 5: Test the model.

/spec/models/listing_spec.rb

require 'spec_helper'

describe Listing do

  before(:each) do
    @user = Factory(:user)
    @category = Factory(:category)
    @attr = { :title => "some title",
              :description => "some description",
              :category_id => @category.id }
  end

  it "should create a new instance given valid attributes" do
    @user.listings.create!(@attr)
  end

  describe "associations" do
    
    it "should have a category attribute" do
      @category =  @listing.should respond_to(:category)
    end

  end

  describe "validations" do

    before(:each) do
      @attr = { :title => "some title",
                :description => "some description",
                :category_id => @category.id }
    end

    it "should build a listing" do
      @user.listings.build(@attr).should be_valid
    end

    it "should require a category" do
      @user.listings.build(@attr.merge(:category_id => nil)).should_not be_valid
    end

  end

end

A few words about the controller and the view

Before we can test the controller and the model, it’s important to understand what’s going on.

The controller set’s things up for the view. That is, it sets up the the @listing attributes, so the view knows how to render the html necessary to create the view. And it populates the @categories object so the collection_select has all the category information it needs to populate a fully loaded category drop down.

/controllers/listings_controller.rb

class ListingsController < ApplicationController

  before_filter :authenticate, :only => [:create, :destroy, :new]
  before_filter :prepare_categories

  def new
    @listing = Listing.new
    @user = current_user
  end

  def create
    #raise params.inspect

    @listing  = current_user.listings.build(params[:listing])

    begin
      @listing.save!
      flash[:success] = "Listing created!"
      redirect_to root_path
    rescue Exception => e
      render :action => 'new'
    end
  end

  def destroy
  end

  # add the @categories = Category.All to the before action so avail for all actions

  private
    def prepare_categories
      @categories = Category.all
    end

end

Once the controller does this, the view is good to go.

/view/listings/new.html.erb

<%= form_for @listing do |f| %>
  <%= render 'shared/error_messages', :object => f.object %>
  <div class="field">
    <%= f.label :title %><br />
    <%= f.text_field :title, :size => 57 %>
  </div>
  <div class="field">
    <%= f.label :description %><br />
    <%= f.text_area :description, :class => 'description' %>
  </div>
  <div class="field">
    <%= f.label :category %><br />
    <%= f.collection_select(:category_id, @categories, :id, :name, :include_blank => "Please select") %>
  </div>
  <div class="actions">
    <%= f.submit "Submit" %>
  </div>
<% end %>

Step 6: Test the controller

/spec/controllers/listings_controller_spec

require 'spec_helper'

describe ListingsController do
  render_views
  
  describe "GET 'new'" do

    it "should be successful" do
      get :new
      response.should be_success
    end

    it "should prepare categories" do
      get :new
      assigns(:categories).should_not be_nil
    end
  end


  describe "POST 'create'" do

     describe "failure" do

       before(:each) do
         @attr = { :title => "", :description => "", :category_id => "" }
       end

       it "should not create a listing" do
         lambda do
           post :create, :listing => @attr
         end.should_not change(Listing, :count)
       end

       it "should render the home page" do
         post :create, :listing => @attr
         response.should render_template('listings/new')
       end
     end

     describe "success" do

       before(:each) do
         @attr = { :title => "Lorem ipsum",
                   :description => "Lorem ipsum",
                   :category_id => '1' }
       end

       it "should create a listing" do
         lambda do
           post :create, :listing => @attr
         end.should change(Listing, :count).by(1)
       end

       it "should redirect to the home page" do
         post :create, :listing => @attr
         response.should redirect_to(root_path)
       end

       it "should have a flash message" do
         post :create, :listing => @attr
         flash[:success].should =~ /listing created/i
       end
     end
  end
end

Step 7: Test the view.

/spec/requests/listing_spec.rb

require 'spec_helper'

describe "When creating a new listing" do

  it "should create" do

    # signin
    user = Factory(:user)
    visit signin_path
    fill_in "Email",    :with => user.email
    fill_in "Password", :with => user.password
    click_button

    lambda do
      visit '/listings/new'
      fill_in "Title",          :with => "SomeTitle"
      fill_in "Description",    :with => "SomeDescription"
      select "books",            :from => "listing_category_id"
      click_button
    end.should change(Listing, :count).by(1)
  end

end

Note: This test used webrat, and had some test fixture data loaded into the test database via

test/fixtures/categories.yml

one:
  name: books

two:
  name: computers
$ rake db:fixtures:load RAILS_ENV=test

Just a heads up – this example may not work out of the box. It’s just something I through up to remind myself what the steps were for the next time I did this.

To learn more about what’s going on behind the scenes I recommend Michael Hartl’s Ruby Tutorial.

Also apologies for any spelling/typos.

%d bloggers like this: