Technology Musings

July 15, 2008

Project Management / Two good articles on effective subversion use

JB

Here are two articles on subversion usage in projects.

July 07, 2008

Snippets / Scoped has_many :through

JB

In Rails, has_many :through is an excellent way to do join tables.  For example, I can have a person, a project, and a person_project to define the people I have assigned on projects.  This would be setup as follows:

class Person < ActiveRecord::Base
has_many :person_projects
has_many :projects, :through => :person_projects
end 
class Project < ActiveRecord::Base
has_many :person_projects
has_many :people, :through => :person_projects
end
class PersonProject
belongs_to :person
belongs_to :project
end

So, if I have a Project object called proj, I can do proj.people and get access to all the people on the project. I can also use it as a straight arra, and push individual Person objects onto proj.people, and the appropriate records in PersonProject will be created.

However, let's say that we add an additional value to PersonProject called "role".  Let's say this can be set to 'ADMIN', 'DESIGN', or 'TECH'.   Now pushing onto the proj.people array doesn't help much, because it doesn't fill out the role.  I created a snippet of code inspired by Josh Susser's solution, but which I think works a little better.  With this new code, I can rewrite the Project class (and the Person class, if I wanted) like this:

class Project < ActiveRecord::Base
has_many :person_projects
has_many :people, :through => :person_projects
scoped_has_many_through :admins, :through => :person_projects, :source => :person, :scope => { :role => "ADMIN" }
scoped_has_many_through :techs, :through => :person_projects, :source => :person, :scope => { :role => "TECH" }
scoped_has_many_through :designs, :through => :person_projects, :source => :person, :scope => { :role => "DESIGN" }
end

So the code to do that is as follows, although there's probably a more Rails-ish way to do this:

module ActiveRecord
class Base
def self.scoped_has_many_through(assoc, opts)
has_many_scope = opts.delete(:scope)
conditions_scope = Hash[*has_many_scope.to_a.map{|ent|
["#{opts[:through].to_s.pluralize}.#{ent[0]}", ent[1]]
}.flatten]
opts[:conditions] ||= {}      
if opts[:conditions].is_a?(Hash)
opts[:conditions] = opts[:conditions].merge(conditions_scope)
else
raise "Conditions must be a hash for conditions_scope!"
end
has_many assoc, opts do        
#Using define_method instead of def in order to make it lexically scoped
define_method(:construct_owner_attributes) do |reflection|
atts = super(reflection)
return atts.merge(has_many_scope)
end
end
end
end
end 

I have a few things like this.  I might package them together into a plugin sometime.  On the one hand, code snippets this small seem silly for a plugin.  On the other hand, plugins make it easily reusable, and perhaps I could batch several of them together, even if they aren't related.

July 02, 2008

Features / named_scope is really cool

JB

Rails 2.1.0 has, among other things, one very cool feature - named scoping.

class Whatever < ActiveRecord::Base
  named_scope :active, :conditions => { :active => true }
end
 
Whatever.active  #yields all active whatevers
Whatever.active.find(x) #only runs find() on active whatevers

You can even have parameters that get passed in through anonymous functions. It's a very powerful mechanism, and makes everything much more beautiful.  Learn more here.