I am trying to save a product which belongs_to category with foreign_key :category_id. The failed product.save is show below:
Processing by ProductsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"2V3zVrgU3SoGAwyHDLYQuyDFWq9rI7U4GdDeZNDRwrLTwsGloio4MIXUOdU/ckvnTsMGF9B9TL4tuNWKSqZpVg==", "product"=>{"name"=>"trek"}, "commit"=>"Save Product", "category_id"=>"1"}
Everytime I try to save the product it says "category must exist". I understand I have my models and migrations a little off, but can't figure out how. Below are the files.
product.rb:
class Product < ApplicationRecord
belongs_to :category
end
category.rb: empty
create_categories.rb:
class CreateCategories < ActiveRecord::Migration[5.2]
def change
create_table :categories do |t|
t.string :name
t.timestamps
end
end
end
create_products.rb:
class CreateProducts < ActiveRecord::Migration[5.2]
def change
create_table :products do |t|
t.string :name
t.belongs_to :category, index: {unique: true}, foreign_key: true, null: $
t.timestamps
end
end
end
I can also post my product_controller.rb however it is pretty standard with just #product.save and a redirect_to products_path or 'products#index'.
Thank you.
EDIT: products_controller.rb:
def create
#product = Product.new(product_params)
respond_to do |format|
if #product.save
format.html { redirect_to category_products_path([#category, #product])$
format.json { render :show, status: :created, location: #product }
else
format.html { render :new }
format.json { render json: #product.errors, status: :unprocessable_enti$
end
end
end
def product_params
params.require(:product).permit( :name, :category_id)
end
It seems like you want to do something like:
class ProductsController < ApplicationController
def create
#category = Category.find(params[:category_id])
#product = #category.products.new(product_params)
if #product.save
format.html { redirect_to category_products_path([#category, #product]) }
format.json { render :show, status: :created, location: #product }
else
format.html { render :new }
format.json { render json: #product.errors, status: :unprocessable_entity }
end
end
private
def product_params
params.require(:product).permit(:name, :category_id)
end
end
This assumes, naturally, that you have something like:
class Category < ApplicationRecord
has_many :products
end
class Product < ApplicationRecord
belongs_to :category
end
As you currently have it written, there are at least a couple of problems.
First, you say:
def product_params
params.require(:product).permit( :name, :category_id)
end
But, params[:product] doesn't have category_id.
Second, you say:
format.html { redirect_to category_products_path([#category, #product])}
But, you don't define #category.
You should find the category from the category id, then under that category create product.
class ProductsController < ApplicationController
def create
#category = Category.find(params[:category_id])
#product = #category.products.new(product_params)
if #product.save
format.html { redirect_to category_products_path([#category, #product]) }
format.json { render :show, status: :created, location: #product }
else
format.html { render :new }
format.json { render json: #product.errors, status: :unprocessable_entity}
end
end
private
def product_params
# as your category_id is outside of product hash, doesn't need to permit in product hash though
params.require(:product).permit(:name)
end
end
In your product/_form.html.erb create the form_for with nested resources, then you'll get the category_id under params[:product] hash.
Related
How I can show at the same time on view data from two tables: first has type has_many and second has type belongs_to
I don`t understand how to display data from main page list and all related strings from table list_items. Help me, please.
Controllers of list and list_item
class TodoListsController < ApplicationController
before_action :set_todo_list, only: [:show, :edit, :update, :destroy]
def index
#todo_lists = TodoList.all
end
def show
end
def new
#todo_list = TodoList.new
end
def edit
end
def create
#todo_list = TodoList.new(todo_list_params)
respond_to do |format|
if #todo_list.save
format.html { redirect_to #todo_list, notice: 'Todo list was successfully created.' }
format.json { render :show, status: :created, location: #todo_list }
else
format.html { render :new }
format.json { render json: #todo_list.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if #todo_list.update(todo_list_params)
format.html { redirect_to #todo_list, notice: 'Todo list was successfully updated.' }
format.json { render :show, status: :ok, location: #todo_list }
else
format.html { render :edit }
format.json { render json: #todo_list.errors, status: :unprocessable_entity }
end
end
end
def destroy
#todo_list.destroy
respond_to do |format|
format.html { redirect_to root_url, notice: 'Todo list was successfully destroyed.' }
format.json { head :no_content }
end
end
private
def set_todo_list
#todo_list = TodoList.find(params[:id])
end
def todo_list_params
params.require(:todo_list).permit(:title, :description)
end
end
class TodoItemsController < ApplicationController
before_action :set_todo_list
before_action :set_todo_item, except:[:create]
def create
#todo_item = #todo_list.todo_items.create(params[:todo_item].permit(:content))
redirect_to #todo_list
end
def destroy
if #todo_item.destroy
flash[:success] = "Todo List item was deleted."
else
flash[:error] = "Todo List item could not be deleted."
end
redirect_to #todo_list
end
def complete
#todo_item.update_attribute(:completed_at, Time.now)
redirect_to #todo_list, notice: "Todo item completed"
end
private
def set_todo_list
#todo_list = TodoList.find(params[:todo_list_id])
end
def set_todo_item
#todo_item = #todo_list.todo_items.find(params[:id])
end
def todo_item_params
params[:todo_item].permit(:content)
end
end
I have such models
class TodoList < ApplicationRecord
has_many :todo_items, dependent: :delete_all
end
class TodoItem < ApplicationRecord
belongs_to :todo_list
def completed?
!completed_at.blank?
end
end
and have such migrations
class CreateTodoLists < ActiveRecord::Migration[6.0]
def change
create_table :todo_lists do |t|
t.string :title
t.text :description
t.timestamps
end
end
end
class CreateTodoItems < ActiveRecord::Migration[6.0]
def change
create_table :todo_items do |t|
t.string :content
t.references :todo_list, foreign_key: true
# t.references :todo_list, null: false, foreign_key: true
t.timestamps
end
end
end
class TodoLists < ApplicationController
...
# GET /todo_lists
def index
#todo_lists = TodoList.include(:todo_items).all
end
end
Using .includes prevents a N+1 query as all items for all the lists will be loaded in one query.
You then just iterate through the lists and loop through the items for each list.
Lets start by creating a partial to render a todo_list:
# app/views/todo_lists/_todo_list.html.erb
<article>
<h1><%= todo_list.title %></h1>
<p><%= todo_list.description %></p>
<% if todo_list.todo_items.any? %>
<ul>
<% todo_list.todo_items.each do |item| %>
<li><%= item.content %></li>
<% end %>
</ul>
<% else %>
<p>This list contains no items.</p>
<% end %>
</article>
Then lets just render our partial with the collection from the index view:
# app/views/todo_lists/index.html.erb
<%= render(#todo_lists) || content_tag("p", "There are no todo lists.") %>
I want the all products should be added depending on the brand,
I have a brand and product scaffolding,
I generate brand like this: rails generate scaffold Brand brand:string
and generate product has_many assosiation with brand scaffolding:
rails generate scaffold Product productname:string brand:references
my brand model
class Brand < ApplicationRecord
has_many :products
end
product model
class Product < ApplicationRecord
belongs_to :brand
end
brand migration
class CreateBrands < ActiveRecord::Migration[5.2]
def change
create_table :brands do |t|
t.string :brandname
t.timestamps
end
end
end
product migration
class CreateProducts < ActiveRecord::Migration[5.2]
def change
create_table :products do |t|
t.string :productname
t.references :brand
t.timestamps
end
end
end
Brand controller
class CreateProducts < ActiveRecord::Migration[5.2]
def change
create_table :products do |t|
t.string :productname
t.references :brand
t.timestamps
end
end
end
Product controller
class ProductsController < ApplicationController
before_action :set_product, only: [:show, :edit, :update, :destroy]
def index
#products = Product.all
end
def new
#product = Product.new
end
def create
#product = Product.new(product_params)
respond_to do |format|
if #product.save
format.html { redirect_to #product, notice: 'Product was successfully created.' }
format.json { render :show, status: :created, location: #product }
else
format.html { render :new }
format.json { render json: #product.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if #product.update(product_params)
format.html { redirect_to #product, notice: 'Product was successfully updated.' }
format.json { render :show, status: :ok, location: #product }
else
format.html { render :edit }
format.json { render json: #product.errors, status: :unprocessable_entity }
end
end
end
def destroy
#product.destroy
respond_to do |format|
format.html { redirect_to products_url, notice: 'Product was successfully destroyed.' }
format.json { head :no_content }
end
end
private.
def set_product
#product = Product.find(params[:id])
end
def product_params
params.require(:product).permit(:productname, :brand_id)
end
end
I succesfully crud operation with brand but I tried new product depending on brand I get an error:
1 error prohibited this product from being saved:
Brand must exist
How can I fix this, thanks for suggestions
before create product you should have brand record,
when create product, you should include which brand that will be connected to that product
and that information saved through brand_id (see: t.references :brand)
in your ProductsController.rb add #brands
class ProductsController < ApplicationController
before_action :set_product, only: [:show, :edit, :update, :destroy]
def new
#product = Product.new
#brand_list = Brand.all.map { |c| [ "#{c.brandname} ", c.id] }
end
end
in your view file that create product add field to choose brand below is sample
<%= f.select :brand_id, #brand_list, { include_blank: false } %>
I am being super new to Rails, so, please keep it in mind reading my post.
I am trying to develop a very simple app where I can create a game and assign players to this game.
So far I have a scaffolded game and player resources. I have set has_many :players and has_many: games relationship, for both, - game and player models.
I added form.select in view form (new):
<%= form.label :player_id %>
<%= form.select :player_id, Player.all.collect{|x| [x.name]} %>
What I am trying to achieve is, - I want to be able to add multiple players to the single game. If I paste the same form.select field again it will overwrite first selection and eventually save only one (last) player.
I can, however, replicate this functonality in rails console. with:
g = Game.last
g.players << Player.find(1)
g.players << Player.find(2)
And it works.
Can you advise me on how to do same at FronEnd level? I understand if my approach is completely wrong, then let me know what will be a better one.
Many thanks in advance.
Update 1.
I have very default scaffold controller:
class GamesController < ApplicationController
before_action :set_game, only: [:show, :edit, :update, :destroy]
def index
#games = Game.all
end
def show
end
def new
#game = Game.new
end
def edit
end
def create
#game = Game.new(game_params)
respond_to do |format|
if #game.save
format.html { redirect_to #game, notice: 'Game was successfully created.' }
format.json { render :show, status: :created, location: #game }
else
format.html { render :new }
format.json { render json: #game.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if #game.update(game_params)
format.html { redirect_to #game, notice: 'Game was successfully updated.' }
format.json { render :show, status: :ok, location: #game }
else
format.html { render :edit }
format.json { render json: #game.errors, status: :unprocessable_entity }
end
end
end
def destroy
#game.destroy
respond_to do |format|
format.html { redirect_to games_url, notice: 'Game was successfully destroyed.' }
format.json { head :no_content }
end
end
private
def set_game
#game = Game.find(params[:id])
end
def game_params
params.require(:game).permit(:name, :date, :player_id)
end
end
and my db tables look like this:
class CreatePlayers < ActiveRecord::Migration[5.1]
def change
create_table :players do |t|
t.string :name
t.string :role
t.integer :game_id
t.timestamps
end
end
end
class CreateGames < ActiveRecord::Migration[5.1]
def change
create_table :games do |t|
t.string :name
t.date :date
t.integer :player_id
t.timestamps
end
end
end
Do i need to have action in game controller for it ?
I have some problem with my code:
Models:
class UsefulPhrase < ActiveRecord::Base
has_many :useful_phrase_contents
accepts_nested_attributes_for :useful_phrase_contents
validates_presence_of :key
end
class UsefulPhraseContent < ActiveRecord::Base
belongs_to :useful_phrase
attr_accessor :useful_phrase_id
validates_presence_of :language, :content
end
Controller:
def new
#useful_phrase = UsefulPhrase.new
#available_languages = available_languages
#useful_phrase.useful_phrase_contents.build
end
def create
#useful_phrase = UsefulPhrase.new(useful_phrase_params)
#useful_phrase.useful_phrase_contents.build(upc_params)
respond_to do |format|
if #useful_phrase.save
format.html { redirect_to #useful_phrase, notice: 'bla-bla' }
format.json { render :show, status: :created, location: #useful_phrase }
else
format.html { render :new }
format.json { render json: #useful_phrase.errors, status: :unprocessable_entity }
end
end
end
def useful_phrase_params
params.require(:useful_phrase).permit(:key)
end
def upc_params
params.require(:useful_phrase).require(:useful_phrase_content).permit(:language, :content)
end
When i'm trying save any record I get:
ActiveModel::MissingAttributeError at /useful_phrases
can't write unknown attribute useful_phrase_id
I don't know how to repair it.
try edit your parameter in your upc_params
params.require(:useful_phrase)
.permit(:language, content, :useful_phrase_content => [puttheattributefor use_ful_phrase_content])
In rails console & using the models below, I connected grades K, 1, and 2 to the school whose Edit form has this select field:
As you can see, that association correctly selects the 3 items in the field, but if I click to select/deselect grades, those changes aren't getting saved.
Here are the models:
# app/models/school.rb
class School < ActiveRecord::Base
has_many :grades_schools, inverse_of: :school
has_many :grades, through: :grades_schools
accepts_nested_attributes_for :grades_schools, allow_destroy: true
end
# app/models/grades_school.rb
class GradesSchool < ActiveRecord::Base
belongs_to :school
belongs_to :grade
end
# app/models/grade.rb
class Grade < ActiveRecord::Base
has_many :grades_schools, inverse_of: :grade
has_many :schools, through: :grades_schools
end
The form looks like this:
# app/views/schools/_form.html.haml
= form_for(#school) do |f|
/ <snip> other fields
= collection_select(:school, :grade_ids, #all_grades, :id, :name, {:selected => #school.grade_ids, include_hidden: false}, {:multiple => true})
/ <snip> other fields + submit button
And the controller looks like this:
# app/controllers/schools_controller.rb
class SchoolsController < ApplicationController
before_action :set_school, only: [:show, :edit, :update]
def index
#schools = School.all
end
def show
end
def new
#school = School.new
#all_grades = Grade.all
#grades_schools = #school.grades_schools.build
end
def edit
#all_grades = Grade.all
#grades_schools = #school.grades_schools.build
end
def create
#school = School.new(school_params)
respond_to do |format|
if #school.save
format.html { redirect_to #school, notice: 'School was successfully created.' }
format.json { render :show, status: :created, location: #school }
else
format.html { render :new }
format.json { render json: #school.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if #school.update(school_params)
format.html { redirect_to #school, notice: 'School was successfully updated.' }
format.json { render :show, status: :ok, location: #school }
else
format.html { render :edit }
format.json { render json: #school.errors, status: :unprocessable_entity }
end
end
end
private
def set_school
#school = School.find(params[:id])
end
def school_params
params.require(:school).permit(:name, :date, :school_id, grades_attributes: [:id])
end
end
I have a feeling that the crux of my problem has to do with a mismatch between the params generated by collection_select and the strong parameters. One or both of these is probably incorrect, but I can't for the life of me find example code online that shows me what I'm doing wrong.
After trying a load of failed variations, I'm at my wits end! Thanks in advance for your help!
Crap. I could have sworn I tried this before, but it must have been when using fields_for in the form instead of collection_select. The solution:
def school_params
params.require(:school).permit(:name, :date, :school_id, grades_attributes: [:id])
end
becomes
def school_params
params.require(:school).permit(:name, :date, :school_id, grade_ids: [])
end
I'm still curious how it would work when using fields_for #grades_schools, but will have to save that investigation for another day....