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 1 2 3 4 5 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 1 2 3 4 5 6 7 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 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 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 1 2 3 4 5 6 7 8 9 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 %> 1 2 3 4 5 6 <% @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