Monday, April 21, 2008

Harvesting Fixtures from a Live Rails project

So I've put together a simple plugin for grabbing fixtures out of a Live Rails project.


Harvixture is a tool that can be used to extract data, in the form of fixtures,
from a Rails project. It is done by pointing the harvixture at a request_path
and dumping fixtures for all "found" ActiveRecord objects.

$ sudo gem install jeremyf-harvixture --source=http://gems.github.com/

$ cd path/to/rails/project

$ . harvixture run --request_path=/users/1/calendars./bug_100/users.yml
./bug_100/calendars.yml


Get the plugin here.
http://github.com/jeremyf/harvixture/tree/master

TODO:
- Possibly convert to a gem.

Monday, April 14, 2008

A little bash script hacking

Puttering around the blog-o-tubes, I discovered an awesome little post concerning git and your command line. The problem with the script is its too limiting. I use both svn and git. So enter my little hack.

In ~/.bashrc


function parse_git_branch() {
git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/(git::\1)/'
}
function parse_svn_branch() {
parse_svn_url | sed -e 's#^'"$(parse_svn_repository_root)"'##g' | awk -F / '{print "(svn::"$1 "/" $2 ")"}'
}
function parse_svn_url() {
svn info 2>/dev/null | grep -e '^URL*' | sed -e 's#^URL: *\(.*\)#\1#g '
}
parse_svn_repository_root() {
svn info 2>/dev/null | grep -e '^Repository Root:*' | sed -e 's#^Repository Root: *\(.*\)#\1\/#g '
}
function parse_scm_branch() {
local value=$(parse_git_branch)
if [ -z "$value" ]; then
local value=$(parse_svn_branch)
fi
echo "$value"
}

export PS1="\[\033[00m\]\u@\h\[\033[01;34m\] \w \[\033[31m\]\$(parse_scm_branch) \[\033[00m\]$\[\033[00m\] "



For git directories

jeremyf@reclusive-geek ~/Documents/Repositories/git/ccs_portal (git::master) $


For svn directories

jeremyf@reclusive-geek ~/Documents/Repositories/svn/ccs_main/app/controllers (svn::ccs_main/trunk) $


If anyone knows how to tighten this script up, let me know.

Tuesday, April 08, 2008

git add --patch file.name

I would highly recommend reading http://tomayko.com/writings/the-thing-about-git, it highlights yet another awesome feature of git. Git lets you do your work how you do it, instead of how the SCM says you should. Here is a great example. I have a hopelessly muddled set of changes in file_the_other.txt. The changes are for two (or more) different purposes, and I need to only push a portion of it. Enter `git add --patch`. I can go through each diff element and select to put it in the queue (the Index) for what will be committed.



$ git add --patch file_the_other.txt
diff --git a/file_the_other.txt b/file_the_other.txt
index 2120d21..e3aa90d 100644
--- a/file_the_other.txt
+++ b/file_the_other.txt
@@ -1,3 +1,6 @@
+World is here
A file like another file, but with different content.

-There is even more content here.
\ No newline at end of file
+There is even more content here.
+
+Hello World
\ No newline at end of file
Stage this hunk [y/n/a/d/s/?]?

Friday, March 14, 2008

Some rspec love for acts_as_state_machine

So I've once again revisited the almighty "acts_as_state_machine" plugin (http://elitists.textdriven.com/svn/plugins/acts_as_state_machine). Its really helpful, but one thing I've never liked is testing the valid transitions. Because, really you should test a whole lot of stuff; Enter :describe_valid_event_transitions.


class MyStatedObject < ActiveRecord::Base
acts_as_state_machine :initial => :pending, :column => 'status'
state :pending
state :active
state :completed
state :rejected

event :approve! do
transitions :to => :active, :from => :pending
end

event :reject! do
transitions :to => :rejected, :from => :pending
end
event :complete! do
transitions :to => :completed, :from => :active
end
end

describe MyStatedObject do
describe_valid_event_transitions do |transition|
transition.is_valid(:event => :approve!, :from => :pending, :to => :active)
transition.is_valid(:event => :reject!, :from => :pending, :to => :rejected)
transition.is_valid(:event => :complete!, :from => :active, :to => :completed)
end
end


Using the above code it will generate 3 (events) * 4 (statuses) * 4 (statuses) specs. If I were to inadvertently add a validator for :event => :stupify!, then the number of specs would be 4 * 4 * 4, and there would be a whole lot of failures.



module Spec
module Rails
module Matchers
# Specifies a valid state change
#
# options
# :from: The from state
# :to: The desired state
# :via: The event to fire the transition
def change_state(*args)
options = args.extract_options!
from_status = options[:from]
to_status = options[:to]
via_event = options[:via]
return simple_matcher("model should change status from :#{from_status} to :#{to_status} via :#{via_event} event") do |klass|
object = klass.is_a?(Class) ? klass.new : klass
object.stub!(:current_state).and_return(from_status.to_sym)
object.stub!(:status).and_return(from_status.to_s)
if event = object.next_states_for_event(via_event).detect{|event| event.to == to_status}
event.to.to_sym == to_status.to_sym
else
false
end
end
end
end
end
end


class Spec::Rails::Example::RailsExampleGroup

class ValidTransitionCollector #:nodoc:

def events
transition_table.collect{|t| t[:event]}.uniq
end

def states
transition_table.collect{|t| [t[:to], t[:from]]}.flatten.uniq
end

def add(options = {})
options.symbolize_keys!
transition_table << {:event => (options[:event] || options[:via]).to_sym, :from => options[:from].to_sym, :to => options[:to].to_sym}
end
alias_method :is_valid, :add

def has?(event, from, to)
transition_table.detect{|o| o[:event] == event && o[:from] == from && o[:to] == to}
end
protected
def transition_table
@transition_table ||= []
end
end

class << self
# This method assumes the use of the awesome acts_as_state_machine,
#
# Given all events (both defined and proposed to the validator)
# and all statuses (both defined and proposed to the validator),
# this method will iterate over the events, and then over
# the statuses as the "from" status, and then over the
# statuses again as the "to" status.
#
# With the event, from status and to status, this method
# will check against the validator to say it should or
# should_not state_change
#
#
# Usage:
#
# describe MyStatedObject do
# describe_valid_event_transitions do |transition|
# transition.is_valid(:event => :approve!, :from => :pending, :to => :active)
# transition.is_valid(:event => :reject!, :from => :pending, :to => :rejected)
# transition.is_valid(:event => :complete!, :from => :active, :to => :completed)
# end
# end
#
def describe_valid_event_transitions
collector = ValidTransitionCollector.new
yield(collector)
klass = description.constantize
describe 'status changes' do
(klass.event_table.keys + collector.events).uniq.each do |event|
(klass.states + collector.states).each do |possible_from_state|
(klass.states + collector.states).each do |possible_to_state|
should_transition = false
if result = collector.has?(event, possible_from_state, possible_to_state)
should_transition = true
end

it "should #{should_transition ? '' : 'NOT '}change from :#{possible_from_state} to :#{possible_to_state} via :#{event}" do
klass.send("#{should_transition ? 'should' : 'should_not'}", change_state(:via => event, :from => possible_from_state, :to => possible_to_state))
end
end
end
end
end
end
end
end

Thursday, January 24, 2008

RSpec Delegation Assertion

So I've made the jump to using RSpec, and I love it. I really like the declarative nature of the matchers, and feel compelled to write useful ones. Below is a matcher for testing delegations. As of right now, I have not yet decided if I should have the
matcher be at the class level or the object level. We'll see. Perhaps iteration 2.


class Comment < ActiveRecord::Base
extend Forwardable
belongs_to :user
def_delegator :user, :name, :author_name
end

describe Comment do
it 'should delegate author_name to user' do
Comment.should delegate(:author_name, :to => :user, :via => :name)
end
end

Below is a helpful tester for delegations, inspired by Jay Fields, and more importantly made possible by Obie Fernandez's most excellent The Rails Way.

module Spec
module Rails
module Matchers
# describe Comment do
# it 'should delegate author_name to user' do
# Comment.should delegate(:author_name, :to => :user, :via => :name)
# end
# end
class Delegation
private
attr_reader :expected, :actual, :method_name, :options, :to, :via
public
def initialize(method_name, options = {})
@method_name = method_name
options.symbolize_keys!
@to = options[:to]
@via = options[:via] || method_name
end

def matches?(klass)
@actual = klass
delegate_class = Class.new
delegate_class.send(:attr_accessor, @via)

delegate_object = delegate_class.new
delegate_object.send("#{@via}=", :spec_rails_matchers_delegation)

@base_object = @actual.new

# Use the singleton class of the base_object
# I am rewriting the @to method on the base_object's meta_class
# but not altering the base_object's parent class
# So if I instantiate another instance of @actual
# it will not have the below behavior, only the @base_object will
#
# http://whytheluckystiff.net/articles/seeingMetaclassesClearly.html
singleton = @base_object.instance_eval("class << self; self; end")
singleton.class_eval("attr_accessor :#{@to}")

@base_object.send("#{@to}=", delegate_object)
@base_object.send(method_name) == :spec_rails_matchers_delegation
end

def failure_message
"expected #{actual.to_s} to delegate :#{method_name} to :#{to} via :#{via}"
end

def negative_failure_message
"expected #{actual.to_s} to NOT delegate :#{method_name} to :#{to} via :#{via}"
end

def to_s
"#{actual.to_s} delegates :#{method_name} to :#{to} via :#{via}"
end

end
def delegates(method_name, options = {})
Delegation.new(method_name, options)
end
alias_method :delegate, :delegates
end
end
end