parm530
11/8/2017 - 3:26 PM

Polymorphism in Rails

Guide to understanding polymorphism in Rails

Polymorphism in Rails


  • Allows you to have a model associated with more than one other model without the use of a join table
  • How?
    • Adding a type and id field to the table of the model you wish to make polymorphic
    • When creating the association using rails g and appending name:references{polymorphic} can be used to quickly establish the _type and _id polymorphic column names (all done by Rails).
  • Convention is to '-able' to the polymorphic attribute.

Example: Person, Business & Interaction

  • Interactive will belong to an Interaction which Person and Business will have many of:
rails g model Person first_name last_name 
rails g model Business name
rails g model Interaction description interactive:references{polymorphic}
# you can use any name for the polymorphic id and type, just as long as they make sense

rake db:migrate
  • Add to Person and Business class (keep Interaction class the same)
class Person < ApplicationRecord
   has_many :interactions, as: :interactive
end

class Business < ApplicationRecord
   has_many :interactions, as: :interactive
end

# This should defaulted by rails
class Interaction < ApplicationRecord
   belongs_to :interactive, polymorphic: true
end
  • If you open rails c and then entered Interaction.column_names, you will see:
    • interaction_id: holds the id of the model that the interaction belongs to
    • interaction_type: holds the type of the model that the interaction belongs to
    • Both tell Rails which model this interaction should be associated with
  • Adding the following controllers:
# in terminal
rails g controller People index show new create edit update
rails g controller Businesses index show new create edit update
rails g controller Interactions new create edit update
  • Nest your resources such that the polymorphic association is nested within each model!
  • You will need some way to parse which object the polymorphic association belongs to:
    • Checking the params for the object's class and then returning the object in the db
class InteractionsController < ApplicationController
  def new
    @context = context
    @interaction = @context.interactions.new
  end

  def create
    @context = context
    @interaction = @context.interactions.new(interaction_params)

    if @interactions.save
        redirect_to context_url(@context), notice: "The interaction has been successfully created."
    end
  end

  def edit
    @context = context
    @interaction = @context.interactions.find(params[:id])
  end

  def update
    @context = context 
    @interaction = @context.interactions.find(params[:id])

    if @interaction.update_attributes(interaction_params)
        redirect_to context_url(@context), notice: "The interaction has been successfully updated."
    end
  end

  private

  def interaction_params
    params.require(:interaction).permit!
  end

  def context
    if params[:person_id]
        id = params[:person_id]
        Person.find(params[:person_id])
    else
        id = params[:business_id]
        Business.find(params[:business_id])
    end
    
  end

  def context_url(context)
    if Person === context
        person_path(context)
    else
        business_path(context)
    end
  end

end
  • For your forms that use the polymorphic association:
<%= form_for [obj1, obj2] do %>
   #...
<% end %>
  • obj1 is the object returned from parsing the polymorphic association (ex. @context = context)
  • obj2 is the object of the polymorphic class (ex. Interaction.new)

Checking in Rails Console

  • Seed the database with Person and Business objects.
  • In rails console:
p = Person.first
p.interactions.build(description: "asdfgh asdf").save
p.interactions.first.interactive_type 
# returns "Person"
p.interactions.first.interactive_id 
# returns the corresponding id of the Person object

# Now that you have an interaction, you can call the method interactive on the Interaction object to retrieve
# it's parent:
i = Interaction.first
i.interactive # returns the corresponding parent object (Person in this case)
  • You build the polymorphic relationship by calling build on the model that has_many of the polymorphic association.
  • You'll then have access to the id and type of the object!