0

I'm trying to create an "ingredient" checkbox list derived from my "recipes", I'd like for the values to be saved in the database so that when it's checked and I refresh the page, it still shows as checked.

The error says "uninitialized constant #Class:0x00007f8f2d360830::Parties"

Here's an example of what i am trying to do

Controller:

# parties_controller.rb

def ingredients
  @party = Party.find(params[:party_id])
  @party_recipe = @party.recipes
  @party_recipe.each do |recipe|
  @ingredients = recipe.ingredients
end

The models:

Party model

#party.rb

class Party < ApplicationRecord
  has_many :party_recipes
  has_many :recipes, through: :party_recipes
end

Recipe model

#recipe_ingredient.rb

class RecipeIngredient < ApplicationRecord
 belongs_to :recipe
 belongs_to :ingredient
end

Ingredient model

#ingredient.rb

class Ingredient < ApplicationRecord
 has_many :recipe_ingredients
 has_many :recipes, through: :recipe_ingredients
end

Form:

#ingredients.html.erb

<% form_for "/parties/#{@party.id}/ingredients" do |f| %>
  <% Parties::Recipes::Ingredients.each do |ingredient| %>
    <%= check_box_tag(ingredient) %>
    <%= ingredient %>
  <% end %>
<% end %>

Schema:

create_table "ingredients", force: :cascade do |t|
    t.string "name"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

  create_table "parties", force: :cascade do |t|
    t.string "title"
    t.string "address"
    t.bigint "user_id", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.string "theme"
    t.date "date"
    t.integer "attendancy"
    t.integer "appetizers"
    t.integer "mains"
    t.integer "desserts"
    t.string "status", default: "pending"
    t.index ["user_id"], name: "index_parties_on_user_id"
  end

  create_table "party_recipes", force: :cascade do |t|
    t.bigint "recipe_id", null: false
    t.bigint "party_id", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["party_id"], name: "index_party_recipes_on_party_id"
    t.index ["recipe_id"], name: "index_party_recipes_on_recipe_id"
  end

  create_table "recipe_ingredients", force: :cascade do |t|
    t.bigint "recipe_id", null: false
    t.bigint "ingredient_id", null: false
    t.string "amount"
    t.boolean "included", default: false
    t.index ["ingredient_id"], name: "index_recipe_ingredients_on_ingredient_id"
    t.index ["recipe_id"], name: "index_recipe_ingredients_on_recipe_id"
  end

  create_table "recipes", force: :cascade do |t|
    t.string "title"
    t.text "description"
  end

add_foreign_key "party_recipes", "parties"
add_foreign_key "party_recipes", "recipes"
add_foreign_key "recipe_ingredients", "ingredients"
add_foreign_key "recipe_ingredients", "recipes"

I'm not entirely sure where exactly needs to be corrected, any help appreciated, thank you so much!

2 Answers 2

1

Well the error message is correct, you don't have any model called Parties, in fact in Rails, models are always singular, camel-case. So that explains the error message.

However that won't fix your problem! The iterator in the view should be

  <% @ingredients.each do |ingredient| %>
    <%= check_box_tag(ingredient) %>
    <%= ingredient %>
  <% end %>

Because I think you are trying to populate an @ingredients variable in your controller. However it still won't work, b/c the value of the @ingredients variable is not being correctly assigned...

Personally I much prefer the "fat model skinny controller" design style for Rails. So I would have a PartiesController#ingredients method that looks like this:

# parties_controller.rb

def ingredients
  @party = Party.find(params[:party_id])
  @ingredients = @party.ingredients
end

then in your Party model:

# app/models/party.rb

  def ingredients
    recipes.map(&:ingredients).flatten
  end

Why do it this way? Well you're just getting started with Rails, but eventually (soon hopefully) you'll be writing tests, and it's much much easier to write tests on models than controllers.

Now, there could well be some other issues in your code, but try my suggestions and see where that gets you.

Sign up to request clarification or add additional context in comments.

3 Comments

Could even replace map + flatten with flat_map :) Also consider if eager loading may be appropriate, e.g. recipes.includes(:ingredients).flat_map((&:ingredients). It could mean fewer queries and better performance.
oh @HenrikN I didn't know about flat_map. Where does it come from? I don't see it in Ruby 3.0.1 or ActiveSupport::Array
It’s in Enumerable :)
0

@Les Nightingill's answer should work well for organizing your controller and model! Regarding when you click refresh and the value of the boxes are saved either;

  1. Set up some listeners in javascript and send a request to your update controller method every time there is a value change for one of your check boxes.

  2. Or add a save button at the bottom of your form that points to your update controller method to save the values of the checkboxes. Something like:

<%= submit_tag "Save", data: { disable_with: "Saving..." } %>

https://api.rubyonrails.org/v5.2.3/classes/ActionView/Helpers/FormTagHelper.html#method-i-submit_tag

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.