Rails Polymorphic association with form collection_select without nesting
Asked Answered
T

2

8

I have the following models in my app

class User < ActiveRecord::Base
has_many :articles
has_many :tour_companies
has_many :accomodations
end

class Articles < ActiveRecord::Base
belongs_to :user
belongs_to :bloggable, :polymorphic => true
end

class TourCompany < ActiveRecord::Base
belongs_to :user
has_many :articles, :as => :bloggable
end

class Accommodation < ActiveRecord::Base
belongs_to :user
has_many :articles, :as => :bloggable
end

Now my problem is i want a logged in user to be able to write an article and use a form collection_select to select which one of his / her tour companies or accommodations the article should be associated with, how do i do it in rails 4? how do i select bloggable type and id from form collection select? I don't want nested resource

Transept answered 5/3, 2014 at 6:55 Comment(0)
L
14

Since Rails 4.2, this can be handled via rails/globalid. This newer option is used by Rails' ActiveJob. It makes the parsing and lookup very simple to setup.

First, check your Gemfile.lock for globalid. For Rails 5, it is included.

The magic all happens in the model...

Article Model:

# Use :to_global_id to populate the form
def bloggable_gid
  bloggable&.to_global_id
end

# Set the :bloggable from a Global ID (handles the form submission)
def bloggable_gid=(gid)
  self.bloggable = GlobalID::Locator.locate gid
end

To get a feel for what this does, open up a rails console. Play around with gid = TourCompany.first.to_global_id and GlobalID::Locator.locate gid.

Now the rest of your code is stock Rails stuff...

Articles Controller:

# consider building the collection in the controller.
# For Rails 5, this would be a `before_action`.
def set_bloggables
  @bloggables = TourCompany.all + Accomodation.all
end

# permit :bloggable_gid if you're using strong parameters...
def article_params
  params.require(:article).permit(:bloggable_gid)
end

Articles Form:

<%= f.collection_select(:bloggable_gid, @bloggables, :to_global_id, :to_s) %>

For more of a walk-through, Simple Polymorphic Selects with Global IDs blog post is helpful.

Laminitis answered 18/9, 2018 at 15:47 Comment(2)
great answer, I had to go lookup the try shorthand &. What a great trick!Shellfish
Here in 2020 (and w/Rails 6) this is definitely the right answer. Thanks!Privy
T
5

so i managed to finally do it. Here's how i did it in the views/articles/_form.html.erb

<div class="row">
<% bloggable_collection = TourCompany.all.map{|x| [x.title, "TourCompany:#{x.id}"]} +
                          Accomodation.all.map{|x| [x.title, "Accomodation:#{x.id}]}
%>
<p>Select one of your listing this article is associated with</p>
<%= f.select(:bloggable, bloggable_collection,:selected =>"#{f.object.bloggable_type}:#  {f.object.bloggable_id}" ) %>
</div>

Then in the Articles Controller

#use regular expression to match the submitted values
def create
bloggable_params = params[:article][:bloggable].match(/^(?<type>\w+):(?<id>\d+)$/)
params[:article].delete(:bloggable)

@article = current_user.articles.build(article_params)
@article.bloggable_id         =  bloggable_params[:id]
@article.bloggable_type       =  bloggable_params[:type]
if @article.save
  redirect_to admin_root_url, :notice => "Successfully created article"
else
  render 'new', :alert => "There was an error"
end
end

And it should work!

Transept answered 19/3, 2014 at 13:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.