March 2008 Archives

resource_controller plugin

For those of you learning or using Ruby on Rails, here are my notes on Fabio Akita’s excellent screencast tutorial for James Golick’s resource_controller plugin. I find it to be useful to be able to refer to a written document quickly instead of having to watch the entire hour presentation if you forget something. Feel free to report errors or inaccuracies in my notes. I hope you find the notes to be handy too.

Notes on Fabio Akita’s screencast: http://www.akitaonrails.com/2008/1/25/easy-restful-rails-screencast

Topic: James Golick’s resource_controller plugin for Ruby on Rails: http://jamesgolick.com/resource_controller

Problem #1: RESTful controllers almost always have 7 methods that look nearly the same. There is a lot of repetitive code. This is not DRY. Instead, wouldn’t it be great if you could inherit from a standard RESTful superclass and just configure your controller if it needed.

Problem #2: When you have a has_many relationship between two models, and want to nest your routes. e.g. http://yoursite.com/posts/42/comments/3, then you have to change all your links in all your sub-model’s views. This is kind of a pain.

Solution: resource_controller plugin provides that RESTful superclass and generators to replace all the repetitive code and makes your controllers skinny like models. Standard models are customizeable through a DSL-like API provided by the plugin. Generic helper functions for views are also provided so that helper links are generic so you don’t have the problem of customizing links when making nested routes (is this true?).

This tutorial describes how to create blog of postings with a Post model and a Comment model.

The Comment model is polymorphic which means that it can be used to place comments not just on the Post model but any other model in the application.

Also, the Comment model will be using nested routes.

How to install:

In your rails_root application directory type:
svn export http://svn.jamesgolick.com/resource_controller/tags/stable 
  vendor/plugins/resource_controller

How to use the generator

ruby script/generate scaffold_resource Post title:string body:text

How to create a polymorphic has_many submodel

ruby script/generate scaffold_resource Comment commentary:references comment:text

Note: you need to make your own css and application layouts:

public/stylesheets/scaffold.css
app/views/layouts/application.html.erb

CONTROLLERS

Now your controllers are skinny:


# /app/controllers/posts_controller.rb
class PostsController < ResourceController::Base
end

/app/controllers/comments_controller.rb

class CommentsController < ResourceController::Base
end

ResourceController::Base inherits from the usual ApplicationController

MODELS

Now modify your Post model to express the has_many relationship with the Comment model.

Ie. A post have many comments.


# /app/models/post.rb 
class Post < ActiveRecord::Base
  has_many :comments, :as => :commentary
end

Note: For non-polymorphic relationships you don’t need :as => commentary.

Modify your Comment model to have the polymorphic belongs_to relationship.


# /app/models/comment.rb 
class Comment < ActiveRecord::Base
  belongs_to :commentary, :polymorphic => true
end

How polymorphic tables works: Your comments table has two columns (in addition to the text field that stores your comment): commentary_id and commentary_type. commentary_type is the name of the table you want to reference. commentary_id is the row in the table that you’re referencing. In this way you can associate a comment to any row in any table in your database. You can comment on anything in your application.

CREATING A POST HAS_MANY COMMENT RELATIONSHIP, WHERE COMMENT IS POLYMORPHIC.

MIGRATIONS You need to modify your 002_create_comments.rb migration to make it polymorphic.

# /db/migrate/002_create_comments.rb
class CreateComments < ActiveRecord::Migration
  def self.up
    create_table :comments, :force => true do |t|
    t.references :commentary, polymorphic => true
    t.text :comment

    t.timestamps
    end # do
  end # def self.up

  def self.down
    drop_table :comments
  end
end # class
Now run your migration.

rake db:migrate
Now you need to add “belongs_to :post” to your controller. This will make it so that you don’t have to load the parent resource in your controller like this

@post = Post.find(param[:post_id])
“belongs_to :post” will automatically load your parent resource. This reduces a lot of repetitive coding.

# /app/controllers/comments_controller.rb
class CommentsController < ResourceController::Base
  belongs_to :post
end
Modify your routes.rb file to enable nested routing.

# /config/routes.rb file
ActionController::Routing::Routes.draw do |map|
  map.resources :posts, :has_many => :comments
end

VIEWS In your /app/views/comments/_form.html.erb file, take out the commentary field, cuz that’s not something that is user-facing. It’s an internal field used to implement polymorphism.

Start up your server

script/server
Go to
http://localhost:3000/posts/new

Create dummy data in your database by filling out the form.

Now, you should be able to add a comment to that posting. (Assum your posting has an id of 1.) Go to
http://localhost:3000/posts/1/comments/new

Fill out the form. It should have created a new comment linked to your post record with id 1.

Check to see if the comment has been associated with your post using the console: script/console

>> Post.find(1)
=> #<Post id: 1, title: "My first post", body: "restful post", .......>
>> Post.find(1).comments
=> [#<Comment id: 1, commentary_id: 1, commentary_type: "Post", comment: "This is a new comment." ......>]
>> Comment.count
=> 1

Where is the magic happening? Let’s look at the edit comment view.

Normally, without using the resource_controller plug, with nested resources, you would have to refer to the parent resource as post_comment_url(@post, @comment). With resource_controller, all you have to do is use: “object_url”. Ie. in the old way, the helper method names are coupled to the names of your models. In the new way, the information of the relationships are taken from the controller, and helper method names are independent of the names of the models. (Coupling is generally bad, because it means your code is brittle and linked dependent on other parts of your code. Independence is good because it makes your code more modular.)


# /app/views/comments/edit.html.erb
....
<%= link_to 'Show', object_url %> |
<%= link_to 'Back', collection_url %>

ADDING ANOTHER COMMENTABLE MODEL CALLED “ARTICLE”

To see the value of a polymorphism, let’s create another model called Article and use the same comments table to store comments on articles. Use the resource_controller generator to create articles:


ruby script/generate scaffold_resource Article title:string 
 author:string article:text
Create the database table.
rake db:migrate
Configure the models Add the has_many clause.

# /app/models/article.rb
class Article <ActiveRecord::Base
  has_many :comments, :as => :commentary
end
Configure the routes.rb file

# /config/routes.rb
ActionController::Routing::Routes.draw do |map|
  map.resources :articles, :has_many => :comments
  map.resources :comments
  map.resources :posts, :has_many => :comments
end
Finally, add :article to the belongs_to method call in CommentsController

# /app/controllers/comments_controller.rb
class CommentsController < ResourceController::Base
  belongs_to :post, :article
end
Now it should work. Let’s say you already input an article with id of 1. Then add a comment to your article

http://localhost/articles/1/comments/new
CREATING AN ADMIN NAMESPACE Let’s say you want only the admin to create, edit, or delete Post instances. Make an admin folder in in /app/controllers and /app/views.

mkdir app/controllers/admin
mkdir app/views/admin
mkdir app/views/admin/posts
Copy your your Post views into /app/views/admin/posts Note the all the post views still exist in the normal directory location at this point. They are still accessible publicly at this point.

cp app/views/posts/* app/views/admin/posts/
Copy your post controlelr into /app/controllers/admin Note: there are going to be 2 copies of the controller, one for the public and one for the admin. See how we configure each later.

cp app/controllers/posts_controller.rb app/controllers/admin
Modify your posts controller. Just add “Admin::” to the front of your PostsController.

# /app/controllers/admin/posts_controller.rb
class Admin::PostsController < ResourceController::Base
end
To restrict editing ability of posts from the public, delete the following files _form, edit, and new.html.erb in /app/views/posts/.

rm app/views/posts/_form.html.erb
rm app/views/posts/new.html.erb
rm app/views/posts/edit.html.erb
Take out the links to edit functionality in your remaining views: app/views/posts/index.html.erb remove these lines:

<%= link_to 'Edit', object_url(post) %>
<%= link_to 'Destroy', object_url(post), :confirm => ......... %>
<%= link_to 'New Post', new_object_url %>

Remove similar links in the show page: app/views/posts/show.html.erb

Configure the public PostsController:

# /app/controllers/posts_controller.rb
class PostsController < ResourceController::Base
  actions :all, :except => [:new, :edit, :create, :update, :destroy]
end
Create the namespace routes in routes.rb. Add the following link at the bottom of your routes list in routes.rb

# /config/routes.rb
ActionController::Routing::Routes.draw do |map|
...
  map.namespace :admin do |admin|
    admin.resources :posts
  end
end
If necessary restart your web server. Now you can access:

http://localhost:3000/admin/posts

Note: Your admin section will have access to new, edit, destroy, etc..

And your public access is here:

http://localhost:3000/posts

NOW LET’S ADD PAGINATION BY CUSTOMIZING THE WAY THE POST CONTROLLER BUILDS COLLECTIONS OF POSTINGS. A COLLECTION IS A VARIABLE USED WHEN YOU WANT TO DISPLAY A LIST OF ALL YOUR RECORDS.

First, install the will_paginate plugin. By the way, there is a free screencast on will_paginate here: http://railscasts.com/episodes/51 Here is the command to install the plugin:

script/plugin install svn://errtheblog.com/svn/plugins/will_paginate

Without customization the PostsController builds collections like this: @collection = Post.find(:all) We want to customize this. Define the collection method to overwrite the way collections are built. end_of_association_chain is the way resource_controller plugin refers to the name of the model in question. In this case it will return the Post controller. If we were in the CommentsController, it would return the Comment model associated with params[:post_id].


private
def collection 
  @collection ||= end_of_association_chain.paginate :page => params[:page], :per_page => 5
end

Note: The paginate function comes from the will_paginate controller.

# /app/views/posts/index.html.erb # Add pagination links at the bottom.

....
<%= will_paginate @posts %>
OTHER METHODS FROM RESOURCE_CONTROLLER For more documentation and helper methods, take a look at the resource_controller README file:

/vendor/plugins/resource_controller/README
For example,

To customize the create method. Place this inside your controller:


create do
  # Sample customization code and methods:
  flash "Object was created successfully!  Right on!" 
  wants.js {render :template => "show.rjs"}
  failure.wants.js {render :template => "display_errors.rjs"}
end

To create a before filter before your controller’s new method, place this in your controller:


new_action.before do
  3.times { object.tasks.build } # This is just an example of a customization.
end

To customize the way your controller builds an individual object for the view, overwrite the build_object method.


private
  def build_object
    @object ||= end_of_association_chain.build_my_object_some_funky_way object_params
  end

If that was your PostsController, then in your posts views you would be able to access your @post variable like normal, but it would be built in the funky way as prescribed by the way you defined build_my_object_some_funky_way method.

See the README for many other functions.

Posted by David Beckwith on Mar 24, 2008

git, an alternative to SVN

These are my notes from watching the git peepcode video. I highly recommend peepcode.com videos. I feel slightly guilty for publishing his notes for his screencast, which costs $9, but I hope this page will ultimately sell more of his screencasts.

“git” is source control software. For those of you who are not programmers, source control software is software that tracks changes in your files for several reasons:

  1. When collaborating on a set of files with a team of people, source control software will prevent you from overwriting somebody else’s changes to a file. You must explicitly resolve the conflicts before merging your changes into the repository.
  2. It creates a time machine of your code and allows you to literally view the state of all your files in a project at any point in time in the past. This is helpful if you unwittingly introduced a bug into your code and want to rollback to a state where the bug wasn’t there.
  3. It can be used to track different branches of development, representing different purposes or levels of stability.

Using source control software is considered to be a best-practice in software engineering. The current standard open source tool is called SVN or Subversion. Subversion is very good, and we use it in the office daily. git offers a few improvements on subversion, namely

  1. response-time / performance (very easy to work locally to make commits fast)
  2. agility (easy to make branches and makes you a faster programmer)
  3. distributed, so you can synchronize with your laptop, desktop, friend’s computer, or work computer without having to always connect to a centralized server.

You can do all of these things with SVN, but git just makes it a lot easier to. You can think of git as the “sqlite3” of version control software (source control). Not a huge deal, but if it saves me a few minutes each day, then it’s worth switching to it.

website: git.or.cz

Features of git:

  • offline: All files are committed with respect to a local repository. Therefore, git can always be used offline. No need for central server or internet.
  • distributed: can share git repository with peers—not just server.
  • agile branching: branching becomes easier than in SVN: makes you more agile
  • emphasizes content not files…. not sure how this helps me though.

INSTALLATION Mac: sudo port install git-core

Others: install from source and compile.

Latest: git clone git://git.kernel.org/pub/scm/git/git.git

CONFIGURE identify yourself to git: email and your name git config—global user.name “David Beckwith” git config—global user.email “dbitsolutions@gmail.com”

To view all users: git config—list OR cat .gitconfig

SET UP ALIASES git config—global alias.co checkout

VIEW YOUR CONFIGURATION cat .gitconfig

TO IGNORE WHITESPACE (Ruby is whitespace insensitive) git config—global apply.whitespace nowarn

Some nice aliases: gb = git branch gba = git branch -a gc = git commit -v gd = git diff | mate gl = git pull gp = git push gst = git status

START USING GIT: git init

TO IGNORE SOME FILES MAKE A FILE (in the root directory) CALLED .gitignore and add some files to it: # comments begin with hash. *.log db/schema.rb db/schema.sql

TO SCHEDULE THE ADDITION OF ALL FILES TO THE NEXT COMMIT: git add .

TO SEE WHAT THE STATUS: git status

TO COMMIT: git commit -m “First import”

TO SEE WHAT HAS BEEN COMMITTED: git ls-files

TO SCHEDULE DELETION OF A FILE: git rm

TO COMMIT ALL CHANGES IN FILES IN THE CURRENT REPOSITORY: git commit -a

TO SCHEDULE THE ADDITION OF AN INDIVIDUAL FILE TO THE NEXT COMMIT: git add

TO VIEW THE DIFFERENCE AS YOU COMMIT USE THE -v OPTION git commit -v

TO COMMIT AND TYPE THE MESSAGE ON THE COMMAND LINE USE THE -m OPTION git commit -m “This is the message describing the commit”

TO COMMIT AND GET AUTOMATICALLY ANY CHANGES FROM OTHER PEOPLE USE THE -a OPTION git commit -a

THE NORMAL COMMIT COMMAND: git commit -a -v

TO VIEW A LOG OF YOUR COMMITS git log

TO VIEW A LOG OF YOUR COMMITS WITH A GRAPH TO SHOW THE EXTENT OF THE CHANGES git log—stat

TO HAVE PAGINATION WHEN VIEWING THE LOG FILE USE THE -v OPTION git log -v

TO VISUALIZE YOUR CHANGES gitk—all

TO CREATE A NEW BRANCH: git branch

TO VIEW ALL OF THE EXISTING BRANCHES git branch

TO VIEW A LIST OF ALL BRANCHES git branch -a

TO SWITCH TO ANOTHER BRANCH. The state of your file system will change after executing this command. git checkout OR git co

TO MAKE SURE THAT YOUR NEW BRANCH GETS CHANGES FROM THE MASTER BRANCH (WHERE EVERYBODY ELSE IS WORKING) USE THE REBASE COMMAND: git rebase master

TO MERGE YOUR NEW BRANCH INTO THE MASTER BRANCH.
  1. first switch back to the master branch. git co master
  2. check to see what changes you’re about to merge together, compare the two branches: git diff master xyz
  3. if you’re in the xyz branch, and want to merge the xyz branch into it. git merge xyz

TO REVERT YOUR CHANGES to before the merge. git reset—hard ORIG_HEAD

TO RESOLVE CONFLICTS just edit your file. Remove the markings, add the file, then commit.

TO CREATE A BRANCH AND SWITCH TO THE BRANCH IN ONE MOVE: git checkout -b

TO CREATE A “CLIPBOARD” or “STASH” OF CHANGES THAT ARE NOT YET COMMITED (SO THAT YOU CAN SWITCH TO ANOTHER BRANCH IN THE MIDDLE OF YOUR CHANGES.), CREATE A STASH. git stash “Put a message here to remind you of what you’re saving to the clipboard”

TO SWITCH AWAY FROM THE CURRENT BRANCH git co
  1. do whatever
  2. switch back to the stashed branch git co

TO VIEW THE LIST OF STASHES git stash list

TO LOAD BACK THE “CLIPBOARD” OR “STASH” git stash apply
  1. now you can continue to work where you were previously.

TO DELETE A BRANCH THAT IS NOT USED ANYMORE, but already merged into the current branch. (TO CLEAN UP) git branch -d

TO DELETE AN UNMERGED BRANCH git branch -D

TO DELETE THE STASH. (ERASE THE “CLIPBOARD” FROM MEMORY) git stash clear

TO SET UP YOUR REPOSITORY FOR SHARING ON A CENTRAL SERVER
  1. Copy up your repository. e.g.: scp -r my_project deploy@yourbox.com:my_project
  2. Move your files on the remote server to /var/git/my_project
  3. For security make the owner of this project git
  4. On the repository server: sudo chown -R git:git my_project
  5. then (for security) restrict the “deploy” user to doing git-related things in /etc/passwd with a git-shell.

TO CHECK OUT THE GIT REPOSITORY TO YOUR LOCALHOST. ON YOUR LOCAL HOST do this: git clone git@yourbox.com:/var/git/my_project

TO SEE SOME INFO ABOUT THE REPOSITORY THAT WILL TELL YOU WHICH REPOSITORY IS THE MASTER AND WHICH IS THE SLAVE: cat .git/config

  1. By virtue of having cloned the remote repository, your local repository becomes the slave and will track and synchronize with the remote master branch.

TO UPDATE YOUR LOCAL BRANCH FROM THE REMOTE SERVER: git pull

TO GET A COPY OF THE ENTIRE REMOTE REPOSITORY (e.g. a repository named “laptop”) WITHOUT MERGING THEM INTO YOUR LOCAL BRANCHES USE FETCH git fetch laptop

TO MERGE TWO LOCAL BRANCHES (ie. your local xyz branch with your local master branch) USE MERGE git merge laptop/xyz
  1. this merged the (already copied laptop repository’s xyz branch) with the current branch you’re sitting in.

TO MERGE THE REMOTE BRANCH WITH YOUR LOCAL BRANCH THAT YOU ARE SITTING IN USE PULL

TO ADD LOCAL KNOWLEDGE (TO YOUR LOCAL REPOSITORY) OF A 2ND REMOTE REPOSITORY, LIKE YOUR LAPTOP git remote add laptop duo2book.local:repos/m_project
  1. “laptop” is the name of the remote repository. “duo2book.local” is the name of the machine, I guess

TO VIEW META INFORMATION ABOUT THAT REMOTE REPOSITORY git remote show laptop

TOP PUSH A COMMITTED LOCAL CHANGE OF THE xyz BRANCH TO THE REMOTE laptop BRANCH git push laptop xyz

TO CREATE A TRACKING BRANCH (A SLAVE BRANCH). Ie. to link a local branch to a remote branch: git branch—track

NOW IF YOU’RE SITTING IN THE LOCAL TRACKING BRANCH, TO PULL YOU DON’T NEED TO SPECIFY THE REMOTE TRACKING BRANCH: git pull

  1. Note: you can tracking different branches from different remote machines. For example, you can track your friend’s “upgrade” branch and track the “master” branch from your main webserver.
  1. By convention, ‘origin’ is the ‘centralized server’ which is the way SVN is usually set up on a remote server.

TO SEE WHICH LOCAL BRANCHES ARE TRACKING A REMOTE BRANCH: git remote show origin

TO WORK WITH AN SVN REPOSITORY BUT WORK WITH GIT LOCALLY: git-svn clone

  1. Now you can work with the checked out directory as though it was a git repository. (cuz it is)

TO PUSH (COMMIT) CHANGES TO THE REMOTE SERVER git-svn dcommit

TO UPDATE YOUR LOCAL REPOSITORY FROM THE SVN REPOSITORY git-svn rebase

  1. NOTE: make sure you have your perl bindings to your local svn installation.
Continue Reading…

Posted by David Beckwith on Mar 21, 2008