JoinFix

A reflection-based solution to the fixture join problem.

Description

Making fixtures for models with complex joins can be a redundant, error-prone process. JoinFix provides a solution to this problem by letting you reference and/or define child entries inline with their parents.

[users.yml]
john_doe:
  full_name: John Doe
  groups: 
    - admin_group           # => entry reference
    - devel_group:          # => inline definition
        name: Developers
[groups.yml]
admin_group:                # => referenced entry
  name: Administrators

JoinFix uses reflection on ActiveRecord associations to determine how to perform joins so no configuration is required. Simply require joinfix and begin writing entries.

Info

Copyright © 2006-2007, Regents of the University of Colorado.

Developer

Simon Chiang, Biomolecular Structure Program, Hansen Lab

Support

UC Denver School of Medicine Deans Academic Enrichment Fund

Licence

MIT-Style

Installation

Tap is available as a gem on RubyForge. Use:

% gem install joinfix

Usage

Consider the following data model:

class User < ActiveRecord::Base
  has_many :user_groups
  has_many :groups, :through => :user_groups
end
class Group < ActiveRecord::Base
  has_many :user_groups
  has_many :users, :through => :user_groups
end
class UserGroup < ActiveRecord::Base
  belongs_to :user
  belongs_to :group
end

Write your fixtures using the naming scheme you lay out in your models. Entries can be referenced across multiple fixture files or defined inline:

[users.yml]
john_doe:
  full_name: John Doe
  groups: admin_group       # => reference to the 'admin_group' entry
jane_doe:
  full_name: Jane Doe
  groups:                   # => you can specify an array of entries if needed
    - admin_group
    - worker_group:         # => inline definition of the 'worker_group' entry
        name: Workers
[groups.yml]
admin_group:                # => the referenced 'admin_group' entry
  id: 3                     # => you can (but don't have to) specify ids 
  name: Administrators

Join entries implied in your definition, as in a has_and_belongs_to_many association, will be created and named by joining together the names of the parent and child, ordered by the ‘<’ operator. For example, the users.yml and groups.yml fixtures produce these entries:

[users]
john_doe:
  id: 1                     # => primary keys are assigned to all entries (see note)
  full_name: John Doe
jane_doe:
  id: 2
  full_name: Jane Doe
[groups]
admin_group:                
  id: 3                     
  name: Administrators
worker_group:
  id: 1
  name: Workers
[user_groups]
admin_group_john_doe  
  id: 1
  user_id: 1                # => references are resolved to their foreign keys
  group_id: 3               # => explicitly set primary keys are respected  
admin_group_jane_doe    
  id: 2
  user_id: 2                
  group_id: 3   
jane_doe_worker_group       # => Notice the '<' operator in action
  id: 3
  user_id: 2                
  group_id: 1

Note: Primary keys are assigned to entries based on the way the entry names are hashed, ie ‘john_doe’ will not necessarily have id ‘1’. If you need a specific id for an entry, then you must explicitly set it as in the ‘admin_group’ entry.

If you need to add additional fields to an implied entry, simply define them in their fixture file. All fields across all fixtures will be merged into one entry (JoinFix raises an error in the event of a collision).

[user_groups.yml]
admin_group_john_doe:
  date_added: 2007-06-12

Nesting is allowed. This will make the same entries as above:

[users.yml]
john_doe:
  full_name: John Doe
  groups:    
    admin_group:
      id: 3
      name: Administrators
      users:
        jane_doe:
          full_name: Jane Doe
          groups:
            worker_group:
              name: Workers

In your tests, require joinfix and use the fixtures exactly as you would normally.

One gotcha – you must be sure to name all the tables for which your fixtures create entries.

In fact this is no different than normal, but it’s easy to forget if you lump joins into one file.

require 'joinfix'
class UserTest < Test::Unit::TestCase
  fixtures :users, :groups, :user_groups  # => got to name them all!!!
  def test_joinfix
    assert_equal "Administrators", users(:john_doe).groups.first.name
    assert_equal 2, User.find_by_full_name("jane_doe").groups.count
    assert_equal 3, UserGroup.find(user_groups(:admin_group_jane_doe).id).group.id
  end
end

Command line options

JoinFix allows some command line options through the ENV variables. Setting these variables is easy if you’re using rake to run your test suite:

% rake test key=value         # => sets ENV['key'] = 'value'

Available options:

format_joinfix_errors

Unless ‘false’, JoinFix will simplify the console output when a JoinFixError occurs.

joinfix_dump

Prints all entries for tables matching the value to STDOUT. Prints entries for all tables if ‘true’.