CppNoob CppNoob - 5 months ago 21
Ruby Question

Ruby on Rails: How do I write a view to create a composite object?

I am modeling a

Bouquet
of
Flower
objects. I want to write a form which will allow me to create a
Bouquet
, create instances of
Flower
objects, and put them in the
Bouquet
.

I have modeled the following relationships.


  1. Bouquet
    -- has_many -->
    Flower
    s.

  2. Flower
    -- belongs_to -->
    Bouquet

  3. Deleting a
    Bouquet
    will also delete the
    Flower
    s in it from the database.



Are there any patterns for creating a view which achieves the above.

Answer

You can accomplish this with nested attributes. Your models will have a basic has_many/belongs_to association setup:

# app/models/bouquet.rb
class Bouquet < ActiveRecord::Base
  has_many :flowers, dependent: :destroy
  accepts_nested_attributes_for :flowers
end

# app/models/flower.rb
class Flower < ActiveRecord::Base
  belongs_to :bouquet
end

Note the accepts_nested_attributes_for method, which allows you to save attributes on associated Flower records through the Bouquet parent.

In your strong params for the BouquetsController, you can whitelist the nested attributes for the bouquet's flowers, and built out some blank flowers to start with:

# app/controllers/bouquets_controller.rb
class BouquetsController < ApplicationController
  def new
    @bouquet = Bouquet.new
    5.times { @bouquet.flowers.build }
  end

  private

  def bouquet_params
    params.require(:bouquet).permit(:name, :size, flowers_attributes: [:color, :species])
  end
end

Now, in your form you can use fields_for to fill out information for your flowers within the bouquet:

<!-- app/views/bouquets/new.html.erb -->

<div class="create-form">
  <%= form_for @bouquet do |f| %>
    <%= f.label "Bouquet Name" %>  
    <%= f.text_field :name %>

    <%= f.label "Size of Bouquet" %>
    <%= f.text_field :size %>

    <%= fields_for :flower do |ff| %>
      <%= ff.label "Flower Name" %>
      <%= ff.text_field :name %>

      <%= ff.label "Species of Flower" %>
      <%= ff.text_field :species %>
    <% end %>

    <%= f.submit %>
  <% end %>
</div>

Passing a symbol of a relationship name to fields_for allows it to infer from that relationship how to render the nested fields within the form.

The above setup allows you to create a bouquet, and have its nested flowers automatically associated with it. Deleting a bouquet will remove all of its flowers as well. You can add some JS to make this form more dynamic (a button to add/remove individual flowers, for example).

Hope this helps!

Comments