Tech Talk Section Archive


Capistrano 2.0 and Tagged Releases

January 15, 2008
Posted by Seth Faxon

A few weeks ago Anthony setup http://vintagejesus.net. I was going to setup yet another deployment bash script on the server, but not all of our applications are deployed with a bash script. I stepped back and decided it was time to bring some order to the chaos of our current deployment strategy. Currently I use a lot of ‘organic’ bash scripts that deploy off of trunk, something I’ve never been a fan of. Mark enlightened me to use tagging to deploy apps. We use Capistrano to deploy some rails apps, so I figured I would set it up to deploy using tags. I was surprised I couldn’t find a plugin that I liked so I set off on building my own recipe.

The basic idea was that I wanted to run something like this:

cap deploy

The script should prompt for a tag, and if none is given prompt for a
tag name to apply to trunk and then deploy from the new tag.

As a bonus it makes rolling back a revision pretty easy. You can just select the previous tag, or use the built in:

cap deploy:rollback

To do this in Capistrano I created a set_tag recipe that lists the
recent tags and prompts to use one of them or create a new tag from
trunk. This updates the base_repository variable. I also had to
overwrite the default update_code action to call set_tag. (You may want to copy and paste it out since the formating gets a little hairy)


require 'rexml/document' #gem install rexml
set :application, "yourappname"
set :base_repository, "http://your.repository/#{application}"
set :repository, "#{base_repository}/trunk" # default, but is overwritten by set_tag

role :app, "some.prod.server", :primary => true
set :deploy_to, "/some/server/path"
set :use_sudo, false
namespace :deploy do
  task :set_tag do
    # get the release path
    depend :local,:command,"svn"

    show_recent_tags("#{base_repository}/tags")
    tag = ::Capistrano::CLI.ui.ask("which tag (blank for trunk): ")
    if tag == ""
      new_tag = ::Capistrano::CLI.ui.ask("deploy from trunk, but you have to give it a tag: ")
      raise Capistrano::Error, "have to tag a new release" if new_tag == ""
      #raise error if there are invalid characters

      raise Capistrano::Error, "invalid characters in tag" if new_tag =~ /[|]|\?|:|\/|\*|\"|\'||\||(\s)/
      cmd = "svn copy quiet message \"cap tagged release\" #{base_repository}/trunk #{base_repository}/tags/#{new_tag}"
      puts  "  * locally executing \"#{cmd}\""
      system cmd
      tag = new_tag
    end

    set :repository, "#{base_repository}/tags/#{tag}"
    puts "repository: #{repository}"
  end
  # Overwrite update_code task to add call to set_tag
  task :update_code, :except => { :no_release => true } do

    set_tag
    on_rollback { run "rm -rf #{release_path}; true" }
    strategy.deploy!
    finalize_update
  end

  def show_recent_tags(repo)

    hsh = Hash.new
    cmd = "svn ls --xml #{repo}"
    puts " >> Listing tags: "
    pipe = IO.popen cmd
    result = pipe.read

    pipe.close
    entries = REXML::Document.new(result).root.elements['list']
    entries.elements.each do |e|
      # set hash key to release id and value to: name by: commit.author
      hsh[e.elements['commit'].attribute('revision').to_s] = e.elements['name'].text + " \tby: " + e.elements['commit'].elements['author'].text
    end

    sorted_arr = hsh.sort {|a,b| a[0].to_i <=> b[0].to_i}
    if sorted_arr.size > 5
      sorted_arr.to_a[-5, 5].each {|t| puts "#{t[1]} \trev: #{t[0]} "}
    else
      sorted_arr.to_a.each {|t| puts "#{t[1]} \trev: #{t[0]} "}

    end
  end
end

Lets take a look at it in action:


cap deploy
>> Listing tags:
cap_test_0.0    by: sfaxon      rev:163

cap_test_0.1    by: sfaxon      rev: 167
which tag (blank for trunk): [type a tag listed above or hit return]

by just pressing return it asks:


deploying from trunk, but you have to give it a tag: [cap_test_0.2]

Next time I’ll show how we took this and applied it to other applications.

Update: direct link to the code above.


Re:Greek - Open Source Initiative

November 13, 2007
Posted by Pastor Zack Hubert

regreek logo

I’m happy to announce that we have made Re:Greek an Open Source Initiative!

What is Re:Greek?

It’s a website I (Zack) created a couple of years ago to assist fellow Greek students like myself to better interact with the Scriptures in their original languages (although getting the Hebrew text has been a bit tough). Over the years its feature set and audience grew as I chipped away at the possibilities in 15 minutes/week intervals. Eventually the code became so horrible, that I just knew I had to start over if I wanted to open it up for other developers to get involved. Re:Greek was born.

Possibilities

In one afternoon I rewrote www.zhubert.com into www.regreek.com, from 20k lines of code down to 811. It went from PHP to Ruby on Rails (two development frameworks). I was incredibly pleased with the new framework, as I knew it would get the code to the place it needed to be for others to jump in and build a Bible Software platform far more interesting than I could alone. So if we can get to where we are today with such a small investment of time, can you imagine what will be possible when developers from all over get involved?

Interest

There’s been some good interest so far, being covered in Challies, The Resurgence (naturally), and a few others. Including one I couldn’t read without the help of Google, hopefully it says nice things. There’s also been 14 developers that have jumped into the Google Group to assist in the collaboration of the site. If you’re interested, jump in!


Nested Resources in Rails

October 13, 2007
Posted by Pastor Zack Hubert

This is another one of those geeky posts…reader be warned!

A project that we’ve been working on behind the scenes has gradually become more complex. What started out as just a few resources, has now grown into twenty-four different resources…imagine all the duplication!

In fact, I took this snapshot of the application right before I did some cleanup (output below trimmed):

zack-huberts-computer:~/workspace/thecity zhubert$ rake stats
+----------------------+-------+-------+---------+---------+-----+-------+
| Name                 | Lines |   LOC | Classes | Methods | M/C | LOC/M |
+----------------------+-------+-------+---------+---------+-----+-------+
| Controllers          |  2626 |  1860 |      27 |     202 |   7 |     7 |

As you can see, the Controllers were pretty hefty with 2626 lines total. Ouch.

Inspired by a pretty old blog post, I decided to take a stab at metaprogramming to clean up this mess. I think it was a success:

zack-huberts-computer:~/workspace/thecity zhubert$ rake stats
(in /Users/zhubert/workspace/thecity)
+----------------------+-------+-------+---------+---------+-----+-------+
| Name                 | Lines |   LOC | Classes | Methods | M/C | LOC/M |
+----------------------+-------+-------+---------+---------+-----+-------+
| Controllers          |  1340 |  1006 |      28 |     123 |   4 |     6 |

So what does the superclass look like? Like this:

# This is a CRUD controller for nested routes.
class CRUDController < ApplicationController
  before_filter :crud_setup_nested_routes

  def self.named_filter( filter_hash )
    filter, function = filter_hash.to_a[0]
    @filter_store ||= Hash.new {|a,k| a[k] = Array.new }
    @filter_store[filter] << function
  end

  def self.named_parents(parents_array)
    @parents ||= Array.new
    @parents << parents_array
    @parents.flatten!
  end

  def parents
    if self.class.instance_variable_get("@parents").nil?
      self.class.instance_variable_set("@parents", Array.new)
    end
    self.class.instance_variable_get("@parents")
  end

  def named_filter_store
    if self.class.instance_variable_get("@filter_store").nil?
      self.class.instance_variable_set("@filter_store", Hash.new {|a,k| a[k] = Array.new } )
    end
    self.class.instance_variable_get("@filter_store")
  end

  def named_filter(name)
    named_filter_store[name].each do |filter|
      case filter
      when nil
        # do nothing
      when Proc
        filter.call
      when Symbol
        (Proc.new { send filter }).call
      else
        raise("PANIC!  Named Filter is being called on something unusual.")
      end
    end
  end

  # These are the standard CRUD functions, appropriately made generic
  def index
    named_filter(:index)
    @object = instance_variable_set("@#{plural_name}", current_model.find(:all, :conditions => conditions_string))
    respond_to do |format|
      format.html # index.rhtml
      format.xml  { render :xml => @object.to_xml }
      format.js
    end
  end

  def show
    named_filter(:show)
    respond_to do |format|
      format.html # show.rhtml
      format.xml  { render :xml => @object.to_xml }
      format.js
    end
  end

  def new
    named_filter(:new)
    instance_variable_set("@#{singular_name}", current_model.new)
  end

  def edit
    named_filter(:edit)
  end

  def create
    @object = instance_variable_set("@#{singular_name}", current_model.new(params_hash))
    named_filter(:create)
    unless parents.size == 0
      @object["#{parents[-1]}_id"] = params["#{parents[-1]}_id".to_sym]
    end
    respond_to do |format|
      if @object.save
        flash[:notice] = "#{singular_name} was successfully created"
        named_filter(:after_save_success)
        format.html { redirect_to @hook_send_to_after_create || send_to_singular_name_url }
        format.xml  { head :created, :location => @hook_send_to_after_create || send_to_singular_name_url }
        format.js
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @object.errors.to_xml }
        format.js
      end
    end
  end

  def update
    named_filter(:update)
    respond_to do |format|
      if eval("@#{singular_name}").update_attributes(params_hash)
        named_filter(:after_update_success)
        flash[:notice] = "#{singular_name} was successfully updated"
        format.html { redirect_to @hook_send_to_after_update || send_to_singular_name_url }
        format.xml  { head :ok }
        format.js
      else
        format.html { render :action => "edit" }
        format.xml  { render :xml => @object.errors.to_xml }
        format.js
      end
    end
  end

  def destroy
    named_filter(:destroy)
    @object.destroy

    respond_to do |format|
      format.html { redirect_to @hook_send_to_after_destroy || send_to_plural_name_url  }
      format.xml  { head :ok }
      format.js
    end
  end

  private

  # do name handling
  def plural_name
    controller_name.gsub('_controller','')
  end

  def singular_name
    plural_name.singularize
  end

  # model naming
  def current_model
      Object.const_get singular_name.classify
  end

  def params_hash
    params[singular_name.to_sym]
  end

  # redirect urls, should probably genericize for non-redirect blocks
  def send_to_singular_name_url
    parent_array = build_parent_array
    parent_array << @object
    send("#{singular_name}_url",*parent_array)
  end

  def send_to_plural_name_url
    parent_array = build_parent_array
    send("#{plural_name}_url",*parent_array)
  end

  # cleanly build the parent array for the send()
  def build_parent_array
    parent_array = []
    for parent in parents
      parent_model = Object.const_get parent.classify
      parent_array << parent_model.find(params["#{parent}_id".to_sym])
    end
    return parent_array
  end

  def conditions_string
    case parents.size
    # SQL doesn't like it when you indicate a condition and don't pass one, so
    # true is passed when you don't really want a condition
    when 0
      con = ["1 = 1"]
    else
      con = "#{parents[-1]}_id = " + params["#{parents[-1]}_id".to_sym]
    end
  end

  # pulls together all the nested routes
  def crud_setup_nested_routes
    for parent in parents
      parent_model = Object.const_get parent.classify
      instance_variable_set("@#{parent}", parent_model.find(params["#{parent}_id".to_sym]))
    end
    if params[:id]
      @object = instance_variable_set("@#{singular_name}", current_model.find(params[:id], :conditions => conditions_string))
    end
    named_filter(:setup)
  end  

end

Kudos to David Parrott, one of the faithful developers that volunteers here at Mars Hill, for the metamagic necessary to make this more Rail-ish with the named_filter paradigm.

What does a controller look like that uses this? Much more readable as only the overrides stick out…everything else is therefore convention!

# RESTful Resource, the Invitations controller handles all aspects of CRUD for
# the Invitation resource and belongs to 'group'
class InvitationsController  < CRUDController
  named_filter :setup => :allow_bypass_with_magic_code
  named_filter :create => :create_code_and_be_secure_about_it
  named_filter :after_save_success => :redirect_to_invitations
  named_parents ['group']

  def allow_bypass_with_magic_code
  ...
  end

  def create_code_and_be_secure_about_it
  ...
  end

  def redirect_to_invitations
    @hook_send_to_after_create = invitations_url(@group)
  end
end

Or if everything is standard, as simple as:

class NewsItemsController < CRUDController
  named_parents ['group']
end

Following the development of this, I’ve found that there are some similar things out there, but this approach seems pretty good to me. Being able to read the first five lines of the Controller and get the big picture is very handy, especially as the project grows.


Polymorphic Associations & Social Channels

September 6, 2007
Posted by Pastor Zack Hubert

Note - From time to time, the Codex blog will have topics which are not likely generally interesting, but more of a Geeky nature. If it’s tagged ‘Tech Talk’, be prepared for pocket-protector pensees (in this case, skip ahead to Spider-Webs below if you just want the punchline without all the tech talk).

Last night while working on our top secret project, I ran into the following, rather interesting problem.

Background
Just like many applications, there is a place where interesting stuff is created and many places that you want that interesting stuff to go. Theoretically this is just a one to many relationship that Ruby on Rails (for instance) would describe like:


class Content < ActiveRecord::Base
has_many :consumers
end
class Consumer < ActiveRecord::Base
belongs_to :content
end

This is a pretty straightforward but unrealistic model (when does a Consumer only care about one piece of content?) but it allows you to make the following calls:

# first from the perspective of this singleminded consumer
>>zack = Consumer.find_by_name('Zack')
>>zack.content
=> returns whatever was in the source content
# or from the perspective of the content
>>code_is_fun = Content.find(:first)
>>code_is_fun.consumers
=> returns Zack and all his buddies that are interested in the same content

Polymorphism
But what is a good model if you want multiple types of content, that the consumer can indifferently consume? This is where polymorphism comes in nicely. It should be noted that the other name for Polymorphic Associations is Promiscuous Associations, but we don’t like promiscuity around here so we’ll stick with Polymorphic. We will also make it a little more realistic by allowing the consumer to be subscribed to many different channels of content.

Here’s what the model looks like (a bit more complex):

class BlogContent < ActiveRecord::Base
has_many :channels, :as => :pipeline
end
class TextContent < ActiveRecord::Base
has_many :channels, :as => :pipeline
end
class Channel < ActiveRecord::Base
belongs_to :pipeline, :polymorphic => true
has_many :subscriptions
has_many :consumers, :through => :subscriptions
end
class Subscription < ActiveRecord::Base
belongs_to :consumer
belongs_to :channel
end
class Consumer < ActiveRecord::Base
has_many :subscriptions
has_many :channels, :through => :subscriptions
end

So in this, we have a couple of content types that are exposed as a channel which the consumer can then subscribe to. Behind the scenes the Channel model has two columns which make all this happen. They are the channel_id (integer) and the channel_type (string). This allows Rails to remember which original content type to point to and which object id in that model lives in that channel. So whether it’s text or a blog or something else, the consumer can just get to it via the pipeline. What does all of this get us?

Spider-Webs
The killer thing as far as a social network is concerned is the ability to rapidly push content from the edges to the masses through self-organizing channels. When you start to draw what these networks look like, it’s a spider-web of interconnection. Why would a ‘church’ care about this?

Consider this…a small group of people learn about an elderly woman in their neighborhood who has recently taken ill. Her yard is rapidly degenerating through neglect and being Christians filled with love, they want to help her out. What if that group could easily disseminate this information to all other small groups within a 2 mile radius of that house and build an ad hoc service network.


# identify the need
>> need = NeedContent.new(:title => 'Elderly woman needs help')
>> need.save
# create a channel to communicate this need
>> channel = Channel.new(:title => 'Invite neighborhood of groups')
>> need.channels << channel
# invite other groups into this channel via subscriptions if Google says they are within the radius
>> group.subscriptions << Subscription.new(...)
# and so on...

Of course, the channel could have any kind of content…that’s the beauty of Polymorphism! Many interesting possibilities exist once you have the framework for self-organizing channels. Write in with your ideas, we’d love to hear from you.