ruby-on-railsform-for

Update multiple records from a table in a single form in rails?


I have a single page with 72 time slots each with a radio button beside it for available / unavailable.

I want the physician to be able to select whether they're available or not, then click "Update" when they're done.

This means the form must accept all 72 records.

I (think) I can work out how to do this with some very low level programming. But I don't know what the 'rails way' is?

What I know so far

What I've tried

Please don't use this idea if it's bad (which it probably is). But what I had in mind was to first generate all possible hours in the next 3 days

time_now = Time.now 
current_time =  time_now 
three_days = time_now + 3.days

hours = []
(current_time.beginning_of_hour.to_datetime.to_i .. three_days.beginning_of_hour.to_datetime.to_i).step(1.hour) do |date|
hours << Time.at(date)
end

    @hours = hours

Then, using these start times as keys in a hash, assuming available: false as the value in the hash (unless Availability.where(user: current_user).all says otherwise), and passing that to the view to set the default positions of the radio buttons, then the physician can update the radio buttons as they please and submit when they're done.

The problems are

i) I have no idea if this is a good/bad/ugly approach ii) (the main question) how would I go about creating such a form with a radio button for each key/value pair in the hash? (one single submit button)

Note: so far, the only forms I know in rails are via form_for, but I'm keen to use whatever is best practice for this type of form


Solution

  • There is no 'rails way' for this. Rails is opinionated in many ways. But, not in this way.

    It seems to me you have at least two parts to your problem. First is constructing an appropriate data structure. I think you can clean up your existing code a little by doing:

    time_now = Time.now
    start_time = time_now.beginning_of_hour.to_datetime.to_i
    end_time = (time_now + 3.hours).beginning_of_hour.to_datetime.to_i
    
    @hours = (start_time..end_time).step(1.hour).map {|t| Time.at(t)}
    

    ...which is mostly cosmetic. (Please note that I'm using 3.hours instead of 3.days just to keep the output shorter.) It saves you a couple of assignments and uses .map instead of that whole pushing to array bit that you're doing.

    I'm speculating that you'll want an array of hashes, not an array of times. Perhaps something like:

    time_now = Time.now
    start_time = time_now.beginning_of_hour.to_datetime.to_i
    end_time = (time_now + 3.hours).beginning_of_hour.to_datetime.to_i
    
    @hours = (start_time..end_time).step(1.hour).map {|t| {time_slot: Time.at(t)}}
    

    ...and that you'll probably want to include whether the time_slot is currently available:

    time_now = Time.now
    start_time = time_now.beginning_of_hour.to_datetime.to_i
    end_time = (time_now + 3.hours).beginning_of_hour.to_datetime.to_i
    
    @hours = (start_time..end_time).step(1.hour).map do |t| 
      time_slot = Time.at(t)
      {time_slot: time_slot, time_slot_available?: time_slot_available?(time_slot)}
    end
    

    ...which requires a time_slot_available? method. For testing purposes, I've stubbed this as:

    def time_slot_available?(time_slot)
      [true, false].sample
    end
    

    ...which will end up giving you something like:

    [
      {:time_slot=>2020-08-21 08:00:00 -0700, :time_slot_available?=>false}, 
      {:time_slot=>2020-08-21 09:00:00 -0700, :time_slot_available?=>true}, 
      {:time_slot=>2020-08-21 10:00:00 -0700, :time_slot_available?=>false}, 
      {:time_slot=>2020-08-21 11:00:00 -0700, :time_slot_available?=>false}
    ]
    

    Naturally, time_slot_available? would check the database. This particular approach is bad because it probably yields and N+1, but this is just for illustration purposes.

    Now, you could create a single, massive form as you are describing. Or, you could generate a radio button with no form for each time_slot and then attach some javascript that makes a ajax call each time a button is clicked - setting the time_slot as available or not available, as appropriate.

    I like the latter from a UX perspective (the work gets saved as the user clicks buttons), but it is a choice of preference, style, programming skill, and probably other stuff.