def remove_items
line_items.each do |item|
#ci = Product.find(item.id)
#ci.quantity = #ci.quantity.to_i - 1
end
Hello, I am trying to use the id of the item and then match the id with a product and then minus 1 to the quantity property of that product.
I currently get this error though.
TypeError in OrdersController#create
can't convert nil into String
What is wrong?
Thanks
OrderController#create
Please bear in mind code is scruffy due to being in progress. :)
def create
#order = current_cart.build_order(params[:order])
#order.ip_address = request.remote_ip
#cart = current_cart
if #order.save
if #order.purchase
#order.status = "paid"
#cart.remove_items
#cart.destroy
render :action => "success"
else
#order.status = "failed"
#cart.destroy
render :action => "failure"
end
else
render action: "new"
end
end
I think this is the stack trace
[0m
←[1m←[35mLineItem Load (0.0ms)←[0m SELECT "line_items".* FROM "line_items" WH
ERE "line_items"."cart_id" = 129
←[1m←[36mProduct Load (0.0ms)←[0m ←[1mSELECT "products".* FROM "products" WHE
RE "products"."id" = ? LIMIT 1←[0m [["id", 147]]
Completed 500 Internal Server Error in 5762ms
TypeError (can't convert nil into String):
app/controllers/application_controller.rb:60:in `+'
app/controllers/application_controller.rb:60:in `record_not_found'
According to your comment this should solve the problem:
# application_controller.rb
def record_not_found
flash[:alert] = "Cannot find record number #{params[:id]}. Displaying all records."
redirect_to root_path
end
But if I were you I wouldn't output the params[:id] in the alert message. Just say that the record has not been found, basta.
flash[:alert] = "Cannot find record. Displaying all records."
You can also do this in one line:
redirect_to root_path, alert: "Cannot find record. Displaying all records."
To fix the logic in your remove_items method you need to actually save the object at the end:
def remove_items
line_items.each do |item|
ci = Product.find(item.id)
ci.quantity = ci.quantity.to_i - 1
ci.save
end
end
Looks like you're buding a shopping cart app from scratch. Have you considered using an existing platform, like Spree?
It's a better idea to have the DB decrement all the Products at once:
def remove_items
Product.where(id:line_items.map(&:id)).update_all('quantity = quantity - 1')
end
OR even better:
def remove_items
Product.decrement_counter(:quantity, line_items.map(&:id) )
end
These are faster, avoid errors if the product can not be found, and also avoid a Race Condition if you have multiple processes running concurrently.
Related
I did a date validation in model which should display a message on page, but for some reason it isn't. I need this message to be shown in notice on error. Currently I just see the message in controller (Order not registered) but I need to see the reason (Delivery date should be from now). How can I do that?
model
def delivery_date_from_now_only
if self.delivery_date.present? && self.delivery_date <= Date.today
self.errors.add(:delivery_date, messsage: 'Delivery date should be from now')
end
end
controller
def create
#order = Order.new(order_params)
#order.user = current_user
if #order.save
flash[:notice] = 'Order registered successfully'
return redirect_to #order
end
#warehouses = Warehouse.all.order(:name)
#suppliers = Supplier.all.order(:brand_name)
#users = User.all.order(:name)
flash.now[:alert] = 'Order not registered'
render :new, status: 422
end
if you call #order.valid? it will validate your model an also populate errors for that object. that means #order.errors.messages will be a hash with { :delivery_date => ["Delivery date should be from now"]} etc (other errors will be in there as well) which u can use to do some flashes.
Here's my cart.rb model
class Cart < ActiveRecord::Base
has_many :tutors
def current_cart
if session[:cart_id]
#current_cart ||= Cart.find(session[:cart_id])
end
if session[:cart_id].nil?
#current_cart = Cart.create!
session[:cart_id] = #current_cart.id
end
#current_cart
end
def add_tutor(tutor_id)
tutor = Tutor.find(tutor_id)
if tutor
self.tutors << tutor
end
save
end
end
And here's my carts_controller.rb
class CartsController < ApplicationController
def show
#cart = current_cart
end
def checkout
#cart = current_cart
name = params[:checkout][:your_name]
email = params[:checkout][:your_email]
message = params[:checkout][:your_hp]
ApplicationMailer.checkout(#cart, name, email, message).deliver
flash[:success] = "We have received your request and will be in touch with you shortly!"
redirect_to root_path
end
def add_to_cart
#cart = current_cart.add_tutor(params[:tutor_id])
if #cart
flash[:success] = "You have successfully added the tutor to your cart!"
redirect_to tutors_path
else
flash[:danger] = "Oops! Something went wrong. Please try again!"
redirect_to root_path
end
end
end
And here's the button for the add to cart function
<%= button_to "Shortlist Tutor", add_to_cart_path(:tutor_id => tutor.id), :method => :post %>
The add to cart function seems to work fine as it redirects me to the correct page and no errors or anything like that seems to show up. In my rails console i can see in the server that there isn't any rollback.
Processing by CartsController#add_to_cart as HTML
Parameters: {"authenticity_token"=>"da3XDg69FSCrEyn39v8Apty4aX40TJH85BeW49x/4R3MElKYxip1w7rpbWRBYj5hhZDAivf7Bxn4FK1dkHyKpg==", "tutor_id"=>"3"}
Cart Load (0.2ms) SELECT "carts".* FROM "carts" WHERE "carts"."id" = ? LIMIT 1 [["id", 12]]
Tutor Load (0.3ms) SELECT "tutors".* FROM "tutors" WHERE "tutors"."id" = ? LIMIT 1 [["id", 3]]
(0.1ms) begin transaction
(0.1ms) commit transaction
(0.1ms) begin transaction
(0.1ms) commit transaction
Thats what i see in the server. I'm really not quite sure why don't i see anything when i go to my carts#show view.
Would appreciate any help that i can get. Thanks!
Check what you are receiving in params[:tutor_id].
Looking at SQL statements that are executed it doesn't look like it is saving at all when invoking add_to_cart. It only carries out a SELECT on carts and tutor.
I do this in my edit functions , update, destroy
#post = Post.friendly.find(params[:id])
if #post.user_id == current_user.id
there any way to optimize and make a single function, you used to like this:
before_action :set_user_post, only: [:edit, :update, :destroy]
private
def set_user_post
#post = current_user.posts.find_by(id: params[:id])
end
But add the friendly_id gem and modify it as follows:
private
def set_user_post
#post = current_user.posts.friendly.find(id: params[:id])
end
But it gives me error.
error:
Mysql2::Error: Unknown column 'id.id' in 'where clause': SELECT `posts`.* FROM `posts` WHERE `posts`.`user_id` = 1 AND `id`.`id` = 'primero' LIMIT 1
The find method finds a record by id and you can only pass a single id or an array of ids as input. But in your method, you're passing a hash. Modify your set_user_post method as
def set_user_post
begin
#post = current_user.posts.friendly.find(params[:id])
rescue ActiveRecord::RecordNotFound
redirect_to root_path, alert: 'You are knocking at the wrong door'
end
end
If a user's trying to access another user's post, the find method will raise an exception cause we're finding the posts of the current_user. We can rescue the exception and redirect the user to the home page with a
warning flash message.
EXTRA
You can also find a record using find_bymethod and pass it a hash. This method is used to find a record with any field(not just id). It returns the first record which matches the query.
For example,
def set_user_post
#post = current_user.posts.friendly.find_by(id: params[:id])
end
This does the same thing but the difference is that find raises an ActiveRecord::RecordNotFound exception if a record doesn't exist with the given id while find_by returns nil if a record doesn't exist.
For more, see http://apidock.com/rails/ActiveRecord/FinderMethods/find
I got the below to work and call up membership data based on current_user.id (See 200 Ok response) this shows up in my logs.
GET https://api-e1.authrocket.com/v1/memberships?user_id=usr_0vKhXTr0K1bz6kUipKNnjD
-> 200 (103.4 ms)
NoMethodError (undefined method `id' for #<NCore::Collection:0x007f50c82a7e50>):app/controllers/directory/categories_controller.rb:13:in `index'
I was using current_user.id which worked.
#directory_category.user_id = current_user.id
Trying to change it so it grabs the user's membership.id
#directory_category.user_id = membership_info.id
The full create statement I'm working with.
# Create
def create
#directory_category = Directory::Category.new(directory_category_params)
#directory_category.user_id = membership_info.id
if #directory_category.save
redirect_to directory_categories_path, notice: 'Category was successfully created.'
else
render :new
end
end
# Directory Category Index
def index
#directory_categories = Directory::Category.where(user_id: membership_info.id)
end
My attempt, which does say 200 ok getting current_user id and putting it at the end AuthRocket::Membership.all statement like below:
def current_user
#_current_user ||= AuthRocket::Session.from_token(session[:ar_token]).try(:user)
end
def membership_info
AuthRocket::Membership.all user_id: current_user.id
end
https://authrocket.com/docs/api/memberships
It looks like your
def membership_info
AuthRocket::Membership.all user_id: current_user.id
end
method is actually returning a collection and not a single item. if you change it to
def membership_info
AuthRocket::Membership.all(user_id: current_user.id).first
end
or whichever record you actually want, you should stop this error from raising.
I have the action, in where I assign groups to the members that apply. Basically, I just get a list of emails, from the view in a form, and then I have the action to catch it:
What I'm wondering is, that if I can rollback the changes that's been made already, if say the second member doesn't exist, or has a group already, how can I rollback these?
def group_create
#group = Group.new
params[:member].each { |m|
v = Volunteer.find_by_email(m[1])
raise "#{m[1]} doesn't exist" unless v.present?
raise "#{v.email} is already in a group" if v.group.present?
v.group = #group
v.save!
}
#group.save
rescue => error
flash[:error] = "Error: #{error}"
ensure
respond_to do |format|
unless flash[:error].present?
flash[:notice] = 'Group Application succeded.'
flash[:joined] = true
format.html { redirect_to apply_group_path }
else
flash.discard
format.html { render :group }
end
end
end
What I've thought about already, was move the v.save and #group.save to the end, and make another loop of params[:member].each...., but that would be quite a waste of ressources, to do the find_by_email-method twice as many times, as needed.
I suggest move your non-controller logic into model and wrap it in ActiveRecord::Base.transaction:
ActiveRecord::Base.transaction do
#group = Group.new
params[:member].each { |m|
v = Volunteer.find_by_email(m[1])
raise "#{m[1]} doesn't exist" unless v.present?
raise "#{v.email} is already in a group" if v.group.present?
v.group = #group
v.save!
}
#group.save
end
Use transactions as a protective wrapper around SQL statements to ensure changes to the database only occur when all actions succeed together.