Polymorpheus

Polymorphic relationships in Rails that keep your database happy with almost no setup

Installation

If you are using Bundler, you can add the gem to your Gemfile:

# with Rails >= 4.2
gem 'polymorpheus'

Or:

# with Rails < 4.2
gem 'foreigner'
gem 'polymorpheus'

Background

Basic Use

We'll outline the use case to mirror the example [outline in the Rails Guides]( guides.rubyonrails.org/association_basics.html#polymorphic-associations):

With Polymorpheus, you would define this relationship as follows:

Database migration

class SetUpPicturesTable < ActiveRecord::Migration
  def self.up
    create_table :pictures do |t|
      t.integer :employee_id
      t.integer :product_id
    end

    add_polymorphic_constraints 'pictures',
      { 'employee_id' => 'employees.id',
        'product_id' => 'products.id' }
  end

  def self.down
    remove_polymorphic_constraints 'pictures',
      { 'employee_id' => 'employees.id',
        'product_id' => 'products.id' }

    drop_table :pictures
  end
end

ActiveRecord model definitions

class Picture < ActiveRecord::Base
  # takes same additional options as belongs_to
  belongs_to_polymorphic :employee, :product, :as => :imageable
  validates_polymorph :imageable
end

class Employee < ActiveRecord::Base
  # takes same additional options as has_many
  has_many_as_polymorph :pictures, inverse_of: employee
end

class Product < ActiveRecord::Base
  has_many_as_polymorph :pictures
end

That's it!

Now let's review what we've done.

Database Migration

Model definitions

Requirements / Support

Interface

The nice thing about Polymorpheus is that under the hood it builds on top of the Rails conventions you're already used to which means that you can interface with your polymorphic relationships in simple, familiar ways. It also lets you introspect on the polymorphic associations.

Let's use the example above to illustrate.

sam = Employee.create(name: 'Sam')
nintendo = Product.create(name: 'Nintendo')

pic = Picture.new
 => #<Picture id: nil, employee_id: nil, product_id: nil>

pic.imageable
 => nil

# The following two options are equivalent, just as they are normally with
# ActiveRecord:
#   pic.employee = sam
#   pic.employee_id = sam.id

# If we specify an employee, the imageable getter method will return that employee:
pic.employee = sam;
pic.imageable
 => #<Employee id: 1, name: "Sam">
pic.employee
 => #<Employee id: 1, name: "Sam">
pic.product
 => nil

# If we specify a product, the imageable getting will return that product:
Picture.new(product: nintendo).imageable
 => #<Product id: 1, name: "Nintendo">

# But, if we specify an employee and a product, the getter will know this makes
# no sense and return nil for the imageable:
Picture.new(employee: sam, product: nintendo).imageable
 => nil

# A `polymorpheus` instance method is attached to your model that allows you
# to introspect:

pic.polymorpheus.associations
 => [
      #<Polymorpheus::InterfaceBuilder::Association:0x007f88b5528b00 @name="employee">,
      #<Polymorpheus::InterfaceBuilder::Association:0x007f88b55289c0 @name="picture">
    ]

pic.polymorpheus.associations.map(&:name)
 => ["employee", "product"]

pic.polymorpheus.associations.map(&:key)
 => ["employee_id", "product_id"]

pic.polymorpheus.active_association
 => #<Polymorpheus::InterfaceBuilder::Association:0x007f88b5528b00 @name="employee">,

pic.polymorpheus.query_condition
 => {"employee_id"=>"1"}

Credits and License

polymorpheus is Copyright © 2011-2015 Barun Singh and [WegoWise]( wegowise.com). It is free software, and may be redistributed under the terms specified in the LICENSE file.