Rails why is my parent object not saving? - ruby-on-rails

I'm quite new to rails/programming/web development, and this is the error that I'm getting:
ActiveModel::MissingAttributeError (can't write unknown attribute 'shopping_list_id')
I'm trying to save a shopping list object which is populated with Item objects after selecting specific Meal objects (with collection_check_box) and the associated Ingredient objects.
Looking through the localhost server logs, all the items are created correctly using the correct ingredients retrieved (and assigned to the current shopping list id) from the correct meal_ids - but when it comes to saving the actual shopping list I get the above error. Please see below for image of server log.
I've been stuck on this for a few days now and I've seen many posts with the same error but all seem to have quite different contexts to this issue.
Is it something to do with strong_parameters? Or are my associations wrong?
In my meals index.html.erb:
<%= form_for :shopping_list, url: shopping_lists_path do |f| %>
<%= collection_check_boxes :shopping_list, :meal_ids, Meal.all, :id, :name do |b|%>
<%= b.label class:"label-checkbox" do%>
<%=b.check_box + b.text%>
<%end%>
<% end %>
<%= f.submit "Create Shopping List" %>
<% end %>
My shopping_lists_controller:
def create
#shopping_list = ShoppingList.new(shopping_list_params)
#meals = Meal.where(id:[#shopping_list.meal_ids])
#meals.map do |meal|
meal.ingredients.map do |ingredient|
#shopping_list.items.build(name: ingredient.name, quantity: ingredient.quantity, unit: ingredient.unit, shopping_list_id: #shopping_list.id)
end
end
respond_to do |format|
if #shopping_list.save
format.html { redirect_to #shopping_list, notice: 'Shopping List was successfully created.' }
format.json { render :show, status: :created, location: #shopping_list }
else
format.html { render :new }
format.json { render json: #shopping_list.errors, status: :unprocessable_entity }
end
end
end
private
def set_shopping_list
#shopping_list = ShoppingList.find(params[:id])
end
def shopping_list_params
params.require(:shopping_list).permit(:name, {meal_ids: []})
end
My ShoppingList Model:
class ShoppingList < ApplicationRecord
has_many :items
has_many :meals
accepts_nested_attributes_for :meals
accepts_nested_attributes_for :items
end
My Item Model:
class Item < ApplicationRecord
belongs_to :shopping_list
end
My schema.rb:
create_table "ingredients", force: :cascade do |t|
t.integer "meal_id"
t.string "name"
t.float "quantity"
t.integer "unit"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["meal_id"], name: "index_ingredients_on_meal_id"
end
create_table "items", force: :cascade do |t|
t.integer "shopping_list_id"
t.string "name"
t.float "quantity"
t.integer "unit"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["shopping_list_id"], name: "index_items_on_shopping_list_id"
end
create_table "meals", force: :cascade do |t|
t.string "name"
t.string "description"
t.string "method"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "meal_image_file_name"
t.string "meal_image_content_type"
t.integer "meal_image_file_size"
t.datetime "meal_image_updated_at"
t.integer "diet"
end
create_table "shopping_lists", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Image of server log

The issue is that Meal model has belongs_to :shopping_list but it doesn't have column shopping_list_id.
So when you run
ShoppingList.create(meal_ids: ['1'])
Rails tries to create Meal model and link it back to the ShoppingList, but it can't since there is no such attribute and error tells you that ActiveModel::MissingAttributeError. To fix create a migration e.g.:
add_column :meals, :shopping_list_id, :integer

In the create method of shopping_lists_controller, you are using a new instance of shopping_list and using the id of that instance to create items. Since the new instance is not saved to the db, it will not have id. So you cannot use #shopping_list.id while creating items.
So either change
#shopping_list = ShoppingList.new(shopping_list_params)
to
#shopping_list = ShoppingList.create(shopping_list_params)
or do
#shopping_list = ShoppingList.new(shopping_list_params)
#shopping_list.save
You may also want to destroy this object if any exception is raised.

Related

Rails many-to-many through relationship returning empty array

I'm a beginner in rails and am making a card app. I have a user, card, and user_card models with a many to many, through relationship set up between the cards and users. My problem is that when I return the card table and try to include: the users I get an empty array. I've tried resetting the database but still nothing.
ActiveRecord::Schema.define(version: 2022_06_15_200100) do
create_table "cards", force: :cascade do |t|
t.string "name"
t.string "image"
t.string "text"
t.integer "level"
t.string "types"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.integer "quantity", default: 0
end
create_table "user_cards", force: :cascade do |t|
t.integer "user_id"
t.integer "card_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["card_id"], name: "index_user_cards_on_card_id"
t.index ["user_id"], name: "index_user_cards_on_user_id"
end
create_table "users", force: :cascade do |t|
t.string "username"
t.string "password_digest"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
class User < ApplicationRecord
has_secure_password
has_many :user_cards
has_many :cards, through: :user_cards
end
class Card < ApplicationRecord
has_many :user_cards
has_many :users, through: :user_cards
end
class UserCard < ApplicationRecord
belongs_to :user
belongs_to :card
end
controller
class CardsController < ApplicationController
wrap_parameters false
def create
card = Card.create(card_params)
if card.valid?
render json: card, status: :created, include: :users
else
render json:{errors: card.errors}
end
def index
card = Card.all
render json: card, include: :users
end
In order for the User.first.cards to work, you need to ensure the application is inserting data in the user_cards table.
You may check if there are any records in there by doing UserCard.all in your rails console.
Coming to the controller, after creating a card record, you have to assign it to a user record in order for the relationship to be established.
def create
card = Card.create(card_params)
if card.valid?
card.users << current_user # Or card.users << (any user object like) User.first
render json: card, status: :created, include: :users
else
render json:{errors: card.errors}
end
end
card.users << user object will create the necessary record in the user_cards table and you'll be able to access them using includes: :user
You may refer the examples given - here in the Rails API guide

Multiple Checkbox in Ruby

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!
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.
#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;
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.
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

Sorting a recipe list by amount of ingredients on view page in Rails?

My schema is as follows:
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 "recipe_ingredients", force: :cascade do |t|
t.integer "recipe_id"
t.integer "ingredient_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "recipes", force: :cascade do |t|
t.string "name"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
On my index page, I would like to list all the recipes and then either have link or button that sorts the recipes by the amount of ingredients each recipe has. I've been toying with this for a couple hours now and getting closer but being a newb, my brain isn't quite making the leap. Right now I have some code in the RecipesController in #index that takes in some params from the view file:
<%= button_to "sort by ingredient amount", {:controller => "index", :action => "update", :order => true}, :method=>:post %>
the code in the controller so far is something like this:
def index
if params[:order] = true
Recipe.all.each do |r|
r.ingredients.each do |i|
???
end
end
else
#recipes = Recipe.all
end
end
I'm not sure how to use .order when accessing an association such as the recipes_ingredients. This could also be the totally bassackwards way to do it.
Yes you can find the data based on ingredients like these
def index
if params[:order] = true
Recipe.left_joins(:ingredients).group(:id).order('COUNT(ingredients.id) DESC')
else
#recipes = Recipe.all
end
end

Joining two tables in rails

I am trying to link the location and the operating tables so that I can display some data in location table in the operatings views. However, I am stuck and don't know what to do. Any help would be appreciated.
#below are models#
class Location < ApplicationRecord
has_many :operatings
end
class Operating < ApplicationRecord
belongs_to :location
end
##below are my tables##
enable_extension "plpgsql"
create_table "locations", force: :cascade do |t|
t.string "country"
t.string "supra_region"
t.string "region"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "operatings", force: :cascade do |t|
t.string "operating_company_name"
t.string "address"
t.date "year_formed"
t.string "other_operational_countries"
t.string "about_company"
t.string "current_focus"
t.string "incumbent_irm_contractor"
t.string "irm_frame_agreements"
t.text "estimated_irm_budgets"
t.integer "location_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["location_id"], name: "index_operatings_on_location_id", using: :btree
end
add_foreign_key "operatings", "locations"
###below is my operating controller###
def create
#operating = Operating.new(op_company)
if #operating.save
flash[:success] = "A recorded has been successfully Saved"
redirect_to operatings_path
else
render 'new'
end
end
####routes####
resources :offshores, :index, :show, :new, :create, :destroy
resources :locations, :index, :show, :new, :create, :destroy
Since your Location and Operating models are linked together using has_many and belongs_to, if you have an operating object in your template, you can easily access the attributes of its location:
<% #operatings.each do |operating| %>
<div>The name of its location: <%= operating.location.name %></div>
<% end %>
You need to be careful with this though. If you only fetch the operatings from the database, accessing each operating's location attribute in that each loop will trigger a separate database query for every operating item. This is called an N+1 query, and it is very inefficient. To fix the problem, make sure to pre-fetch the associated location as well when loading operatings using includes:
# in the controller
#operatings = Operating.all.includes(:location)
This way the associated locations of every operating will be fetched using just a single query.

Associating models in ruby on rails

I'm new to rails so I'm still trying to figure things out so any help is greatly appreciated! I am building an app that will define "seasons" and there will be multiple "danceclasses" associated with those seasons. After you create a season, you should have the option to create "danceclasses" so as part of my show on the seasons I have:
<h2>Dance Classes Created</h2>
<%= #seasons.danceclass.each do |danceclass| %>
<p>
However I get the following error:
undefined method `danceclass' for nil:NilClass
My data model is I have a seasons table, a danceclasses table and a season_danceclasses table.
My model for seasons is this:
class Season < ActiveRecord::Base
has_many :season_class
has_many :danceclass, through: :season_class
end
My model for dance classes looks like this:
class Danceclass < ActiveRecord::Base
belongs_to :season
has_many :student_class
has_many :student, through: :student_class
end
And my model for season_danceclass looks like this:
class SeasonDanceclasses < ActiveRecord::Base
belongs_to :season
belongs_to :danceclass
end
My season_controller looks like this:
class SeasonsController < ApplicationController
before_action :set_season, only: [:show, :edit, :update, :destroy]
# GET /seasons
# GET /seasons.json
def index
#seasons = Season.all
end
# GET /seasons/1
# GET /seasons/1.json
def show
end
# GET /seasons/new
def new
#season = Season.new
end
# GET /seasons/1/edit
def edit
end
# POST /seasons
# POST /seasons.json
def create
#season = Season.new(season_params)
respond_to do |format|
if #season.save
format.html { redirect_to #season, notice: 'Season was successfully created.' }
format.json { render :show, status: :created, location: #season }
else
format.html { render :new }
format.json { render json: #season.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /seasons/1
# PATCH/PUT /seasons/1.json
def update
respond_to do |format|
if #season.update(season_params)
format.html { redirect_to #season, notice: 'Season was successfully updated.' }
format.json { render :show, status: :ok, location: #season }
else
format.html { render :edit }
format.json { render json: #season.errors, status: :unprocessable_entity }
end
end
end
# DELETE /seasons/1
# DELETE /seasons/1.json
def destroy
#season.destroy
respond_to do |format|
format.html { redirect_to seasons_url, notice: 'Season was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_season
#season = Season.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def season_params
params.require(:season).permit(:season_name, :season_start, :season_end)
end
end
What am I doing wrong?
EDIT: Adding Schema.rb
# encoding: UTF-8
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# Note that this schema.rb definition is the authoritative source for your
# database schema. If you need to create the application database on another
# system, you should be using db:schema:load, not running all the migrations
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160729111417) do
create_table "danceclasses", force: :cascade do |t|
t.string "class_id", limit: 255
t.string "class_name", limit: 255
t.text "class_description", limit: 65535
t.integer "min_students", limit: 4
t.integer "max_students", limit: 4
t.string "category", limit: 255
t.datetime "start_date"
t.datetime "end_date"
t.integer "week_frequency", limit: 4
t.integer "day_frequency", limit: 4
t.string "start_time", limit: 255
t.string "end_time", limit: 255
t.integer "fee", limit: 4
t.string "level", limit: 255
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "season_classes", force: :cascade do |t|
t.integer "season_id", limit: 4
t.integer "danceclass_id", limit: 4
end
create_table "seasons", force: :cascade do |t|
t.string "season_name", limit: 255
t.datetime "season_start"
t.datetime "season_end"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "student_classes", force: :cascade do |t|
t.integer "student_id", limit: 4
t.integer "class_id", limit: 4
end
create_table "students", force: :cascade do |t|
t.string "first_name", limit: 255
t.string "last_name", limit: 255
t.string "student_id", limit: 255
t.datetime "date_of_birth"
t.text "notes", limit: 65535
t.string "gender", limit: 255
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "user_students", force: :cascade do |t|
t.integer "user_id", limit: 4
t.integer "student_id", limit: 4
end
create_table "users", force: :cascade do |t|
t.string "username", limit: 255
t.string "email", limit: 255
t.string "password", limit: 255
t.string "first_name", limit: 255
t.string "last_name", limit: 255
t.string "phone_number", limit: 255
t.datetime "date_of_birth"
t.string "street_1", limit: 255
t.string "street_2", limit: 255
t.string "city", limit: 255
t.string "state", limit: 255
t.string "zipcode", limit: 255
t.boolean "enabled"
t.boolean "is_admin"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
You need to change your has_many association properly.
class Season < ActiveRecord::Base
has_many :season_classes
has_many :danceclasses, through: :season_class
end
Hope this help you!
First of all the associations you have taken should be plural for has_many,
class Season < ActiveRecord::Base
has_many :season_classes
has_many :dance_classes, through: :season_classes
end
class Danceclass < ActiveRecord::Base
belongs_to :season
has_many :student_classes
has_many :students, through: :student_classes
end
class SeasonDanceclasses < ActiveRecord::Base
belongs_to :season
belongs_to :danceclass
end
Now, as you said that view is show page for season your season show action is,
class SeasonsController < ApplicationController
before_action :set_season, only: [:show, :edit, :update, :destroy]
def index
#seasons = Season.all
end
def show
end
end
Your show method contains #season variable but not #seasons, which was set in set_season action,
so app/views/seasons/show.html.erb is,
This is with your old association
<h2>Dance Classes Created</h2>
<% #season.danceclass.each do |danceclass| %>
-----------
----------
<% end %>
<p></p>
This is with changed associations
<h2>Dance Classes Created</h2>
<% #season.dance_classes.each do |danceclass| %>
-----------
----------
<% end %>
<p></p>

Resources