Jason Swett
Jason Swett

Reputation: 45074

Rails associations and forms

I have a model called appointment. Each appointment has a stylist. In the form where I create a new appointment, I'm doing this:

  <div class="field">
    <%= f.label :stylist_id %><br />
    <%= f.select(:stylist_id, Stylist.order("name").map { |s| [s.name, s.id] }) %>
  </div>

This works but it's going to be tedious to do this kind of thing for every association in my app. I imagine Rails has some way of automatically generating select fields for associations but I have no idea how it works. Does such a thing exist?

Oh, and by the way, I already know about scaffolding. If scaffolding is supposed to take care of what I describe above, I'm apparently doing something wrong, because it's not doing that for me.

(I'm on Rails 3.)

Upvotes: 6

Views: 11478

Answers (4)

lacostenycoder
lacostenycoder

Reputation: 11186

This answer improves performance on @vonconrad 's answer taking advantage of Rail's optimized .pluck vs .map by orders of magnitude especially if used on models with high number of records, though you likely would not used the entire options model method in a view for example.

module ActiveRecord
  class Base
    def self.to_options(title=:name)
      self.pluck(title.to_sym, :id)
    end
  end
end

Here are the benchmark results on a data model with nearly 300k records with the first version using .all.map and with the improved .pluck way.

 15.560000   2.950000  18.510000 ( 19.059607)
  0.390000   0.020000   0.410000 (  0.539866)

=> [
    [0] #<Benchmark::Tms:0x00007fb6091fa628 @label="all", @real=15.474819000111893, @cstime=0.0, @cutime=0.0, @stime=0.5800000000000001, @utime=14.480000000000004, @total=15.060000000000004>,
    [1] #<Benchmark::Tms:0x00007fb5f6a52978 @label="pluck", @real=0.8451100001111627, @cstime=0.0, @cutime=0.0, @stime=0.10000000000000009, @utime=0.5999999999999943, @total=0.6999999999999944>
]

Upvotes: 2

prusswan
prusswan

Reputation: 7091

Hmm, it seems that collection_select would work for the typical scenario:

<%= f.collection_select :stylist_id, Stylist.all, :id, :name %>

Upvotes: 28

vonconrad
vonconrad

Reputation: 25367

As much as RobinBrouwer's answer is correct, I just wanted to share a little nugget I came up with for one of our applications at work:

# config/initializers/to_options.rb
module ActiveRecord
  class Base
    def self.to_options(title=:name)
      self.all.map{|r| [r.send(title), r.id]}
    end
  end
end

Now, you can use to_options on any model, with the flexibility of picking any field for the option text (which defaults to name).

Model.to_options # => Creates select options with [m.name, m.id]
AnotherModel.to_options(:title) # => Creates select options with [m.title, m.id]

Shouldn't be hard to modify with ordering if necessary, either.

Upvotes: 5

RobinBrouwer
RobinBrouwer

Reputation: 973

As far as I know Rails doesn't have an easier way to create select tags. You could use a gem like formtastic (https://github.com/justinfrench/formtastic) or simple_form (https://github.com/plataformatec/simple_form), which make creating forms way easier. I prefer simple_form myself, so I'd advice you to try that out.

Another way is creating your own select helper that automatically gets the associated records from the database. You could also put the Stylist.order... stuff inside the Model:

# Model
def self.select
  Stylist.order("name").map { |s| [s.name, s.id] }
end

# Form
<%= f.select(:stylist_id, Stylist.select) %>

Makes your view look a bit nicer. :)

Upvotes: 1

Related Questions