August 1, 2014 by Daniel P. Clark

Manual Polymorphic Creation in Rails

Finding documentation online to do this was pretty much a no go.  The answer I’ve provided here was grasped from hints given on many websites of what could be done but without anything I could really sink my teeth into.

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.

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, :facebook, :linked_in, :skype, :yahoo ]
  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 = [
      [ "LinkedIn ID",      :linked_in    ],
      [ "Facebook ID",      :facebook     ],
      [ "Twitter ID",       :twitter      ],
      [ "Google Plus ID",   :google_plus  ]
    ]
   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 one_by_kind 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. one_by_kind 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 comment, share, subscribe to my RSS Feed,and follow me on twitter @6ftdan!

God Bless! – Daniel P. Clark

#diy#howto#manual#one to many#polymorphic#rails#ruby on rails

Comments

  1. Johnny Chan
    March 11, 2016 - 6:56 am

    Hi Daniel, I have had a go working through this blog post tutorial and I am getting errors at the `_form.html.erb` – somewhere around the invocation of `one_by_kind()` helper method. I am sorry for the ask but I would be very grateful if you could help me out debugging this? I have uploaded my Rails 4 project to GitHub [here](https://github.com/Atlas7/params101). I have only started using Rails for 2 months and still a newbie. Thank you!!!

    • Daniel P. Clark
      March 11, 2016 - 7:38 am

      In your “params101/app/views/profiles/new.html.erb” file you need to change the code from

      render "form"

      to

      render partial: "form"

      Let me know how that works for you.

      • Daniel P. Clark
        March 11, 2016 - 8:25 am

        I found that the polymorphic model relationship wasn’t created in the DB.

        rails generate model Social kind:integer:index username:string:index sociable:references{polymorphic}
        

      • Johnny Chan
        March 11, 2016 - 9:16 am

        Hi Daniel,

        Thanks so much for the super debugging session via Twitter chat! (https://twitter.com/6ftdan/status/708282028517675008). Now I have managed to at last render the partial `_form.html.erb` from `new.htm.erb` ok.

        My next step will be to play around with actually creating the object via that form (i.e. will probably need a submit button in that form, plus some fiddling with the ProfilesController.)

        Have started playing around with the Rails console – as a Rails newbie hopefully I can start using your trick shortly!

        Thanks!
        Johnny

Leave a Reply

Your email address will not be published / Required fields are marked *