Friday, July 20, 2007

has_one :through

Rails is missing the capability to have a has_one association via a link table. I created this implementation that adds methods to add basic "has_one" support through a habtm association.

The following method defines a new type of association that is currently
missing in rails. There is currently no way to have a
has_one association when a join table is used.

This association requires that a has_and_belongs_to_many
association exists and adds methods to access the first
element of the resulting array.

Example: Enrollments have one registration, but is
modeled through the enrollments_registrations join table.
There is already a "has_and_belongs_to_many :registrations"
and the following operations are added
(and perform as expected):

  • enrollment.registration => enrollments.registrations.first

  • enrollment.registration_id => enrollments.registrations.first.id

  • enrollment.registration= will set the assocation, erasing any existing registration.
    This method will take an id or a registration object

  • enrollment.registration_id= like registration=, but takes an id

  • enrollment.has_registration? => true or false as the case may be



def self.has_one_through_join_table(class_name)
class_name = class_name.to_s
has_many_association_name = class_name.pluralize

# def registration
define_method(class_name) do
send(has_many_association_name).first
end

#def registration_id
define_method(class_name+"_id") do
send(class_name) ? send(class_name).id : nil
end

# def registration=(id_or_object)
define_method(class_name+"=") do |id_or_object|
clazz = eval(class_name.classify)
# the has_and_belong_to_many association (i.e. registrations)
habtm = send(class_name.pluralize)

if id_or_object.nil?
habtm.clear
return
end

id_or_object = clazz.find(id_or_object) unless
id_or_object.is_a?(clazz)
if id_or_object
habtm.clear # can only have one object
habtm << id_or_object
end
end

#def registration_id=(id)
define_method(class_name+"_id=") do |id|
send(class_name+"=", id)
end

# def has_registration?
define_method("has_#{class_name}?") do
! send(class_name).nil?
end

end # has_many_through_join_table