jump to navigation

Passing Hashes to Partials September 13, 2007

Posted by reidmix in Code, Example, Hash, Partial, Rails, Ruby, SimpleDelegator.
add a comment

Sometimes you need to pass a Hash or collection of Hashes to a partial. In either case, when render tries to determine if you are using the old style method (render_partial) or the new style. When it comes across your hash, rails believes it’s dealing with all your supplied parameters to render. You can see this logic in action_view/partials.rb:

module ActionView
	module Partials
		def render_partial(partial_path, local_assigns = nil, deprecated_local_assigns = nil) #:nodoc:
			path, partial_name = partial_pieces(partial_path)
			object = extracting_object(partial_name, local_assigns, deprecated_local_assigns)
			local_assigns = extract_local_assigns(local_assigns, deprecated_local_assigns)
			local_assigns = local_assigns ? local_assigns.clone : {}
			...
		end
	end
end

You can see the last line shown in the method, where if it can’t determine a local_assigns, it just uses an empty Hash. And so that’s how it surfaces, you pass your Hash to the partial and nothing gets rendered. What we need to do is disguise that we are passing a Hash but have all the functionality of one.

Ruby gives us a few options but the easiest is SimpleDelegator. All we need to do is pass our object upon initialization and SimpleDelegator will delegate all methods to that object:

myhash = { :a => "test1", :b => "test2" }
d = SimpleDelegator.new(myhash)
puts d[:a] # => test1

If sometimes have collections of Hashes (or Hashes mixed in with my collection), I employ the following pattern:

collection = [collection] unless collection.is_a? Array
collection = collection.collect { |i| (i.is_a? Hash) ? SimpleDelegator.new(i) : i }
render :partial => "mypartial", :object => collection.compat

Patching Capistrano to use CVS over SSH September 11, 2007

Posted by reidmix in Capistrano, Code, Command Line, CVS, Monkey Patch, Ruby, SSH.
4 comments

At work we still use CVS as our versioning repository and most of our systems rely on CVS to build and deploy into production. Currently there is no effort to transition to SVN, so we need to develop a patch to Capistrano to allow our projects to deploy to our development lab. We needed to extend Capistrano handle SSH logins to CVS and handle CVS repository directories.

Overall, both changes needed to be applied to the checkout method in Capistrano’s Cvs class.

We use SSH as the underlying communication mechanism for CVS but there is a difference in the password prompt: SSH’s is capitalized. We just need to make the prompt check case insensitive with the i switch:

if out =~ %r{password:}i

When setting up Capistrano (v1.4) for use in our development lab, the :application configuration is not sufficient to identify our rails project the CVS tree. We are not cool enough to setup or use modules to map into our repository directories, so our projects are usually in some form of webdev/project/appname. To handle this, i create an additional configuration parameter called :project in the deploy.rb:

set :project, "cvs/dir/to/myproj"

And we no longer use the application name for the CVS path, we use the project variable:

project = configuration[:project] || actor.application
command = <<-CMD
  cd #{configuration.releases_path};
  CVS_RSH="#{cvs_rsh}" #{cvs} -d #{configuration.repository} -Q #{op} -D "#{configuration.revision}" #{branch_option} -d #{File.basename(actor.release_path)} #{project};
CMD

The hardest part is patching Capistrano. Capistrano’s code gets loaded well after any library or environment gets loaded. The trick is to use the method_added hook to wait for the checkout method to get loaded and then alias the method to the patched one, we need to keep a variable around to check if we’ve patched the method so we don’t end up in an infinite loop:

@@checkout_patched = false
def Cvs.method_added(id)
  if id.id2name == "checkout"
    unless @@checkout_patched
      @@checkout_patched = true
      alias_method :checkout, :checkout_patch
    end
  end
end

I’ve not checked to see how these changes work or differ in Capistrano 2. But with all these elements together using Capistrano v1.4, I can drop this file in my lib directory or create a plugin with it. Together, here is the full patch which I have in a file called capistrano_cvs_ext.rb:

module Capistrano
  module SCM
    class Cvs < Base
      @@checkout_patched = false
      def Cvs.method_added(id)
        if id.id2name == "checkout"
          unless @@checkout_patched
            @@checkout_patched = true
            alias_method :checkout, :checkout_patch
          end
        end
      end

      def checkout_patch(actor)
        cvs = configuration[:cvs] || "cvs"
        cvs_rsh = configuration[:cvs_rsh] || ENV['CVS_RSH'] || "ssh"

        if "HEAD" == configuration.branch then
            branch_option = ""
        else
            branch_option = "-r #{configuration.branch}"
        end

        # cvs has a root and repository, repository is not the same as application name as it can be a path
        op = configuration[:checkout] || "co"
        project = configuration[:project] || actor.application

        command = <<-CMD
          cd #{configuration.releases_path};
          CVS_RSH="#{cvs_rsh}" #{cvs} -d #{configuration.repository} -Q #{op} -D "#{configuration.revision}" #{branch_option} -d #{File.basename(actor.release_path)} #{project};
        CMD

        run_checkout(actor, command) do |ch, stream, out|
          prefix = "#{stream} :: #{ch[:host]}"
          actor.logger.info out, prefix
          if out =~ %r{password:}i  # SSH asks for "Password" with a capital P
            actor.logger.info "CVS is asking for a password", prefix
            ch.send_data "#{actor.password}n"
          elsif out =~ %r{^Enter passphrase}
            message = "CVS needs your key's passphrase and cannot proceed"
            actor.logger.info message, prefix
            raise message
          end
        end
      end
    end
  end
end