Use a nested dynamic form with a has_many :through association in Rails.
Tags: rails, tutorial, ruby
Published: 2021-01-17 16:39:12

## `has_many :through` at a high-level.
A `has_many :through` association is how we setup many-to-many connections with another model. When we define a `has_many :through` relationship on a model, we're saying that we can potentially link the model to another model by going _through_ a third model. I think [the Rails guides](https://guides.rubyonrails.org/association_basics.html#the-has-many-through-association) do a great job of illustrating an example of how to use this relationship. Let's think of a different example. Say we have a company, a company has employees through an employment.

```ruby
class Company < ApplicationRecord
  has_many :employments
  has_many :employees, through: :employments
end

class Employment < ApplicationRecord
  belongs_to :employee
  belongs_to :company
end

class Employee < ApplicationRecord
  has_many :employments
  has_many :companies, through: :employments
end
```

Our migrations would look like this:

```ruby
class CreateCompanies < ActiveRecord::Migration[6.0]
  def change
    create_table :companies do |t|
      t.string :name
      t.timestamps
    end

    create_table :employees do |t|
      t.string :name
      t.timestamps
    end

    create_table :employments do |t|
      t.belongs_to :employee
      t.belongs_to :company
      t.datetime :start_date
      t.datetime :end_date
      t.string :role
      t.timestamps
    end
  end
end
```

When an employee "leaves" a company, we don't have to create the employee all over again. We make a new employment linked to their employee record and their new company.

<!--more-->

## When should I reach for `has_many :through`
In the example above, we touched on _when_ you should go for this association type. We don't want an employee tied to a company. We want there to be some intermediary relationship that links the two things together. In this case, it's because we want to store some extra data on the employment table, like start date, end date, and role name. An employee will have multiple employments throughout their life. Now we have a table that keeps track of them. A relationship allows us to ask `employee.companies` and shortcut through the `employments` relationship to grab all the companies for which an employee has worked. My advice is to reach for `has_many :through` when you need additional data on the association.

## Introducing Pantry
I'm big on meal prepping, recipe saving, and cooking in general. I thought a recipe organization application would be a great example because it is interesting, fresh (not a to-do app!), and provides some potential challenges.

Pantry will be the name of the application that we'll be building to illustrate how to use `has_many :through,` nested forms, nested_attributes, and dynamic fields using a third-party library. Let's get started.

First, we need to set requirements, expectations and then think through this application's business logic/relationships.

Pantry is a proof-of-concept application. There will be no extraneous styling or interactivity. We're talking bare bones to illustrate a concept only. We will create a recipe using a form, and a recipe has a name and ingredients. Ingredients have a name, an amount, and a description.

I think our relationships will likely look something like this:

![Diagram showing the relationships between recipes, recipe_ingredients, and ingredients. recipes have an integer column id and string column title, recipes has_many :recipe_ingredients and has_many :ingredients, through: :recipe_ingredients. ingredients has an integer column id and string column name, ingredients has_many :recipe_ingredients and has_many :recipes, through: :recipe_ingredients. a recipe_ingredient has integer column id, integer column recipe_id, integer column ingredient_id, string column amount, and string column description, recipe_ingredients belongs_to :recipe and recipe_ingredient belongs_to :ingredient](/assets/images/blog-images/has-many-through-diagram.png)

There are many more attributes each of these could have, and I might expand on that in a future blog post to illustrate other concepts, but we are keeping it as simple as possible for now.


### Getting started
Before we dive in, you can find the completed code [here](https://github.com/meaganewaller/pantry). I will break out each section into branches and link them as we get to them.

I'm using Rails version 6.1.1 and Ruby version 2.7.2.

First things first, we need to create our Rails application:

```bash
rails new pantry
cd pantry
rails db:create
rails webpacker:install
```

(You may not have to do the `webpacker:install` step, I did because I didn't have yarn installed already.)

Now we can view our rails application at `localhost:3000`.

```bash
rails server
```

### Creating the models

First, we will create the `Recipe.`

```bash
rails g model Recipe title
```

And the migration this generation creates.

```ruby
class CreateRecipes < ActiveRecord::Migration[6.1]
  def change
    create_table :recipes do |t|
      t.string :title

      t.timestamps
    end
  end
end
```

Next, let's create the `Ingredient.`
```bash
rails g model Ingredient name
```

And the migration this generation creates.

```ruby
class CreateIngredients < ActiveRecord::Migration[6.1]
  def change
    create_table :ingredients do |t|
      t.string :name

      t.timestamps
    end
  end
end
```

Last, we create `RecipeIngredient.`

```bash
rails g model RecipeIngredient amount description recipe:belongs_to ingredient:belongs_to
```

And the migration this generation creates.
```ruby
class CreateRecipeIngredients < ActiveRecord::Migration[6.1]
  def change
    create_table :recipe_ingredients do |t|
      t.string :amount
      t.string :description
      t.belongs_to :recipe, null: false, foreign_key: true
      t.belongs_to :ingredient, null: false, foreign_key: true

      t.timestamps
    end
  end
end
```

We are going to add an index to the `belongs_to` fields.

```ruby
class CreateRecipeIngredients < ActiveRecord::Migration[6.1]
  def change
    create_table :recipe_ingredients do |t|
      t.string :amount
      t.string :description
      t.belongs_to :recipe, null: false, foreign_key: true
      t.belongs_to :ingredient, null: false, foreign_key: true

      t.timestamps
    end
  end
end
```

Now, let's run the migrations!

```bash
rails db:migrate
```

Set up the associations inside of our models.

`app/models/recipe.rb`
```ruby
class Recipe < ApplicationRecord
  has_many :recipe_ingredients
  has_many :ingredients, through: :recipe_ingredients
end
```

`app/models/ingredient.rb`
```ruby
class Ingredient < ApplicationRecord
  has_many :recipe_ingredients
  has_many :recipes, through: :recipe_ingredients
end
```

`app/models/recipe_ingredient.rb`
```ruby
class RecipeIngredient < ApplicationRecord
  belongs_to :recipe
  belongs_to :ingredient
end
```

Let's import the recipe for [Best Ever Grilled Cheese Sandwich](https://www.tasteofhome.com/recipes/the-best-ever-grilled-cheese-sandwich/) into our seeds file.

`db/seeds.rb`
```ruby
grilled_cheese = Recipe.create(title: "Grilled Cheese")
butter = Ingredient.create(name: "Butter")
bread = Ingredient.create(name: "Sourdough Bread")
mayonnaise = Ingredient.create(name: "Mayonnaise")
manchego_cheese = Ingredient.create(name: "Manchego Cheese")
onion_powder = Ingredient.create(name: "Onion Powder")
white_cheddar = Ingredient.create(name: "Sharp White Cheddar Cheese")
monterey_jack = Ingredient.create(name: "Monterey Jack Cheese")
gruyere_cheese = Ingredient.create(name: "Gruyere Cheese")
brie_cheese = Ingredient.create(name: "Brie Cheese")

RecipeIngredient.create(recipe: grilled_cheese, ingredient: butter, amount: "6 tbsp", description: "softened, divided")
RecipeIngredient.create(recipe: grilled_cheese, ingredient: bread, amount: "8 slices")
RecipeIngredient.create(recipe: grilled_cheese, ingredient: mayonnaise, amount: "3 tbsp")
RecipeIngredient.create(recipe: grilled_cheese, ingredient: manchego_cheese, amount: "3 tbsp", description: "finely shredded")
RecipeIngredient.create(recipe: grilled_cheese, ingredient: onion_powder, amount: "1/8 tsp")
RecipeIngredient.create(recipe: grilled_cheese, ingredient: white_cheddar, amount: "1/2 cup", description: "shredded")
RecipeIngredient.create(recipe: grilled_cheese, ingredient: gruyere_cheese, amount: "1/2 cup", description: "shredded")
RecipeIngredient.create(recipe: grilled_cheese, ingredient: brie_cheese, amount: "4 oz", description: "rind removed and sliced")
```

When we run this rails task, it will execute the contents of the `db/seeds.rb` file.
```bash
rails db:seed
```

We can check out what our data looks like in the rails console.
```ruby
# In the rails console
± rails c

irb(main):005:0> Recipe.first
  Recipe Load (0.1ms)  SELECT "recipes".* FROM "recipes" ORDER BY "recipes"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<Recipe id: 3, title: "Grilled Cheese", created_at: "2021-01-14 02:05:16.753424000 +0000", updated_at: "2021-01-14 02:05:16.753424000 +0000">
irb(main):006:0> Recipe.first.ingredients
  Recipe Load (0.2ms)  SELECT "recipes".* FROM "recipes" ORDER BY "recipes"."id" ASC LIMIT ?  [["LIMIT", 1]]
  Ingredient Load (0.2ms)  SELECT "ingredients".* FROM "ingredients" INNER JOIN "recipe_ingredients" ON "ingredients"."id" = "recipe_ingredients"."ingredient_id" WHERE "recipe_ingredients"."recipe_id" = ? /* loading for inspect */ LIMIT ?  [["recipe_id", 3], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Ingredient id: 16, name: "Butter", created_at: "2021-01-14 02:05:16.762877000 +0000", updated_at: "2021-01-14 02:05:16.762877000 +0000">, #<Ingredient id: 17, name: "Sourdough Bread", created_at: "2021-01-14 02:05:16.766733000 +0000", updated_at: "2021-01-14 02:05:16.766733000 +0000">, #<Ingredient id: 18, name: "Mayonnaise", created_at: "2021-01-14 02:05:16.770198000 +0000", updated_at: "2021-01-14 02:05:16.770198000 +0000">, #<Ingredient id: 19, name: "Manchego Cheese", created_at: "2021-01-14 02:05:16.773554000 +0000", updated_at: "2021-01-14 02:05:16.773554000 +0000">, #<Ingredient id: 20, name: "Onion Powder", created_at: "2021-01-14 02:05:16.776771000 +0000", updated_at: "2021-01-14 02:05:16.776771000 +0000">, #<Ingredient id: 21, name: "Sharp White Cheddar Cheese", created_at: "2021-01-14 02:05:16.779594000 +0000", updated_at: "2021-01-14 02:05:16.779594000 +0000">, #<Ingredient id: 23, name: "Gruyere Cheese", created_at: "2021-01-14 02:05:16.784709000 +0000", updated_at: "2021-01-14 02:05:16.784709000 +0000">, #<Ingredient id: 24, name: "Brie Cheese", created_at: "2021-01-14 02:05:16.789495000 +0000", updated_at: "2021-01-14 02:05:16.789495000 +0000">]>
irb(main):007:0> Recipe.first.recipe_ingredients
  Recipe Load (0.1ms)  SELECT "recipes".* FROM "recipes" ORDER BY "recipes"."id" ASC LIMIT ?  [["LIMIT", 1]]
  RecipeIngredient Load (0.2ms)  SELECT "recipe_ingredients".* FROM "recipe_ingredients" WHERE "recipe_ingredients"."recipe_id" = ? /* loading for inspect */ LIMIT ?  [["recipe_id", 3], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<RecipeIngredient id: 5, amount: "6 tbsp", description: "softened, divided", recipe_id: 3, ingredient_id: 16, created_at: "2021-01-14 02:05:16.801631000 +0000", updated_at: "2021-01-14 02:05:16.801631000 +0000">, #<RecipeIngredient id: 6, amount: "8 slices", description: nil, recipe_id: 3, ingredient_id: 17, created_at: "2021-01-14 02:05:16.805122000 +0000", updated_at: "2021-01-14 02:05:16.805122000 +0000">, #<RecipeIngredient id: 7, amount: "3 tbsp", description: nil, recipe_id: 3, ingredient_id: 18, created_at: "2021-01-14 02:05:16.808517000 +0000", updated_at: "2021-01-14 02:05:16.808517000 +0000">, #<RecipeIngredient id: 8, amount: "3 tbsp", description: "finely shredded", recipe_id: 3, ingredient_id: 19, created_at: "2021-01-14 02:05:16.812203000 +0000", updated_at: "2021-01-14 02:05:16.812203000 +0000">, #<RecipeIngredient id: 9, amount: "1/8 tsp", description: nil, recipe_id: 3, ingredient_id: 20, created_at: "2021-01-14 02:05:16.815748000 +0000", updated_at: "2021-01-14 02:05:16.815748000 +0000">, #<RecipeIngredient id: 10, amount: "1/2 cup", description: "shredded", recipe_id: 3, ingredient_id: 21, created_at: "2021-01-14 02:05:16.819594000 +0000", updated_at: "2021-01-14 02:05:16.819594000 +0000">, #<RecipeIngredient id: 11, amount: "1/2 cup", description: "shredded", recipe_id: 3, ingredient_id: 23, created_at: "2021-01-14 02:05:16.825120000 +0000", updated_at: "2021-01-14 02:05:16.825120000 +0000">, #<RecipeIngredient id: 12, amount: "4 oz", description: "rind removed and sliced", recipe_id: 3, ingredient_id: 24, created_at: "2021-01-14 02:05:16.829869000 +0000", updated_at: "2021-01-14 02:05:16.829869000 +0000">]>
```

Now, let's create another recipe that uses some of the same ingredients. I'll be using this [Gruyere and White Cheddar Mac and Cheese recipe.](https://www.oliviascuisine.com/mac-and-cheese/)

`db/seeds.rb`
```ruby
# Creating another recipe that uses some of the same ingredients, switch to find_or_create_by so I don't create duplicate records.
grilled_cheese = Recipe.find_or_create_by(title: "Grilled Cheese")
mac = Recipe.find_or_create_by(title: "Gruyere and White Cheddar Mac and Cheese")


butter = Ingredient.find_or_create_by(name: "Butter")
bread = Ingredient.find_or_create_by(name: "Sourdough Bread")
mayonnaise = Ingredient.find_or_create_by(name: "Mayonnaise")
manchego_cheese = Ingredient.find_or_create_by(name: "Manchego Cheese")
onion_powder = Ingredient.find_or_create_by(name: "Onion Powder")
white_cheddar = Ingredient.find_or_create_by(name: "Sharp White Cheddar Cheese")
monterey_jack = Ingredient.find_or_create_by(name: "Monterey Jack Cheese")
gruyere_cheese = Ingredient.find_or_create_by(name: "Gruyere Cheese")
brie_cheese = Ingredient.find_or_create_by(name: "Brie Cheese")
elbows = Ingredient.find_or_create_by(name: "Elbow Macaroni")
breadcrumbs = Ingredient.find_or_create_by(name: "Seasoned Breadcrumbs")
flour = Ingredient.find_or_create_by(name: "Flour")
milk = Ingredient.find_or_create_by(name: "Milk")
half_and_half = Ingredient.find_or_create_by(name: "Half and Half")
nutmeg = Ingredient.find_or_create_by(name: "Nutmeg")
salt = Ingredient.find_or_create_by(name: "Salt")
pepper = Ingredient.find_or_create_by(name: "Pepper")
parmigiano = Ingredient.find_or_create_by(name: "Parmigiano Reggiano")
olive_oil = Ingredient.find_or_create_by(name: "Olive Oil")

RecipeIngredient.find_or_create_by(recipe: grilled_cheese, ingredient: butter, amount: "6 tbsp", description: "softened, divided")
RecipeIngredient.find_or_create_by(recipe: grilled_cheese, ingredient: bread, amount: "8 slices")
RecipeIngredient.find_or_create_by(recipe: grilled_cheese, ingredient: mayonnaise, amount: "3 tbsp")
RecipeIngredient.find_or_create_by(recipe: grilled_cheese, ingredient: manchego_cheese, amount: "3 tbsp", description: "finely shredded")
RecipeIngredient.find_or_create_by(recipe: grilled_cheese, ingredient: onion_powder, amount: "1/8 tsp")
RecipeIngredient.find_or_create_by(recipe: grilled_cheese, ingredient: white_cheddar, amount: "1/2 cup", description: "shredded")
RecipeIngredient.find_or_create_by(recipe: grilled_cheese, ingredient: gruyere_cheese, amount: "1/2 cup", description: "shredded")
RecipeIngredient.find_or_create_by(recipe: grilled_cheese, ingredient: brie_cheese, amount: "4 oz", description: "rind removed and sliced")

RecipeIngredient.find_or_create_by(recipe: mac, ingredient: elbows, amount: "1 lb")
RecipeIngredient.find_or_create_by(recipe: mac, ingredient: gruyere_cheese, amount: "1 lb", description: "grated")
RecipeIngredient.find_or_create_by(recipe: mac, ingredient: white_cheddar, amount: "1 lb", description: "grated")
RecipeIngredient.find_or_create_by(recipe: mac, ingredient: breadcrumbs, amount: "1/3 cup")
RecipeIngredient.find_or_create_by(recipe: mac, ingredient: butter, amount: "1/2 stick")
RecipeIngredient.find_or_create_by(recipe: mac, ingredient: flour, amount: "1/4 cup")
RecipeIngredient.find_or_create_by(recipe: mac, ingredient: milk, amount: "3 cup")
RecipeIngredient.find_or_create_by(recipe: mac, ingredient: half_and_half, amount: "1 cup")
RecipeIngredient.find_or_create_by(recipe: mac, ingredient: nutmeg, amount: "pinch")
RecipeIngredient.find_or_create_by(recipe: mac, ingredient: salt, description: "to taste")
RecipeIngredient.find_or_create_by(recipe: mac, ingredient: pepper, description: "to taste")
RecipeIngredient.find_or_create_by(recipe: mac, ingredient: parmigiano, description: "to taste")
RecipeIngredient.find_or_create_by(recipe: mac, ingredient: olive_oil)
```

We can jump back into the rails console and see our new data.

```ruby
± rails c

irb(main):001:0> Recipe.first
=> #<Recipe id: 3, title: "Grilled Cheese", created_at: "2021-01-14 02:05:16.753424000 +0000", updated_at: "2021-01-14 02:05:16.753424000 +0000">
irb(main):002:0> Recipe.last
=> #<Recipe id: 4, title: "Gruyere and White Cheddar Mac and Cheese", created_at: "2021-01-14 02:15:40.205860000 +0000", updated_at: "2021-01-14 02:15:40.205860000 +0000">
irb(main):003:0> Ingredient.count
=> 19
irb(main):004:0> RecipeIngredient.count
=> 21
```

[See the code at this point](https://github.com/meaganewaller/pantry/tree/create-models)


### Creating controllers

We're going to create our controller. We won't have a way to make standalone ingredients, so we only need a recipe controller.

```bash
rails g controller recipes
```

Let's setup our routes now.

`config/routes.rb`
```ruby
Rails.application.routes.draw do
  resources :recipes
end
```

We can see what using the `resources` method in our routes provided for us.

```bash
± rails routes | grep recipes

recipes     GET    /recipes(.:format)                recipes#index
            POST   /recipes(.:format)                recipes#create
new_recipe  GET    /recipes/new(.:format)            recipes#new
edit_recipe GET    /recipes/:id/edit(.:format)       recipes#edit
recipe      GET    /recipes/:id(.:format)            recipes#show
            PATCH  /recipes/:id(.:format)            recipes#update
            PUT    /recipes/:id(.:format)            recipes#update
            DELETE /recipes/:id(.:format)            recipes#destroy
```

Let's flesh out our controller.

First, let's start up the Rails server.

```bash
rails s
```

Go to `localhost:3000/recipes` in your browser. You should see an error. Let's fix that.

`app/controllers/recipes_controller.rb`
```ruby
class RecipesController < ApplicationController

  def index
    @recipes = Recipe.all
  end
end
```

`app/views/recipes/index.html.erb`
```html
<h1>Recipes</h1>

<% @recipes.each do |recipe| %>
  <h2>
    <%= recipe.title %>
  </h2>

  <ul>
  <% recipe.recipe_ingredients.each do |recipe_ingredient| %>
    <li>
      <%= recipe_ingredient.amount %> <%= recipe_ingredient.ingredient.name %>, <%= recipe_ingredient.description %>
    </li>
  <% end %>
  </ul>
<% end %>
```

Now refresh to see our index. We will see our listing of the recipes we created.

`app/controllers/recipes_controller.rb`
```ruby
class RecipesController < ApplicationController

  def index
    @recipes = Recipe.all
  end

  def show
    @recipe = Recipe.find(params[:id])
  end
end
```

`app/views/recipes/show.html.erb`
```html
<h1><%= @recipe.title %></h1>

<ul>
  <% @recipe.recipe_ingredients.each do |recipe_ingredient| %>
    <li>
      <%= recipe_ingredient.amount %> <%= recipe_ingredient.ingredient.name %>, <%= recipe_ingredient.description %>
    </li>
  <% end %>
</ul>

<%= link_to "Back to all recipes", recipes_path %>
```

Visit this page by going to `localhost:3000/recipes/RECIPE_ID_HERE`, find the recipe id in your console, `Recipe.first.id`, etc.

`app/controllers/recipes_controller.rb`
```ruby
class RecipesController < ApplicationController

  def index
    @recipes = Recipe.all
  end

  def show
    @recipe = Recipe.find(params[:id])
  end

  def new
    @recipe = Recipe.new
  end
end
```

`app/views/recipes/new.html.erb`
```html
<h1>Create a new recipe</h1>

<%= form_with model: @recipe do |form| %>
  <%= form.text_field :title, placeholder: "Recipe Title" %>
  <%= form.submit %>
<% end %>
```

Visit this page at `localhost:3000/recipes/new`. If you try and create a recipe, you will get an error because our `create` action isn't defined yet.

`app/controllers/recipes_controller.rb`
```ruby
class RecipesController < ApplicationController

  def index
    @recipes = Recipe.all
  end

  def show
    @recipe = Recipe.find(params[:id])
  end

  def new
    @recipe = Recipe.new
  end

  def create
    @recipe = Recipe.create(recipe_params)

    if @recipe.save
      redirect_to @recipe
    else
      render action: "new"
    end
  end

  protected
  def recipe_params
    params.require(:recipe).permit(:title)
  end
end
```

Refresh the new page and create a recipe. You'll see that we're able to save a recipe without a title. Let's add a validation to ensure every recipe at least has a title.

`app/models/recipe.rb`
```ruby
class Recipe < ApplicationRecord
  has_many :recipe_ingredients
  has_many :ingredients, through: :recipe_ingredients

  validates_presence_of :title
end
```

Now we get kicked back to the new page. We're not going to worry about flash messages or anything at this time. We might find we need them later, though.

Let's add our edit action now.

`app/controllers/recipes_controller.rb`
```ruby
class RecipesController < ApplicationController

  def index
    @recipes = Recipe.all
  end

  def show
    @recipe = Recipe.find(params[:id])
  end

  def new
    @recipe = Recipe.new
  end

  def create
    @recipe = Recipe.create(recipe_params)

    if @recipe.save
      redirect_to @recipe
    else
      render action: "new"
    end
  end

  def edit
    @recipe = Recipe.find(params[:id])
  end

  protected
  def recipe_params
    params.require(:recipe).permit(:title)
  end
end
```

`app/views/recipes/edit.html.erb`
```html
<h1>Edit <%= @recipe.title %></h1>

<%= form_with model: @recipe do |form| %>
  <%= form.text_field :title, placeholder: "Recipe Title" %>
  <%= form.submit %>
<% end %>
```

Visit the edit page of a recipe with `localhost:3000/recipes/RECIPE_ID/edit`. Once again, we'll see that we can't edit a recipe because the update action doesn't exist yet.

`app/controllers/recipes_controller.rb`
```ruby
class RecipesController < ApplicationController

  def index
    @recipes = Recipe.all
  end

  def show
    @recipe = Recipe.find(params[:id])
  end

  def new
    @recipe = Recipe.new
  end

  def create
    @recipe = Recipe.create(recipe_params)

    if @recipe.save
      redirect_to @recipe
    else
      render action: "new"
    end
  end

  def edit
    @recipe = Recipe.find(params[:id])
  end

  def update
    @recipe = Recipe.find(params[:id])

    if @recipe.update(recipe_params)
      redirect_to @recipe
    else
      render action: "edit"
    end
  end

  protected
  def recipe_params
    params.require(:recipe).permit(:title)
  end
end
```

Now try and edit a recipe.

Lastly, let's add an action to destroy a recipe, we won't go over deleting recipes in this tutorial, but we've added it to our controller for completion's sake. At the end of this tutorial, I have ideas to further this application. Adding deletion functionality is one enhancement you could add.
```ruby
class RecipesController < ApplicationController

  def index
    @recipes = Recipe.all
  end

  def show
    @recipe = Recipe.find(params[:id])
  end

  def new
    @recipe = Recipe.new
  end

  def create
    @recipe = Recipe.create(recipe_params)

    if @recipe.save
      redirect_to @recipe
    else
      render action: "new"
    end
  end

  def edit
    @recipe = Recipe.find(params[:id])
  end

  def update
    @recipe = Recipe.find(params[:id])

    if @recipe.update(recipe_params)
      redirect_to @recipe
    else
      render action: "edit"
    end
  end

  def destroy
    @recipe = Recipe.find(params[:id])
    @recipe.destroy

    redirect_to recipes_url
  end

  protected
  def recipe_params
    params.require(:recipe).permit(:title)
  end
end
```

In our recipes form, we're only inputting the title of the recipe. In the next section, we will look into how to create a nested form to add ingredients to a recipe.

[View all the code up to this point](https://github.com/meaganewaller/pantry/tree/creating-controller-and-routes)



### Rendering nested forms
We want to create `recipe_ingredients` when we make a `recipe.` We do that by using the `fields_for` that Rails form builder provides. For now, we will only include one field on the `recipe_ingredient` fields to keep this focused.

`app/views/recipes/new.html.erb`
```html
<h1>Create a new recipe</h1>

<%= form_with model: @recipe do |recipe_form| %>
  <%= recipe_form.text_field :title, placeholder: "Recipe Title" %>
  <h2>Ingredients</h2>
  <%= recipe_form.fields_for :recipe_ingredients do |recipe_ingredient_form| %>
    <%= recipe_ingredient_form.text_field :amount, placeholder: "Amount" %>
  <% end %>

  <br />
  <%= recipe_form.submit %>
<% end %>
```
Now go to the new recipe page and create a recipe with a `recipe_ingredient` amount.


Let's go into the rails console and see what we just created.
```ruby
± rails c
irb(main):001:0> Recipe.last
   (2.0ms)  SELECT sqlite_version(*)
  Recipe Load (0.1ms)  SELECT "recipes".* FROM "recipes" ORDER BY "recipes"."id" DESC LIMIT ?  [["LIMIT", 1]]
=> #<Recipe id: 7, title: "Cheesy Broccoli", created_at: "2021-01-14 15:26:17.638397000 +0000", updated_at: "2021-01-14 15:26:17.638397000 +0000">
irb(main):002:0> Recipe.last.recipe_ingredients
  Recipe Load (0.1ms)  SELECT "recipes".* FROM "recipes" ORDER BY "recipes"."id" DESC LIMIT ?  [["LIMIT", 1]]
  RecipeIngredient Load (0.4ms)  SELECT "recipe_ingredients".* FROM "recipe_ingredients" WHERE "recipe_ingredients"."recipe_id" = ? /* loading for inspect */ LIMIT ?  [["recipe_id", 7], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy []>
```

Our recipe has no recipe ingredients associated with it. Hmmm 🤔. Let's take a look at the logs when we submitted the form.

```bash
Started POST "/recipes" for 127.0.0.1 at 2021-01-14 10:26:17 -0500
Processing by RecipesController#create as HTML
  Parameters: {"authenticity_token"=>"[FILTERED]", "recipe"=>{"title"=>"Cheesy Broccoli", "recipe_ingredients"=>{"amount"=>"1 cup"}}, "commit"=>"Create Recipe"}
  Unpermitted parameter: :recipe_ingredients
```
Aha! There's the culprit. We tried to pass a parameter that our `recipe_params` method doesn't know about, `recipe_ingredients.` Let's remedy that.

#### Nested Attributes
Rails nested attributes have this naming convention. We append `_attributes` to the end of the collection name. We won't go into why/how it works, be aware that even though the parameters we saw above showed `recipe_ingredients,` we need to name them the way rails expect inside our `recipe_params.`

`app/controllers/recipes_controller.rb`
```ruby
def recipe_params
  params.require(:recipe).permit(:title, recipe_ingredients_attributes: [:amount])
end
```

Okay, should we working now! Let's try and save a new recipe and check out the logs.
```bash
Started POST "/recipes" for 127.0.0.1 at 2021-01-14 10:31:26 -0500
Processing by RecipesController#create as HTML
  Parameters: {"authenticity_token"=>"[FILTERED]", "recipe"=>{"title"=>"Spaghetti", "recipe_ingredients"=>{"amount"=>"28 oz"}}, "commit"=>"Create Recipe"}
Unpermitted parameter: :recipe_ingredients
```

Still unpermitted! Because we have to tell the `Recipe` model to accept the nested attributes for `recipe_ingredients` (this `accepts_nested_attributes` is where Rails creates the `#{model_name}_attributes` method that it's using inside of the `recipe_params.`)

`app/models/recipe.rb`
```ruby
class Recipe < ApplicationRecord
  has_many :recipe_ingredients
  has_many :ingredients, through: :recipe_ingredients

  validates_presence_of :title

  accepts_nested_attributes_for :recipe_ingredients
end
```

When we refresh the new page, we'll find that no field shows up on our form for `recipe_ingredients.` That's because we've got to build our first `recipe_ingredient` inside of the `new` action.

`app/controllers/recipes_controller.rb`
```ruby
def new
  @recipe = Recipe.new
  @recipe.recipe_ingredients.build
end
```

Finally! We've got the recipe ingredient field showing. We're accepting nested attributes. Let's try to create a `recipe` with a `recipe_ingredient` on it.

Still not creating, and now the form is re-rendering `new`. At this point, we need to add flash messages so we can see errors in the form.

#### Adding flash notices
Inside of our controller, we're going to use the standard Rails way of creating and rendering flash messages for our create action.

`app/controllers/recipes_controller.rb`
```ruby
def create
  @recipe = Recipe.new(recipe_params)
  respond_to do |format|
    if @recipe.save
      format.html { redirect_to @recipe, notice: "Recipe was successfully created."}
      format.json { render :show, status: :created, location: @recipe }
    else
      format.html { render :new }
      format.json { render json: @recipe.errors, status: :unproccessable_entity }
    end
  end
end
```

Below is the standard Rails way of rendering errors in a form.

`app/views/recipes/new.html.erb`
```html
<h1>Create a new recipe</h1>

<%= form_with model: @recipe do |recipe_form| %>
  <% if @recipe.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@recipe.errors.count, "error") %> prohibited this recipe from being saved:</h2>
      <ul>
      <% @recipe.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <%= recipe_form.text_field :title, placeholder: "Recipe Title" %>
  <h2>Ingredients</h2>
  <%= recipe_form.fields_for :recipe_ingredients do |recipe_ingredient_form| %>
    <%= recipe_ingredient_form.text_field :amount, placeholder: "Amount" %>
  <% end %>

  <br />
  <%= recipe_form.submit %>
<% end %>
```

When we try and create our recipe again, we can see the error: `Recipe ingredients, ingredient must exist`. Okay, let's make an `ingredient` when we create a `recipe_ingredient.`

`app/views/recipes/new.html.erb`
```html
<h1>Create a new recipe</h1>

<%= form_with model: @recipe do |recipe_form| %>
  <% if @recipe.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@recipe.errors.count, "error") %> prohibited this recipe from being saved:</h2>
      <ul>
      <% @recipe.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <%= recipe_form.text_field :title, placeholder: "Recipe Title" %>
  <h2>Ingredients</h2>
  <%= recipe_form.fields_for :recipe_ingredients do |recipe_ingredient_form| %>
    <%= recipe_ingredient_form.text_field :amount, placeholder: "Amount" %>
    <%= recipe_ingredient_form.fields_for :ingredient do |ingredient_form| %>
      <%= ingredient_form.text_field :name %>
    <% end %>
  <% end %>

  <br />
  <%= recipe_form.submit %>
<% end %>
```
We've added a nested `fields_for` inside of our recipe_ingredients `fields_for,` this time for an ingredient, and it's got one field, a text field for its name. Let's create a recipe and view the logs and see what's happening.

```bash
Started POST "/recipes" for 127.0.0.1 at 2021-01-14 10:51:28 -0500
Processing by RecipesController#create as HTML
  Parameters: {"authenticity_token"=>"[FILTERED]", "recipe"=>{"title"=>"Macaroni", "recipe_ingredients_attributes"=>{"0"=>{"amount"=>"1lb", "ingredient"=>{"name"=>"Elbow Noodles"}}}}, "commit"=>"Create Recipe"}
Unpermitted parameter: :ingredient
```
Alright, this is the same thing we saw above with `recipe_ingredients.` We know exactly how to tackle this.

`app/controllers/recipes_controller.rb`
```ruby
def recipe_params
  params.require(:recipe).permit(:title, recipe_ingredients_attributes: [:amount, ingredient_attributes: [:name]])
end
```

We're still getting the same error:

```text
1 error prohibited this recipe from being saved:

    Recipe ingredients ingredient must exist
```

We still need to make sure that `recipe_ingredients` accept the `ingredients` nested attributes. Look at our `recipe_params,` `ingredients_attributes` is nested inside `recipe_ingredients_attributes`.

`app/models/recipe_ingredient.rb`
```ruby
class RecipeIngredient < ApplicationRecord
  belongs_to :recipe
  belongs_to :ingredient

  accepts_nested_attributes_for :ingredient
end
```

Like before, when we added the `accepts_nested_attributes_for :recipe_ingredients` to `Recipe,` the form for the ingredient isn't showing up. We need to build the initial `recipe_ingredient` in our controller.

`app/controllers/recipes_controller.rb`
```ruby
def new
  @recipe = Recipe.new
  @recipe.recipe_ingredients.build.build_ingredient
end
```

Now we can save our recipe with recipe ingredients!

#### Adding more recipe ingredients to a recipe
Okay, but no recipe requires only _one_ recipe ingredient. How do we add more to the form? You could build them all inside of the controller.

`app/controllers/recipes_controller.rb`
```ruby
def new
  @recipe = Recipe.new
  10.times { @recipe.recipe_ingredients.build.build_ingredient }
end
```

If you view the form, you have ten inputs for `recipe_ingredients` (I've added some line breaks to the form to make them easier to view)

`app/views/recipes/new.html.erb`
```html
<h1>Create a new recipe</h1>

<%= form_with model: @recipe do |recipe_form| %>
  <% if @recipe.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@recipe.errors.count, "error") %> prohibited this recipe from being saved:</h2>
      <ul>
      <% @recipe.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <%= recipe_form.text_field :title, placeholder: "Recipe Title" %>
  <h2>Ingredients</h2>
  <%= recipe_form.fields_for :recipe_ingredients do |recipe_ingredient_form| %>
    <%= recipe_ingredient_form.text_field :amount, placeholder: "Amount" %>
    <%= recipe_ingredient_form.fields_for :ingredient do |ingredient_form| %>
      <%= ingredient_form.text_field :name %>
    <% end %>
    <br />
  <% end %>

  <br />
  <%= recipe_form.submit %>
<% end %>
```

But, who can say that every recipe will have precisely ten ingredients? For some applications, this could be enough. Maybe we have an application where there is always an expected number of associations, and it never waivers from that. But for this application, we need this to be dynamic. We want to be able to add and remove recipe ingredients as required. So, let's get into it.

[View the code up to this point](https://github.com/meaganewaller/pantry/tree/nested-forms)

### Adding and Removing Recipe Ingredients using Cocoon
To add and remove recipe ingredients, we're going to use my favorite gem for handling this. The gem is called [cocoon](https://github.com/nathanvda/cocoon).
Cocoon defines two helper methods, `link_to_add_association` and `link_to_remove_association,` these are aptly named because what they do is provide links that, when clicked, will either build a new association and create the appropriate fields or will create a link with the ability to mark an association for deletion.

#### Setting up cocoon

To set up cocoon, we first need to add it to our `Gemfile.` Stop the rails server and add the gem to your Gemfile.

`Gemfile`
```ruby
gem 'cocoon'
```

Next, run `bundle install` to install the gem. Because we're using Rails 6, we also need to add the companion file for webpacker. Cocoon requires jQuery, so we need to install that as well.

```bash
yarn add jquery @nathanvda/cocoon
```

Next, let's add it to our application JavaScript.

`app/javascripts/packs/application.js`
```javascript
require("jquery")
require("@nathanvda/cocoon")
```

Cocoon requires the use of partials. We won't get into _how_ cocoon works, but these partials named expectedly are how cocoon knows what to render when we click 'add ingredient.'

`app/views/recipes/new.html.erb`
```html
<h2>Ingredients</h2>
<div id="recipeIngredients">
  <%= recipe_form.fields_for :recipe_ingredients do |recipe_ingredient_form| %>
    <%= render "recipe_ingredient_fields", f: recipe_ingredient_form %>
  <% end %>
  <div class='links'>
    <%= link_to_add_association 'add ingredient', recipe_form, :recipe_ingredients %>
  </div>
</div>
```

Now when we refresh we see this error: `Missing partial recipes/_recipe_ingredient_fields, application/_recipe_ingredient_fields with {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:raw, :erb, :html, :builder, :ruby, :jbuilder]}. Searched in:`

Let's create our partial.

`app/views/recipes/_recipe_ingredient_fields.html.erb`
```html
<div class="nested-fields">
  <div>
    <%= f.label :amount %>
    <%= f.text_field :amount %>
  </div>

  <div class="ingredients">
    <%= f.fields_for :ingredient do |ingredient| %>
      <%= render "ingredient_fields", f: ingredient %>
    <% end %>
  </div>

  <div>
    <%= f.label :description %>
    <%= f.text_field :description %>
  </div>
</div>
```

And let's add our `ingredient_fields` partial as well.

`app/views/recipes/_ingredient_fields.html.erb`
```html
<div>
  <%= f.label :name %>
  <%= f.text_field :name %>
</div>
```

We are also ready to remove the explicit building of `recipe_ingredients` in the `new` action in `RecipesController.`

Now, we have a form that has a link to `add ingredient.` However, nothing is happening when we click it. If I open up the dev tools, I see this message in the console:

```text
Uncaught ReferenceError: jQuery is not defined
```

A quick Google search leads me to [this page](https://www.botreetechnologies.com/blog/introducing-jquery-in-rails-6-using-webpacker/), and we still have a bit more configuration to do before jQuery is available in our application. Copy the code below into `config/webpack/environment.js` to define jQuery

`config/webpack/environment.js`
```js
const { environment } = require('@rails/webpacker')

const webpack = require('webpack')
environment.plugins.prepend('Provide',
  new webpack.ProvidePlugin({
    $: 'jquery/src/jquery',
    jQuery: 'jquery/src/jquery'
  })
)

module.exports = environment
```

When we click add ingredient, a recipe ingredient form is available for us to fill in! How cool is that? However, the ingredient portion of the form doesn't exist, so it's not displaying. Let's figure out how to fix that.

Cocoon's `link_to_add_association` takes four parameters, the parameter `html_options` has some special options that we can take advantage of. We're going to look at the `wrap_object` option. The `wrap_object` option is a proc that wraps the object. We can use this to build our ingredient when we use the add ingredient link.

Replace the `link_to_add_association` with this updated version:

```html
<%= link_to_add_association 'add ingredient', recipe_form, :recipe_ingredients, wrap_object: Proc.new { |recipe_ingredient| recipe_ingredient.build_ingredient; recipe_ingredient } %>
```

When we click add ingredient, the entire form, including the ingredient name, is available to fill in. When filling out my recipe and saving it, I noticed that the `description` for the `recipe_ingredient` didn't seem to be saving. Fix that by adding `description` to our `recipe_params` in the `RecipeController`

```ruby
def recipe_params
  params.require(:recipe).permit(:title, recipe_ingredients_attributes: [:amount, :description, ingredient_attributes: [:name]])
end
```

Let's add the ability to remove a recipe ingredient now.

`app/views/recipes/_recipe_ingredient_fields.html.erb`
```html
<div class="nested-fields">
  <div>
    <%= f.label :amount %>
    <%= f.text_field :amount %>
  </div>

  <div class="ingredients">
    <%= f.fields_for :ingredient do |ingredient| %>
      <%= render "ingredient_fields", f: ingredient %>
    <% end %>
  </div>

  <div>
    <%= f.label :description %>
    <%= f.text_field :description %>
  </div>

  <%= link_to_remove_association "remove ingredient", f %>
</div>
```

Now we can add and also remove our recipe ingredients! Awesome.

However, these changes only apply to the _new_ form. We want to be able to edit recipes and have the same functionality. Let's do some refactoring and make that happen.

[View the code at this point](https://github.com/meaganewaller/pantry/tree/dynamic-forms)

### Refactoring our form and updating recipes

We can pretty much reuse the same form for edit and new, so let's do that now. First, let's pull out the form and put it into a partial that we can render inside of the new view.

`app/views/recipes/_form.html.erb`

```html
<%= form_with model: @recipe do |recipe_form| %>
  <% if @recipe.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@recipe.errors.count, "error") %> prohibited this recipe from being saved:</h2>
      <ul>
      <% @recipe.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div>
    <%= recipe_form.label :title %>
    <%= recipe_form.text_field :title %>
  </div>

  <h2>Ingredients</h2>
  <div id="recipeIngredients">
    <%= recipe_form.fields_for :recipe_ingredients do |recipe_ingredient_form| %>
      <%= render "recipe_ingredient_fields", f: recipe_ingredient_form %>
    <% end %>
    <div class='links'>
      <%= link_to_add_association 'add ingredient', recipe_form, :recipe_ingredients, wrap_object: Proc.new { |recipe_ingredient| recipe_ingredient.build_ingredient; recipe_ingredient } %>
    </div>
  </div>

  <br />
  <%= recipe_form.submit %>
<% end %>
```

Now, our new view is much cleaner.

`app/views/recipes/new.html.erb`
```html
<h1>Create a new recipe</h1>
<%= render "form" %>
```

Let's go ahead and do the same to our edit view.

`app/views/recipes/edit.html.erb`
```html
<h1>Edit <%= @recipe.title %></h1>

<%= render "form" %>
```

Now our edit form allows us to add and remove recipe ingredients. While using the application, I noticed that we don't have a good way of viewing our recipes individually or getting to an edit page with a click. Let's add that to access an edit page to test out our functionality quickly.

Change the index page to be a listing of links to the recipes. We don't need to see the full recipe on the index page.

`app/views/recipes/index.html.erb`
```html
<h1>Recipes</h1>

<% @recipes.each do |recipe| %>
  <h2>
    <%= link_to recipe.title, recipe %>
  </h2>
<% end %>
```

Now, on the show page, we can add a link to edit.

`app/views/recipes/show.html.erb`
```html
<h1><%= @recipe.title %></h1>

<ul>
  <% @recipe.recipe_ingredients.each do |recipe_ingredient| %>
    <li>
      <%= recipe_ingredient.amount %> <%= recipe_ingredient.ingredient.name %>, <%= recipe_ingredient.description %>
    </li>
  <% end %>
</ul>

<%= link_to "Edit recipe", edit_recipe_path(@recipe) %> | <%= link_to "Back to all recipes", recipes_path %>
```

Now we can quickly get to our edit page. Let's ensure that things are working as expected. Try adding a new ingredient to a recipe and removing an existing ingredient. When checking to make sure that recipe ingredients get appropriately removed, I saw that they were not. Let's fix that.

#### Make sure recipe ingredients get removed

We need to make sure that `_destroy,` and `id` are permitted parameters in `recipe_params.`

```ruby
def recipe_params
  params.require(:recipe).permit(:title, recipe_ingredients_attributes: [:amount, :description, :_destroy, :id, ingredient_attributes: [:name, :id]])
end
```

We also need to add `allow_destroy: true` to our `accepts_nested_attributes`. Now we can add and remove recipe ingredients successfully.

`app/models/recipe.rb`
```ruby
accepts_nested_attributes_for :recipe_ingredients, allow_destroy: true
```

Let's make sure that we are taking advantage of the fact that we have a `has_many :through.` When it comes to ingredients, we want the `name` field to be unique. If we have an ingredient with a `name` of butter, every recipe named butter will use that `ingredient` instead of creating multiple ingredients of the same name. Down the road, we will be able to find all the recipes that have a particular ingredient.

### Create or find ingredients

To our `Recipe` model, let's add a `before_save` hook so we can get at our ingredients before they get saved. We can use `find_or_create_by` when assigning an `Ingredient` to `RecipeIngredient.`

`app/models/recipe.rb`
```ruby
class Recipe < ApplicationRecord
  has_many :recipe_ingredients
  has_many :ingredients, through: :recipe_ingredients

  validates_presence_of :title

  accepts_nested_attributes_for :recipe_ingredients, allow_destroy: true

  before_save :find_or_create_ingredients

  def find_or_create_ingredients
    self.recipe_ingredients.each do |recipe_ingredient|
      recipe_ingredient.ingredient = Ingredient.find_or_create_by(name:recipe_ingredient.ingredient.name)
    end
  end
end
```

Test it out by adding multiple recipe ingredients with the same ingredient name, and then go into the rails console, and we can see that an ingredient with the same name gets created once. I made a recipe called "Butter Two Ways," both `recipe_ingredients` have the name of butter. When I go into the rails console, I see that there is only one `ingredient` named butter and that both of my `recipe_ingredients` belong to it.

```bash
rails c
```

View your last `Recipe` in the rails console after you create it in the application.

```ruby
recipe = Recipe.last
  Recipe Load (0.1ms)  SELECT "recipes".* FROM "recipes" ORDER BY "recipes"."id" DESC LIMIT ?  [["LIMIT", 1]]
=> #<Recipe id: 18, title: "Butter Two Ways", created_at: "2021-01-17 00:47:28.243659000 +0000", updated_at: "2021-01-17 00:47:28.243659000 +0000">

recipe.recipe_ingredients.count
   (0.2ms)  SELECT COUNT(*) FROM "recipe_ingredients" WHERE "recipe_ingredients"."recipe_id" = ?  [["recipe_id", 18]]
=> 2

recipe.recipe_ingredients.pluck(:ingredient_id)
   (0.1ms)  SELECT "recipe_ingredients"."ingredient_id" FROM "recipe_ingredients" WHERE "recipe_ingredients"."recipe_id" = ?  [["recipe_id", 18]]
=> [180, 180]

Ingredient.find(180)
  Ingredient Load (1.0ms)  SELECT "ingredients".* FROM "ingredients" WHERE "ingredients"."id" = ? LIMIT ?  [["id", 180], ["LIMIT", 1]]
=> #<Ingredient id: 180, name: "butter", created_at: "2021-01-16 23:17:33.293763000 +0000", updated_at: "2021-01-16 23:17:33.293763000 +0000">
```

Ta-da 🎉 Wow, we did it y'all! How does that feel?

[View the code up to this point](https://github.com/meaganewaller/pantry/tree/refactor-form)

## Wrap Up
Phew, that was a doozy of a post. I hope it was informative and useful for you. Let's refresh what we did.

1. We learned what a `has_many :through` relationship is. It's a relationship that links two other models together. A recipe has many ingredients _through_ its recipe ingredients. In practice, it creates a third database table that has at least a reference to both tables to which it belongs.
2. We learned when we need to use a `has_many :through` relationship, we use one because we wanted ingredients on our recipes to have additional information that was specific to the recipe only. Reach for a `has_many :through` when you need other data on the association.
3. We defined the Pantry app and its requirements
4. We built a Rails 6 application, using generators for our models, migrations, and controllers. Then we used a seed file to populate our database initially and the rails console to view our data. We also built out a CRUD controller for recipes and a form to input our recipe information into and save it.
5. We created nested forms using `fields_for` and `accepts_nested_attributes_for,` learned how to build out our ingredient so the ingredient fields would render
6. We added what we needed when we needed it, like when we added our flash notices when we needed more information about why our recipe wasn't saving.
7. We used the cocoon gem to make our nested form dynamic, allowing us to add and remove recipe ingredients
8. We pulled form partials out so we could reuse our form on the new and edit page
9. We used an ActiveRecord lifecycle hook (`before_save`) so that recipe_ingredients would either use an existing ingredient if it existed and, if not, create the ingredient.

Don't you feel accomplished?

I have some further challenges for you if you'd like to iterate more on this application. Add auto-completion to the ingredient name field, selecting an option from the auto-select dropdown could populate a hidden `id` field in the ingredient fields. Add specs. Ideally, we would do this before/alongside writing our code, but I wanted to focus on the code, still trying to figure out this balance. Add a way to use the `destroy` action from the `RecipesController.` Make the index page better, add a link to create new recipes. Add a way to search for recipes that include a specific ingredient. Think about how your controller would change if your Rails application were API only. How would the request payload look? What changes would you need? There are so many cool ways to expand on this application. I hope that this provides a good base and jumping-off point if you're so inclined.

I do enjoy writing these tutorials, but they take a lot of effort! If you appreciated this post, please share it on Twitter or other social media; that would make my day. Keep in mind that there are different ways to solve this problem; this happens to be _my_ approach using my favorite tools.  If you have any questions or comments, please [tweet at me](https://twitter.com/meaganewaller), I always appreciate lovely messages and constructive feedback.