Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails Polymorphic association with form collection_select without nesting

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

like image 479
zechtz Avatar asked Mar 05 '14 06:03

zechtz


2 Answers

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.

like image 174
Anson Avatar answered Nov 10 '22 15:11

Anson


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!

like image 37
zechtz Avatar answered Nov 10 '22 14:11

zechtz