I am totally new to programming and I am having trouble. About 10 days ago I started the UT-Rails course on ureddit.com hosted by Richard Schneeman. So far it has been going very well, but I am on week 5 and having trouble. You'll have to excuse me if I don't use the right terminology, as it has been a lot to take in.
https://github.com/zkay/move_logic_to_controllers is the tutorial I am following at the moment.
I am up to step 2. I have replaced the text in app/views/products/new.html.erb with the following:
<%= form_for(#product) do |f| %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :price %><br />
<%= f.text_field :price %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
However, when I try to add a new product per the tutorial, the rejection I get back is:
NoMethodError in Products#create
Showing C:/Sites/move_logic_to_controllers/app/views/products/create.html.erb where line #3 raised:
undefined method `name' for nil:NilClass
Extracted source (around line #3):
1: <h2>Product Created Successfully<h2>
2:
3: <%= #product.name %> added to the website, it costs: $<%= #product.price %>
Rails.root: C:/Sites/move_logic_to_controllers
If I remove the .name and .price calls the page works, but it doesn't display any of the data I submitted.
In app/controllers/product_controller.rb I have the following:
class ProductsController < ApplicationController
def index
#products = Product.includes(:user).all
end
def new
#product = Product.new
end
respond_to do |format|
if #product.save
format.html { render :action => "create" }
format.json { render :json => #product }
else
format.html { render :action => "new" }
format.json { render :json => #product.errors, :status => :unprocessable_entity }
end
end
end
Sorry if this is long winded. I appreciate any help.
it should be <%= #products.name %>
/app/views/products/create.html.erb
You don't want to use create.html.erb.
class ProductsController < ApplicationController
def index
#products = Product.includes(:user).all
end
def new
#product = Product.new
end
def create
#product = Product.new(params[:product])
if #product.save
redirect_to products_path, notice: "You added product"
else
flash[:error] = "Something wrong!"
render :new
end
end
end
If you use Rails 4 use:
def create
#product = Product.new(product_params)
if #product.save
redirect_to products_path, notice: "You added product"
else
flash[:error] = "Something wrong!"
render :new
end
end
private
def product_params
params.require(:product).permit(:name, :price)
end
Related
i created an edit page to edit the room(model) and update the form to change the current name and current capacity to whatever we wish but i am getting an error
ActionController::ParameterMissing in RoomsController#edit
param is missing or the value is empty: room
rooms_controller.rb
class RoomsController < ApplicationController
before_action :set_room, only: %i[show edit update]
def index
#rooms = Room.all
end
def show
end
def new
#room = Room.new
end
def create
#room = Room.new(room_params)
respond_to do |format|
if #room.save
format.html { redirect_to room_url(#room), notice: "Room was created Successfully" }
else
format.html { render :new, status: :unprocessable_entity }
end
end
end
def edit
respond_to do |format|
if #room.update(room_params)
format.html { redirect_to room_url(#room), notice: "Room was successfully updated!" }
else
format.html { render :edit, status: :unprocessable_entity }
end
end
end
private
def set_room
#room = Room.find(params[:id])
end
def room_params
params.require(:room).permit(:name, :capacity)
end
end
edit.hml.erb
<h2>Edit Room</h2>
<%= render "form", room: #room %>
_form.html.erb
<%= form_with(model: room) do |form| %>
<% if room.errors.any? %>
<div style="color: red">
<h2><%= pluralize(room.errors.count, "errors") %> Prohibited this Room from saving</h2>
<ul>
<% room.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<div>
<%= form.label :name, style: "display: block" %>
<%= form.text_field :name %>
</div>
<div>
<%= form.label :capacity, style: "display: block" %>
<%= form.number_field :capacity %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>
i am using the same partial _form.html.erb for both new.html.erb and edit.html.erb , is it because of using same partial form for edit and new or there is some other reason?
new.html.erb
<h1>New Room</h1>
<%= render "form", room: #room %>
You're using the wrong action.
In Rails flavored REST the edit action responds to a GET /rooms/:id/edit request and just renders the form. It should also be idempotent. There is no room parameter since you're not responding to a form submission.
Updating the resource is done in the update method (PATCH /rooms/:id).
class RoomsController < ApplicationController
# ...
# you can actually completely omit this method
# Rails will implicitly render edit.html.erb anyways
# GET /rooms/1/edit
def edit
end
# PATCH /rooms/1
def update
# you don't need to use MimeResponds if you're only responding to HTML requests. KISS
if #room.update(room_params)
redirect_to #room, notice: "Room was successfully updated!"
else
render :edit, status: :unprocessable_entity
end
end
# ...
end
I have just recently tried to implement 'Stripe' to take payments on my website. However, I am have a certain issue trying to update the price on the products.
This is my payments controller.
class PaymentsController < ApplicationController
def create
token = params[:stripeToken]
#product = Product.find(params[:product_id])
#user = current_user
# Create the charge on Stripe's servers to charge the user's card
begin
charge = Stripe::Charge.create(
amount: (#product.price*100).to_i,
currency: "eur",
source: token,
description: params[:stripeEmail]
)
if charge.paid
Order.create!(product_id: #product.id, user_id: #user.id, total: #product.price.to_i)
UserMailer.order_placed(#user,#product).deliver_now
end
rescue Stripe::CardError => e
body = e.json_body
err = body[:error]
flash[:error] = "Unfortunately, there was an error processing your payment: #{err[:message]}"
end
redirect_to product_path(#product), notice: 'Thank you for your order!'
end
end
My products model
class Product < ApplicationRecord
has_many :orders
has_many :comments
validates :name, presence: true
validates :price, presence: true
def price_show
"€ %.2f" % (self[:price]/100.0)
end
def price
self[:price]
end
def self.search(search_term)
Product.where("name LIKE ?", "%#{search_term}%")
end
# Called by <%= #product.highest_rating_comment %>
def highest_rating_comment
comments.rating_desc.first
end
def average_rating
comments.average(:rating).to_f
end
end
and my partial form.
<%= form_with(model: #product, local: true) do |form| %>
<% if #product.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(product.errors.count, "error") %> prohibited this product from being saved:</h2>
<ul>
<% product.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= form.label :name %>
<%= form.text_field :name %>
</div>
<div class="field">
<%= form.label :description %>
<%= form.text_area :description %>
</div>
<div class="field">
<%= form.label :image_url %>
<%= form.text_field :image_url %>
</div>
<div class="field">
<%= form.label :color %><br>
<%= form.text_field :color %>
</div>
<div class="field">
<%= form.label :price %>
<%= form.text_field :price %>
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
The problem I am having is that when I enter in the price, I will be getting the 'undefined error" on both of these lines, to which it will say "did you mean #product?".
div id="error_explanation">
<h2><%= pluralize(product.errors.count, "error") %> prohibited this product from being saved:</h2>
<ul>
<% product.errors.full_messages.each do |message| %>
I change both the lines accordingly, the page loads. However, when I try to enter the price on the page, I will get an error message, stating that the price 'cannot be blank', even though I am entering a numerical value in the field.
My products controller code is as follows.
class ProductsController < ApplicationController
before_action :set_product, only: [:show, :edit, :update, :destroy]
# GET /products
# GET /products.json
def index
if params[:q]
search_term = params[:q]
#products = Product.search(search_term)
else
#products = Product.limit(4)
end
end
# GET /products/1
# GET /products/1.json
def show
#comments = #product.comments.order("created_at DESC")
#comments = #comments.paginate(:page => params[:page], :per_page => 2)
end
# GET /products/new
def new
#product = Product.new
end
# GET /products/1/edit
def edit
end
# POST /products
# POST /products.json
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
# PATCH/PUT /products/1
# PATCH/PUT /products/1.json
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
# DELETE /products/1
# DELETE /products/1.json
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
# Use callbacks to share common setup or constraints between actions.
def set_product
#product = Product.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def product_params
params.require(:product).permit(:name, :description, :image_url, :color)
end
end
Am relatively new to rails so sorry if this is a silly question but any help would be greatly appreciated.
price 'cannot be blank'
You should whitelist price in the product_params method to fix the validation error (i.e, price 'cannot be blank') and to able to save the product with its price successfully.
def product_params
params.require(:product).permit(:name, :description, :image_url, :color, :price)
end
Reason for the error:
Since you didn't whitelisted price attribute, it will treated as unpermitted params and won't be included in the params hash while saving the #product. And as save triggers the validations, #product fails the validation check on price resulting in that error.
I'm creating a website on which people can read mangas, thanks to three scaffolds : one for the manga itself, one for its chapters and a last one for the chapter's pages. In my routes.rb file, I nested the pages inside the chapter resources, which I nested inside the manga's, so my routes look like the following:
resources :mangas do
resources :chapters do
resources :pejis #Rails doesn't like the "scan" word
end
end
I can create a manga without troubles, but for an unknown reason, I can't manage to make the form appear:
views/chapters/_form.html.erb
<%= form_for(chapter) do |f| %>
<div class="field">
<%= f.label :titre %>
<%= f.text_field :titre %>
</div>
<div class="field">
<%= f.label :apercu %>
<%= f.file_field :apercu %>
</div>
<div class="field">
<!-- Allows me to upload the chapter's pages in the same time -->
<label for="images[]">Multi Upload</label>
<%= file_field_tag 'images[]', type: :file, multiple: true %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
The form_for parameter is as it was when created by the scaffold command. However, obviously, it doesn't work since I nested the resources inside the manga scaffold. I tried a few things, until new_manga_chapter_path allowed me to see the form. However, when submitting it, I'm getting the following error:
No route matches [POST] "/mangas/1/chapters/new". I'm not even sure if this is normal or strange.
I'm pretty sure I shouldn't put "new_manga_chapter_path" as parameter for the form_for but I have no idea what to put instead.
Just in case, here is the chapter controller:
class ChaptersController < ApplicationController
before_action :set_chapter, only: [:show, :edit, :update, :destroy]
def index
#chapters = Chapter.all
end
def show
end
def new
#chapter = Chapter.new
end
def edit
end
def create
#chapter = Chapter.new(chapter_params)
if #chapter.save
(params[:images] || []).each_with_index do |image, index|
#chapter.pejis.create(image: image, scan_number: index + 1)
end
redirect_to #chapter, notice: 'Chapter was successfully created.'
else
render :new
end
end
def update
if #chapter.update(chapter_params)
(params[:images] || []).each_with_index do |image, index|
#chapter.pejis.create(image: image, scan_number: index + 1)
end
redirect_to #chapter, notice: 'Chapter was successfully updated.'
else
render :edit
end
end
def destroy
#chapter.destroy
redirect_to chapters_url, notice: 'Chapter was successfully destroyed.'
end
private
def set_chapter
#chapter = Chapter.friendly.find(params[:id])
end
def chapter_params
params.require(:chapter).permit(:titre, :apercu)
end
end
Don't hesitate if you want to see something else
Thank you in advance
Try this:
# don't replace the [] by ()
<%= form_for [#manga, #chapter] do |f| %>
Or
<%= form_for manga_chapter_path(#manga, #chapter) %>
And in your controller:
def new
#manga = Manga.find(params[:manga_id])
#chapter = Chapter.new
end
To help you after your comments:
In your create method:
def create
#manga = Manga.find(params[:manga_id])
#chapter = Chapter.new(chapter_params)
if #chapter.save
(params[:images] || []).each_with_index do |image, index|
#chapter.pejis.create(image: image, scan_number: index + 1)
end
redirect_to manga_chapter_path(#manga, #chapter), notice: 'Chapter was successfully created.'
else
render :new
end
end
app/controllers/products_controller.rb:
class ProductsController < ApplicationController
before_action :set_product, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!, except: [:index, :show]
# GET /products
# GET /products.json
def index
##products = Product.all
#products = Product.all
if params[:search]
#products = Product.search(params[:search]).order("created_at DESC").paginate(page: params[:page], per_page: 5)
else
#products = Product.all.order('created_at DESC').paginate(page: params[:page], per_page: 5)
end
if session[:cart] then
#cart = session[:cart]
else
#cart = {}
end
end
# GET /products/1
# GET /products/1.json
def show
end
# GET /products/new
def new
if current_user.admin?
##product = Product.new
#product = Product.new
#categories = Category.all.map{|c| [ c.name, c.id ] }
end
end
# GET /products/1/edit
def edit
if current_user.admin?
#categories = Category.all.map{|c| [ c.name, c.id ] }
end
end
# POST /products
# POST /products.json
def create
if current_user.admin?
#product = Product.new(product_params)
#product.category_id = params[:category_id]
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
end
# PATCH/PUT /products/1
# PATCH/PUT /products/1.json
def update
if current_user.admin?
#product.category_id = params[:category_id]
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
end
# DELETE /products/1
# DELETE /products/1.json
def destroy
if current_user.admin?
#product.destroy
respond_to do |format|
format.html { redirect_to products_url, notice: 'Product was successfully destroyed.' }
format.json { head :no_content }
end
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_product
#product = Product.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def product_params
params.require(:product).permit(:title, :description, :photo, :price, :category, :subcategory)
end
end
app/views/products/new.html.erb:
<h1>New Product</h1>
<%= render 'form' %>
<%= link_to 'Back', products_path %>
app/views/products/_form.html.erb:
<%= form_for(#product, multipart: true) do |f| %>
<% if #product.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#product.errors.count, "error") %> prohibited this product from being saved:</h2>
<ul>
<% #product.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :title %><br>
<%= f.text_field :title %>
</div>
<div class="field">
<%= f.label :description %><br>
<%= f.text_area :description %>
</div>
<div class="field">
<%= f.label :image %><br>
<%= f.file_field :photo %>
</div>
<div class="field">
<%= f.label :price %><br>
<%= f.number_field :price, :step => "0.01" %>
</div>
<div class="field">
<%= f.label :category %><br>
<%= select_tag(:category_id, options_for_select(#categories), :prompt => "Select one!") %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
My test in test/controllers/products_controller_test.rb:
test "should create product" do
sign_in users(:admin)
assert_difference('Product.count') do
post :create, product: { category_id: #product.category, description: #product.description, photo_content_type: #product.photo_content_type, photo_file_name: #product.photo_file_name, photo_file_size: #product.photo_file_size, photo_updated_at: #product.photo_updated_at, price: #product.price, title: #product.title }
end
assert_redirected_to product_path(assigns(:product))
end
Causing this error:
Finished in 0.816541s, 24.4936 runs/s, 35.5157 assertions/s.
1) Error:
ProductsControllerTest#test_should_create_product:
ActionView::Template::Error: undefined method `map' for nil:NilClass
app/views/products/_form.html.erb:32:in `block in _app_views_products__form_html_erb__4489093195482743578_70189257075560'
app/views/products/_form.html.erb:1:in `_app_views_products__form_html_erb__4489093195482743578_70189257075560'
app/views/products/new.html.erb:3:in `_app_views_products_new_html_erb__3438187527367239596_70189257283820'
app/controllers/products_controller.rb:57:in `block (2 levels) in create'
app/controllers/products_controller.rb:52:in `create'
test/controllers/products_controller_test.rb:29:in `block (2 levels) in <class:ProductsControllerTest>'
test/controllers/products_controller_test.rb:28:in `block in <class:ProductsControllerTest>'
20 runs, 29 assertions, 0 failures, 1 errors, 0 skips
I'm running my tests with rake test:functionals. The other tests are fine but only this test is problematic.
There is even no any method named map in app/views/products/_form.html.erb:. So I don't have any idea for cause of this error.
If you want to look to the whole project: https://github.com/mertyildiran/SCOR
CHANGES:
According to answers:
In form I made this change: <%= f.collection_select(:category_id, Category.all, :id, :name) %>
In products controller: params.require(:product).permit(:title, :description, :photo, :price, :category_id)
And new version of test:
test "should create product" do
sign_in users(:admin)
image = fixture_file_upload "../../public/assets/products/1/original/" + #product.photo_file_name
puts image.inspect
assert_difference('Product.count') do
post :create, product: { category_id: #product.category, description: #product.description, photo: image, price: #product.price, title: #product.title }
end
assert_redirected_to product_path(assigns(:product))
end
We get rid of error but Failure persist, stdout:
# Running:
.......#<Rack::Test::UploadedFile:0x007f5a1894fdf8 #content_type=nil, #original_filename="ra_unisex_tshirt_x1000_fafafa-ca443f4786_front-c_235_200_225_294-bg_f8f8f8_(7).jpg", #tempfile=#<Tempfile:/tmp/ra_unisex_tshirt_x1000_fafafa-ca443f4786_front-c_235_200_225_294-bg_f8f8f8_(7).jpg20160515-31919-1j7oypa>>
F............
Finished in 0.800235s, 24.9926 runs/s, 37.4890 assertions/s.
1) Failure:
ProductsControllerTest#test_should_create_product [/home/mertyildiran/Documents/SCOR/test/controllers/products_controller_test.rb:25]:
"Product.count" didn't change by 1.
Expected: 3
Actual: 2
20 runs, 30 assertions, 1 failures, 0 errors, 0 skips
Validation Failure Cause:
<div id="error_explanation">
<h2>2 errors prohibited this product from being saved:</h2>
<ul>
<li>Photo content type is invalid</li>
<li>Photo is invalid</li>
</ul>
</div>
Fixed with this image.content_type = #product.photo_content_type
When you call post :create, product: ... in your test a validation is failing which causes the new view to be rendered.
The new view then causes an error by calling options_for_select(#categories) but #categories is not defined - in other words nil. options_for_select expects the argument to be an array and calls .map on nil.
Thats not the only problem - you are not nesting the input properly either.
<%= select_tag(:category_id, options_for_select(#categories), :prompt => "Select one!") %>
Would end up in params[:category_id] not params[:product][:category_id].
Instead what you want to do is use collection_select and bind it to the model:
<div class="field">
<%= f.label :category_id %><br>
<%= f.collection_select(:category_id, Category.all, :id, :name) %>
</div>
You should also ensure that you are whitelisting the correct param - and if you have a hard time keeping track of them make sure you test it as well!
def product_params
params.require(:product).permit(:title, :description, :photo, :price, :category_id, :subcategory)
end
If you want to debug exactly what attribute causes the validation failure there are a few options:
assigns(:product) will give you the #product instance variable from your controller. assert_equals(#product.errors.full_messages, []) gives you a list. You have to call the controller action first!
You can do assert_equals(#response.body, '') to get a dump of the page HTML. You have to call the controller action first!
Keep a tab on the logs with $ tail -f logs/test.log
From the stack trace it appears that the validation failed when trying to save the #product and the controller wants to render the new template. But you don't have the #categories variable defined for this scenario, that is probably where the map call for nil is getting from.
So, probably you should do both of the following:
add the #categories array to the failed save part of your create and update actions in your controller so that the failed-save scenario works ok
fix the test code so that the validations do not fail when creating the product.
I am trying to follow this tutorial. It has written in previous version of Rails and I am using Rails 4. In section "Creating view file for new method" I have updated given code sample to work with current Rails but I am getting following error:
ActiveRecord::RecordInvalid in BookController#create
Validation failed: Title can't be blank, Price Error Message
Extracted source (around line #14):
12 def create
13 #book = Book.new(book_params)
14 if #book.save!
15 redirect_to :action => 'list'
16 else
17 #subjects = Subject.all
Rails.root: C:/Ruby193/mylibrary
Application Trace | Framework Trace | Full Trace
app/controllers/book_controller.rb:14:in `create'
Request
Parameters:
{"utf8"=>"✓",
"authenticity_token"=>"Xla1xJlMqCx96ZITbI6JHOvoNIoAHc5ItcZgcMzs0/Y=",
"title"=>"asd",
"price"=>"asd",
"book"=>{"subject_id"=>"1"},
"description"=>"asd",
"commit"=>"Create",
"method"=>"post"}
Here is my routing file:
Rails.application.routes.draw do
get 'book/list' => 'book#list'
get 'book/new' => 'book#new'
post 'book/create' => 'book#create'
end
Here is my controller class:
class BookController < ApplicationController
def list
#books = Book.all
end
def show
#book = Book.find(params[:id])
end
def new
#book = Book.new
#subjects = Subject.all
end
def create
#book = Book.new(book_params)
if #book.save!
redirect_to :action => 'list'
else
#subjects = Subject.all
render :action => 'new'
end
end
def edit
#book = Book.find(params[:id])
#subjects = Subject.all
end
def update
#book = Book.find(params[:id])
if #book.update_attributes(book_params)
redirect_to :action => 'show', :id => #book
else
#subjects = Subject.all
render :action => 'edit'
end
end
def delete
Book.find(params[:id]).destroy
redirect_to :action => 'list'
end
private
def book_params
params.require(:book).permit(:title, :price, :description)
end
end
Here is view- new.html
<h1>Add new book</h1>
<%= form_tag(controller: "book", action: "create", method: "post") do %>
<%= label_tag(:title, "Title") %>
<%= text_field_tag(:title) %><br>
<%= label_tag(:price, "Price") %>
<%= text_field_tag(:price) %><br>
<%= label_tag(:q, "Subject") %>
<%= collection_select(:book,:subject_id,#subjects,:id,:name) %><br>a
<%= label_tag(:description, "Description") %><br>
<%= text_area_tag(:description) %><br>
<%= submit_tag( "Create") %>
<%end %>
<%= link_to 'Back', {:action => 'list'} %>
What should I do? Thank you in advance
You need to change your form to properly place the :title attribute underneath the book key in your params. There are other helpers that let you do this:
Try changing lines like:
label_tag( :title )
text_field_tag(:title)
to
label( :book, :title )
text_field(:book, :title )
In your create method use save rather than save!
save! will throw validation errors in the manner you are seeing, save will add error messages to the #book object so that the user can fix them in a form.
See the documentation for details.
def create
#book = Book.new(book_params)
if #book.save
redirect_to :action => 'list'
else
#subjects = Subject.all
render :action => 'new'
end
end