Last Updated: February 25, 2016
·
1.277K
· danielpclark

Manual Polymorphic Creation in Rails

What this code allows you to accomplish is to create a belongs to/has many relationship to something while maintaining only one of each kind per user profile.

If something will, or may, have more than one belongs_to relationship, then you should use polymorphic relationships. The available online documentation on that pretty much just covers the basics. So I hope that this code outline will give you a deeper understanding and find success with polymorphic relationships with Rails from here on forward!

models/profile.rb

class Profile < ActiveRecord::Base
  has_many :socials, as: :sociable, dependent: :destroy
  accepts_nested_attributes_for :socials, allow_destroy: true
end

models/social.rb

class Social < ActiveRecord::Base
  enum kind: [:twitter, :google_plus, :instagram, :facebook]
  belongs_to :sociable, polymorphic: true
  validates_presence_of :kind
  validates_presence_of :username
end

controllers/profiles_controller.rb

class ProfilesController < ApplicationController
  before_action :set_profile, only: [:show, :edit, :update, :destroy]
  before_action :set_social_list, only: [:new, :edit]

  def new
    @profile=Profile.new
  end

  def edit
  end

  private
  def set_profile
    @profile = Profile.find( params[:id] )
  end

  def set_social_list
    @social_list = [
      [ "Twitter ID",       :twitter      ],
      [ "Google Plus ID",   :google_plus  ],
      [ "Instagram ID",     :linked_in    ],
      [ "Facebook ID",      :facebook     ]
    ]
   end

  def profile_params
    params.require(:profile).permit(
        socials_attributes: [:id, :kind, :username, :_destroy]
    )
  end
end

I’ve shortened the actual file for just what’s relevant here. You will need any other parameters permitted for your use case. The rest can remain untouched.

controllers/application_controller.rb

class ApplicationController < ActionController::Base

  def one_by_kind(obj, kind)
    obj.where(:kind => kind).first || obj.where(:kind => kind).build
  end
  helper_method :one_by_kind

end

This is where the magic will happen. It’s designed after .where(…).first or create but uses build instead so we don’t have to declare build for the socials object in the profile_controller.

And lastly the all important view:

(polymorphics most undocumented aspect.)

views/profiles/_form.html

<% @social_list.each do |label, entry| %>
    <%= f.fields_for :socials, one_by_kind(@profile.socials,
        @profile.socials.kinds[entry]) do |a| %>
            <%= a.hidden_field :kind, {value: entry} %><%= label %>:
            <%= a.text_field :username %>
    <% end %>
<% end %>

The @social_list is defined in the profile_controller and is an array of label & kind pairs. So as each one gets passed through, the onebykind method we defined in the application_controller seeks for the first polymorphic child that has the right kind which we’ve named entry. If the database record isn’t found, it is then built. onebykind then hands back the object for us to write/update.

This maintains one view for both creating and updating polymorphic children. So it allows for a one of each kind within your profile and social relation.

Please feel free to leave your own thought, ideas, and input below in the comments. Share! God Bless!