Archive for October, 2007

Ask Anything - Phase 2

October 31, 2007
Posted by Pastor Zack Hubert

AskAnythingHeader

We’re about to enter into Phase 2 of our Ask Anything project. If you’re not aware of Ask Anything, I encourage you to head over to the About page and take a peek at it’s contents, but basically we’re following the example of the Apostle Paul in fielding questions from the people he cared about. In our case, it’s all of you, so head over there and vote for your favorite questions.

In switching to Phase 2, we’re going to take the top 50 questions, put them all on one big page, and let you decide which become the Top 9. These Top 9 will be determined on December 14th, 2007, by whichever 9 questions have the most votes. They will become the sermon series Pastor Mark preaches in January 2008.

So have you wanted to ask Pastor Mark a question? Head over there and do it!


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.


Redeeming Social Networking

October 8, 2007
Posted by Pastor Zack Hubert

Last week, I came across a good explanation on how social networking “works” from a local Seattle native:

Now, the hidden benefits that the Social Network revealed in the case of this video were such things as a new job or relationship, but these are by no means the limitation of this technology.

Quite recently, knowledge itself has been recognized as something that could be leveraged through your Social Network, something that those that use our Member’s Site to get good referrals already know.

What if Social Networking was redeemed and used for more than just selfish ends?