0

I have a collection_select in a rails form that looks like this:

<%= form.collection_select :post_id, Post.all, :id, :title, {}, { class: "mt-1 block" } %>

What I can't seem to figure out from the docs or googling, is how to pass multiple attributes from the Post to the dropdown so the user sees more than just the :title. Something like this:

<%= form.collection_select :post_id, Post.all, :id, :title + :category, {}, { class: "mt-1 block" } %>

I can create a custom method to pass to text_method like :title_with_category in the Post model like:

<%= form.collection_select :post_id, Post.all, :id, :title_with_category, {}, { class: "mt-1 block" } %>

Post.rb:

def title_with_category
  self.title + " " + self.category
end

But is this the best way to do this? If so, what is the appropriate place to define this? The model? Or should this be in a helper? If it's a helper, should it be in the application helper?

Lee McAlilly
  • 9,084
  • 12
  • 60
  • 94

2 Answers2

0

Firstly, it's safer to do this in case one of the items is ever nil:

Post.rb

def title_with_category
  "#{title} #{category}"
end

Next your selection. In the controller, return the options as an attribute:

def new
  @post_options = Post.all.collect{|post| [post.id, post.title_and_category]}
  # OR
  @post_options = Post.all.collect{|post| [post.id, "#{post.title} #{post.category}"]}
  # you can skip the model method with the second option
end

And on the form:

<%= form.select :post_id, @post_options, {}, { class: "mt-1 block" } %>

See form select.

TomDunning
  • 4,829
  • 1
  • 26
  • 33
  • The only part of this answer that makes sense is to use interpolation instead of concatenation. The whole step of creating the options manually is completely reduntant. – max Jul 28 '21 at 21:30
  • Good point on the `#{post.title} #{post.category}` Tom. Thanks for pointing that out. I think I'd prefer to do this in the model rather than collect the post options into an instance variable, but appreciate your take on this. – Lee McAlilly Jul 29 '21 at 21:33
0

You can pass a callable to collection_select for both the value_method and text_method arguments:

<%= form.collection_select :post_id,
                           Post.all,
                           :id, # value_method
                           ->(p){ "#{p.title} #{p.category}" }, # text_method
                           {},
                           { class: "mt-1 block" }
%>

A callable is any object that responds to the call method such as lamdba and proc objects.

It is called with the post for each iteration of the loop.

What is the appropriate place to define this? The model? Or should this be in a helper? If it's a helper, should it be in the application helper?

There is no clear cut answer if you choose to extract this out into a separate method. The model would be the simplest solution but you can also argue that presentational logic should be separated from buisness logic and that models already have tons of responsiblities.

I think we can all agree on that ApplicationHelper is the least suitible option unless you to just are aiming to toss your code into a junk drawer.

This code could go into Post, PostHelper, PostPresenter (if you're into the decorator pattern) or a custom form builder (which seems slightly overkill).

max
  • 96,212
  • 14
  • 104
  • 165
  • Thanks Max. Didn't think about using a lambda like this to loop through. This is the gist of the question I was asking, but I think for now I'm gonna stick with defining this as a method in the model. that seems cleaner to me than having that much logic in the view. But really appreciate your perspective on this problem. – Lee McAlilly Jul 29 '21 at 21:34