I'm having trouble having order go through. I have posted the error bellow. I think the issue has to do with the create method in the OrderController.rb, I do have the total_price method already defined but.. other than that I'm not sure how to fix the issue. Any help would be appreciated. Thank you.
class OrderTransaction
def initialize order, nonce
#order = order
#nonce = nonce
end
def execute
#result = Braintree::Transaction.sale(
amount: order.total_price,
payment_method_nonce: nonce
)
end
def ok?
#result.success?
end
private
attr_reader :order, :nonce
end
class Order < ActiveRecord::Base
belongs_to :user
has_many :order_items
def total_price
order_items.inject(0) { |sum, item| sum + item.total_price }
end
end
class OrdersController < ApplicationController
before_filter :initialize_cart
def index
#orders = Order.order(created_at: :desc).all
end
def create
#order_form = OrderForm.new(
user: User.new(order_params[:user]),
cart: #cart
)
if #order_form.save
notify_user
if charge_user
redirect_to root_path, notice: "Thank you for placing the order."
else
flash[:warning] = <<EOF
Your order ID is #{#order_form.order.id}.
<br/>
Something went wrong.
EOF
redirect_to new_payment_order_path(#order_form.order)
end
else
render "carts/checkout"
end
end
def update
#order = Order.find params[:id]
#previous_state = #order.state
if #order.update state_order_params
notify_user_about_state
redirect_to orders_path, notice: "Order was updated."
end
end
def new_payment
#order = Order.find params[:id]
#client_token = Braintree::ClientToken.generate
end
def pay
#order = Order.find params[:id]
transaction = OrderTransaction.new #order, params[:payment_method_nonce]
transaction.execute
if transaction.ok?
redirect_to root_path, notice: "Thank you for placing the order."
else
render "orders/new_payment"
end
end
private
def notify_user
#order_form.user.send_reset_password_instructions
OrderMailer.order_confirmation(#order_form.order).deliver
end
def notify_user_about_state
OrderMailer.state_changed(#order, #previous_state).deliver
end
def order_params
params.require(:order_form).permit(
user: [ :name, :phone, :address, :city, :country, :postal_code, :email ]
)
end
def charge_user
transaction = OrderTransaction.new #order, params[:payment_method_nonce]
transaction.execute
transaction.ok?
end
def state_order_params
params.require(:order).permit(:state)
end
end
class OrderItem < ActiveRecord::Base
belongs_to :order
belongs_to :product
def total_price
self.quantity * self.product.price
end
end
class OrderForm
include ActiveModel::Model
attr_accessor :user, :order # credit_card
attr_writer :cart
def save
set_password_for_user
if valid?
persist
true
else
false
end
end
def has_errors?
user.errors.any?
end
private
def valid?
user.valid?
end
def persist
user.save
#order = Order.create! user: user
build_order_items
end
def set_password_for_user
user.password = Digest::SHA1.hexdigest(user.email + Time.now.to_s)[0..8]
end
def build_order_items
#cart.items.each do |item|
#order.order_items.create! product_id: item.product_id, quantity: item.quantity
end
end
end
class OrderItem < ActiveRecord::Base
belongs_to :order
belongs_to :product
def total_price
self.quantity * self.product.price
end
end
As a standard note, any NilClass error basically means you haven't defined the variable you're trying to manipulate.
The key to solving the problem is to therefore find why the variable isn't defined, and populate it.
def execute
#result = Braintree::Transaction.sale(
amount: order.total_price,
payment_method_nonce: nonce
)
end
This is where Rails says the variable is not populated.
However, as with many problems in programming, the cause of the issue may not be as defined...
I initially thought the problem was that you weren't calling #order. However, the class initializes with order, so that shouldn't be a problem. So you have to look at how you're invoking the class:
transaction = OrderTransaction.new #order, params[:payment_method_nonce]
This surmises that #order is defined.
I surmise it isn't.
Here's what I'd do:
def create
#order_form = OrderForm.new(
user: User.new(order_params[:user]),
cart: #cart
)
if #order_form.save
notify_user
#order = #order_form.order #-> not efficient but should create #order
if charge_user
redirect_to root_path, notice: "Thank you for placing the order."
else
flash[:warning] = <<EOF
Your order ID is #{#order_form.order.id}.
<br/>
Something went wrong.
EOF
redirect_to new_payment_order_path(#order_form.order)
end
else
render "carts/checkout"
end
end
Personally, I think this highlights a deeper problem with your code structure:
You're creating an OrderForm object and yet processing #order_form.order
Your controller is full of tiny methods which bloat it up big time
Your controller is for orders, yet builds OrderForm objects
I'd do my best to make my controller as thin as possible:
#app/controllers/orders_controller.rb
class OrdersController < ApplicationController
def new
#order = current_user.order.new
end
def create
#order = current_user.order.new order_params
if #order.save
#order.charge
end
end
private
def order_params
params.require(:order).permit(:x, :y, :z, order_products_attributes: [:product, :qty])
end
end
I'd have a more modular model structure:
#app/models/order.rb
class Order < ActiveRecord::Base
belongs_to :user
has_many :order_products
has_many :products, through: :order_products, extend ProductQty
has_many :payments, inverse_of: :order
scope :cart, -> { order_products }
def total_price
products.pluck(:price, :qty) #-> need to work out
end
def charge
payment = payments.create
payment.execute ? payment.success : payment.error #-> something conditional
end
end
#app/models/order_product.rb
class OrderProduct < ActiveRecord::Base
#columns id | order_id | product_id | qty | created_at | updated_at
belongs_to :order
belongs_to :product
end
#app/models/payment.rb
class Payment < ActiveRecord::Base
belongs_to :order, inverse_of: :payments
def execute
Braintree::Transaction.sale(amount: order.total_price)
end
end
#app/models/product.rb
class Product < ActiveRecord::Base
has_many :order_products
has_many :orders, through: :order_products
end
#app/models/concerns/product_qty.rb
module ProductQty
#Load
def load
products.each do |qty|
proxy_association.target << qty
end
end
#Private
private
#Products
def products
return_array = []
through_collection.each_with_index do |through,i|
associate = through.send(reflection_name)
associate.assign_attributes({qty: items[i]})
return_array.concat Array.new(1).fill( associate )
end
return_array
end
#######################
# Variables #
#######################
#Association
def reflection_name
proxy_association.source_reflection.name
end
#Foreign Key
def through_source_key
proxy_association.reflection.source_reflection.foreign_key
end
#Primary Key
def through_primary_key
proxy_association.reflection.through_reflection.active_record_primary_key
end
#Through Name
def through_name
proxy_association.reflection.through_reflection.name
end
#Through
def through_collection
proxy_association.owner.send through_name
end
#Captions
def items
through_collection.map(&:qty)
end
#Target
def target_collection
proxy_association.target
end
end
I wanted to include cart somewhere, I'll have to do that another time.
For now, you'd be able to do the following:
#order = current_user.orders.find params[:id]
#order.products.each do |product|
product.qty #-> 5
#order.total_price #-> prices * qtys
--
This is not complete or tested, but I hope it shows you how you could improve your code structure dramatically, by making it modular. IE keep as many methods tied to your objects as possible.
In short, you should be able to do the following:
#order = current_users.orders.find params[:id]
if #order.payments.any?
#payment = #order.payment.first
#payment.success?
end
The problem is in your charge_user method inside OrdersController class where you call this code:
transaction = OrderTransaction.new #order, params[:payment_method_nonce]
you don't really defined #order in this method, i.e. #order is nil here and that's causing the problem for you here and you are getting this error: undefined method total_price for nil:NilClass
Set #order value inside the charge_user method before you call this line of code and make sure #order is NOT nil:
transaction = OrderTransaction.new #order, params[:payment_method_nonce]
One possible solution is to modify your charge_user method to take an order argument like this:
def charge_user(order)
transaction = OrderTransaction.new order, params[:payment_method_nonce]
transaction.execute
transaction.ok?
end
And, in your create method call like this:
if charge_user(#order_form.order)
redirect_to root_path, notice: "Thank you for placing the order."
else
# rest of the code
end
This will solve your issue.
Related
I am creating a webiste where people can debate with each other. It has 4 main models - post, for_the_motion, against_the_motion, and user( added in the respective order). I ran a migration and made a association between for model and against model.
For each view in "for" model I want to show which user added that particular motion. But I am getting an error
undefined method `image_url' for nil:NilClass
Stuck from long time on this. This is how the models look
user.rb
class User < ApplicationRecord
has_many :posts
has_many :fors
has_many :againsts
class << self
def from_omniauth(auth_hash)
user = find_or_create_by(uid: auth_hash['uid'], provider: auth_hash['provider'])
user.name = auth_hash['info']['name']
user.image_url = auth_hash['info']['image']
user.url = auth_hash['info']['urls'][user.provider.capitalize]
user.save!
user
end
end
end
for.rb
class For < ApplicationRecord
belongs_to :post, optional: true
belongs_to :user,optional: true
end
post.rb
class Post < ApplicationRecord
has_many :fors, dependent: :destroy
has_many :againsts, dependent: :destroy
belongs_to :user, optional: true
end
against.rb
class Against < ApplicationRecord
belongs_to :post, optional: true
belongs_to :user, optional:true
end
CONTROLLERS
posts_controller.rb
class PostsController < ApplicationController
def index
#posts = Post.all
end
def land
end
def show
#post = Post.find(params[:id])
end
def new
#post = Post.new
end
def create
#post = Post.new(post_params)
#post.user = current_user
if #post.save
redirect_to #post
else
render 'new'
end
end
private
def post_params
params.require(:post).permit(:title)
end
end
fors_controller.rb
class ForsController < ApplicationController
def create
#post = Post.find(params[:post_id])
#for = #post.fors.create(fors_params)
#for.user = current_user
redirect_to post_path(#post)
end
private
def fors_params
params.require(:for).permit(:content)
end
end
sessions_controller.rb
class SessionsController < ApplicationController
def create
begin
#user = User.from_omniauth(request.env['omniauth.auth'])
session[:user_id] = #user.id
# flash[:success] = "Welcome, #{#user.name}!"
rescue
# flash[:warning] = "There was an error while trying to authenticate you..."
end
redirect_to root_path
def destroy
if current_user
session.delete(:user_id)
# flash[:success] = 'See you!'
end
redirect_to root_path
end
end
end
This is where I am getting the error
<h1><%=#post.title%></h1>
<div class="fort">
<h3>For the motion</h3>
<%#post.fors.each do |f|%>
<p><%=f.content%></p>
<p><%=f.user.image_url%></p>/*This is where errors arise*/
<%end%>
<%= render "fors/form"%>
</div>
<div class="against">
<h3>Against the motion</h3>
<%#post.againsts.each do |f|%>
<p><%=f.content%></p>
<p><%= #post.user.name%></p>
<%end%>
<%= render "againsts/form"%>
</div>
Here is the github link for any other required information
https://github.com/sarfrazbaig/DebatingSociety2
Seems like you missed saving the .user on fors_controller.rb:
class ForsController < ApplicationController
def create
#post = Post.find(params[:post_id])
#for = #post.fors.create(fors_params)
# .create above already will save a new For record in DB
# therefore your #for.user assignation will be only assigned in memory, but not yet in DB
#for.user = current_user
# you'll need to save it again afterwards:
#for.save
redirect_to post_path(#post)
end
# ...
end
Suggestion:
use .new instead of .create to not-yet-save into the DB, and only call save when everything that you need to assign is already assigned.
class ForsController < ApplicationController
def create
#post = Post.find(params[:post_id])
#for = #post.fors.new(fors_params)
#for.user = current_user
#for.save
redirect_to post_path(#post)
end
# ...
end
Take note that you would still encounter that error even if you already updated your code with the above; this is because currently your For records in the DB all are missing the .user value. You'll have to manually assign and save the .user accordingly for each For record, and probably best that you'd write a...
class For < ApplicationRecord
validates :user, presence: true
end
... validation so that this error will be prevented in the future.
One of the #post.fors is lacking a user, which is permitted by the belongs_to :user, optional: true in your For model.
You can restrict your query to showing only fors that have an associated user:
#post.fors.joins(:users) or you can use the safe navigation operator to return nil when attempting to read the image_url for a non-existent user - f.user&.image_url
When I try to delete something from my inventory database, I get this error:
ActiveRecord::InvalidForeignKey in InventoriesController#destroy
SQLite3::ConstraintException: FOREIGN KEY constraint failed: DELETE FROM "inventories" WHERE "inventories"."id" = ?
In the terminal, it says
{"_method"=>"delete", "authenticity_token"=>"dBNU2GkV0+rOcp4NVEljm4oIpkdOnPsvZKdmisaadBzX3QkY1VwurZNRPL0WFtVvizeAcJb7H6E50ObmpRsXAg==", "id"=>"1"}
It also says the source is at:
def destroy
#inventory = Inventory.find(params[:id])
#inventory.destroy
redirect_to inventory_path
end
In my inventories file which is:
class InventoriesController < ApplicationController
def show
#inventory = Inventory.find(params[:id])
end
def index
#inventories = Inventory.all
end
def new
#inventory = Inventory.new
end
def create
#inventory = Inventory.new(inventory_params)
if #inventory.save
redirect_to #inventory
else
render 'new'
end
end
def edit
#inventory = Inventory.find(params[:id])
end
def update
#inventory = Inventory.find(params[:id])
if #inventory.update(inventory_params)
redirect_to #inventory
else
render 'edit'
end
end
def destroy
#inventory = Inventory.find(params[:id])
#inventory.destroy
redirect_to inventory_path
end
end
private
def inventory_params
params.require(:inventory).permit(:product_name, :brand_name, :item_id, :upc_code, :color, :department, :size, :condition, :fabric_type, :shipping_weight, :sku, :asin, :quantity, :cost_price, :sell_price, :key_product_features, :product_description, :search_terms, :status, :listing_in_usa, :listing_in_canada, :listing_in_mexico)
end
It sounds like there is a foreign key to the inventories table from another one, which you would expect to see expressed in the Inventory model with a has_many or has_one relationship.
The fixes would be to either have the foreign key configured to automatically delete the child records, or to specify dependent: :destroy on the association.
The former would be very fast, but would not allow callbacks on the child instances to execute, so I'd recommend the :destroy option.
hey guys i want to send an email in rails when i click the place order button to a user email id but i get this error when i click the button undefined method 'items' for #<Order:0x6d83c48>
this my order_mailer.rb in mailer folder
class OrderMailer < ActionMailer::Base
default from: "gokuverma94#gmail.com"
def order_confirmation order
#order=order
mail to: order.user.email, subject: "Your order (##{order.id})"
end
end
and this is my orders_controller.rb
class OrdersController < ApplicationController
def create
#order_form=OrderForm.new(user: User.new(order_params[:user]),cart: #cart)
if #order_form.save
notify_user
redirect_to root_path,notice:"Thank You for placing the order sir."
else
render "carts/checkout"
end
end
private
def notify_user
OrderMailer.order_confirmation(#order_form.order).deliver
end
def order_params
params.require(:order_form).permit(
user:[:name,:phone,:address,:city,:country,:postal_code,:email])
end
end
and finally my order_confirmation.text.erb in the views/order_mailer folder
<h2>Hello</h2><%=#order.user.name%>,
Thank you for placing an order. Here is a summary for order #<%=#order.id%>:
<% #order.items.each do |item|%>
*<%=item.quantity%><%=item.product.name%>($<%=item.total_price%>)
<%end%>
Total: $<%=#order.total_price%>
#order.user.name is working fine but,
I dont know why it cannot find the items method in order can someone please tell me what is wrong with it because i just cant seem to figure it out
cart.rb
class Cart
attr_reader :items
def self.build_from_hash (hash)
items= if hash["cart"] then
hash["cart"]["items"].map do |item_data|
CartItem.new item_data["product_id"],item_data["quantity"]
end
else
[]
end
new items
end
def initialize items=[]
#items=items
end
def add_item (product_id)
item=#items.find { |item| item.product_id == product_id}
if item
item.increment
else
#items << CartItem.new(product_id)
end
end
def empty?
#items.empty?
end
def count
#items.length
end
def grand_total
#items.inject(0){|sum,item| sum+item.total_price}
end
def serialize
items = #items.map do |item|
{
"product_id" =>item.product_id,
"quantity" => item.quantity
}
end
{
"items" => items
}
end
end
order_item.rb
class OrderItem < ApplicationRecord
belongs_to :order
belongs_to :product
def total_price
self.product.price*self.quantity
end
end
cart_item.rb
class CartItem
attr_reader :product_id,:quantity
def initialize (product_id,quantity=1)
#product_id=product_id
#quantity=quantity
end
def increment
#quantity=#quantity+1
end
def product
Product.find product_id
end
def total_price
product.price*quantity
end
end
From your model data order does not have items, so you cant use #order.items
Replace <% #order.items.each do |item|%> with <% #order.order_items.each do |item|%>
In an E commerce Rails App I'm building products that is deleted from the ShoppingCart are not added back to the production model after deletion.
When I add Products to the Cart the App is using this controller below to decrease the number of products from the Product model( see the create method)
controllers/product_item_controller.rb
class ProductItemsController < ApplicationController
include CurrentCart
before_action :set_cart, only: [:create]
before_action :set_product_item, only: [:show, :destroy]
def create
#product = Product.find(params[:product_id])
#product_item = #cart.add_product(#product.id)
if #product_item.save
redirect_to root_url, notice:'Product added to Cart'
product = Product.find params[:product_id]
product.update_columns(stock_quantity: product.stock_quantity - 1)
else
render :new
end
end
private
def set_product_item
#product_item = ProductItem.find(params[:id])
end
def product_item_params
params.require(:product_item).permit(:product_id)
end
end
That is woking fine.
But when I delete the Cart it gets deleted but the products are not added to the products model. And I also get this messages : Invalid Cart
this is the carts_controller.rb
class CartsController < ApplicationController
before_action :set_cart, only: [:show, :destroy]
rescue_from ActiveRecord::RecordNotFound, with: :invalid_cart
def new
#cart = Cart.new
end
def show
#images = ["1.jpg", "2.jpg", "3.jpg", "4.jpg", "5.jpg"]
#random_no = rand(5)
#random_image = #images[#random_no]
end
def destroy
#cart.destroy if #cart.id == session[:cart_id]
session[:cart_id] = nil
product = Product.find params[:product_id]
product.update_columns(stock_quantity: product.stock_quantity + 1)
redirect_to root_url, notice: 'Your Cart is Empty'
end
def remove
cart = session['cart']
item = cart['items'].find { |item| item['product_id'] == params[:id] }
product = Product.find(item['product_id'])
product.update_columns(stock_quantity: product.stock_quantity + 1)
if item
cart['items'].delete item
end
redirect_to cart_path
end
private
def set_cart
#cart = Cart.find(params[:id])
end
def cart_params
params[:cart]
end
def invalid_cart
logger_error = 'You are trying to access invalid cart'
redirect_to root_url, notice: 'Invalid Cart'
end
end
I Can't see what is wrong with this code and why the products are not added to the product.rb after being deleted from the Cart.
Am I missing something here? Could someone advise me here?
Below are other relevant models and controllers
products_controller.rb
class ProductsController < ApplicationController
before_action :set_product, only: [:show, :edit, :update, :destroy]
def show
end
def search
#product = Product.search(params[:query]).order("created_at DESC")
#categories = Category.joins(:products).where(:products => {:id => #product.map{|x| x.id }}).distinct
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, :price_usd, :price_isl, :image, :category_id, :stock_quantity, :label_id, :query)
end
end
Cart.rbmodel
class Cart < ActiveRecord::Base
has_many :product_items, dependent: :destroy
def add_product(product_id)
current_item = product_items.find_by(product_id: product_id)
if current_item
current_item.quantity += 1
else
current_item = product_items.build(product_id: product_id)
end
current_item
end
def total_price_usd
product_items.to_a.sum{|item| item.total_price_usd}
end
def total_price_isl
product_items.to_a.sum{|item| item.total_price_isl}
end
end
product.rbmodel
Class Product < ActiveRecord::Base
belongs_to :category
belongs_to :label
has_many :product_item, :dependent => :destroy
#before_destroy :ensure_not_product_item
validates :title, :description, presence: true
validates :price_usd, :price_isl, numericality: {greater_than_or_equal_to: 0.01}
validates :title, uniqueness: true
has_attached_file :image, styles: { medium: "500x500#", thumb: "100x100#" }
validates_attachment_content_type :image, content_type: /\Aimage\/.*\z/
#def ensure_not_product_item
# if product_item.empty?
# return true
# else
# errors.add(:base, 'You have Product Items')
# return false
# end
#end
def self.search(query)
where("title LIKE ? OR description LIKE ?", "%#{query}%", "%#{query}%")
end
end
You are rescuing from ActiveRecord::RecordNotFound
rescue_from ActiveRecord::RecordNotFound, with: :invalid_cart
But you're probably rescuing inappropriately... from the Product.find... in the destroy method. I'm not sure why you would expect the product_id to be in params.
Your code...
def destroy
#cart.destroy if #cart.id == session[:cart_id]
session[:cart_id] = nil
product = Product.find params[:product_id]
product.update_columns(stock_quantity: product.stock_quantity + 1)
redirect_to root_url, notice: 'Your Cart is Empty'
end
A better alternative might be...
def destroy
if #card.id == session[:cart_id]
#cart.product_items each do |product_item|
product_item.product.update_columns(stock_quantity: product_item.product.stock_quantity + 1)
end
#cart.destroy
end
end
However this might better be done as a before_destroy action for product_item model, so that destroying a product_item will automatically increment the stock total.
I'm not going to give a line by line solution as there are quite a few points about this application that not quite right and require a bit of rethinking. Lets look at how a shopping cart commonly is done.
The models:
class User < ApplicationRecord
has_many :orders
has_many :products, through: :orders
def current_order
orders.find_or_create_by(status: :open)
end
end
class Order < ApplicationRecord
enum status: [:in_cart, :processing, :shipped]
belongs_to :user
has_many :line_items
has_many :products, through: :line_items
end
# The join model between a Order and Product
# The name line item comes from the lines on a order form.
class LineItem < ApplicationRecord
belongs_to :order
belongs_to :product
end
class Product < ApplicationRecord
has_many :line_items
has_many :orders, through: :line_items
end
The naming here is not a mistake or sloppy copy pasting. A cart is only a concept in web app which exists as a "user aid" in creating an order.
The join between a Order and Product is commonly called a line-item. Note that we use has_many though: so that we can query:
User.find(1).order
Product.find(1).orders
Order.find(1).products
The Controllers
When building something as complicated as a checkout you will want to pay attention to the Single Responsibility Principle and KISS. Having many classes is not a bad thing. Having huge tangled controllers that do far too much is.
So for example create a controller that has adding and removing items from the cart as its sole responsibility.
# routes.rb
resource :cart
resources :line_items,
only: [:create, :destroy, :update] do
collection do
delete :clear
end
end
end
# app/controllers/line_items.rb
class LineItemsController < ApplicationController
before_action :set_cart
before_action :set_item
rescue_from Orders::NotOpenError, -> { redirect_to #order, error: 'Order is locked and cannot be edited' }
# Add an item to cart
# POST /cart/line_items
def create
#cart.product_items.create(create_params)
# ...
end
# Remove an item from cart
# DESTROY /cart/line_items/:id
def destroy
#item.destroy
if #item.destroyed?
redirect_to cart_path, success: 'Item removed.'
else
redirect_to cart_path, alert: 'Could not remove item.'
end
end
# Remove all items from cart
# DESTROY /cart/line_items
def clear
#order.line_items.destroy_all
if #order.items.count.zero?
redirect_to cart_path, success: 'All items destroyed'
else
redirect_to cart_path, alert: 'Could not remove all items.'
end
end
# Update a line in the order
# PATCH /cart/line_items/:id
def update
#line_item.update(update_params)
end
private
def set_order
#order = current_user.current_order
# Ensure that order is not processed in some way
raise Orders::NotOpenError unless #order.open?
end
def set_line_item
#line_item = #order.line_items.find(params[:id])
end
def create_params
params.require(:line_item).permit(:product_id, :quantity)
end
def update_params
params.require(:line_item).permit(:quantity)
end
end
Notice how nicely the path for route each clearly tells us what it does and how we can write a description of the controller in a single line without using the word and.
In addition to this you will want a ProductsController, CartController, OrderController, PaymentsController etc. each of should do a single job - and do it well.
Don't do it all in your controllers!
When we add a line item to a order the available stock of the product should of course decrease. This is a clear cut example of business logic.
In MVC business logic belongs in the model layer.
A user adding a item to the cart should only create a reservation. The actual inventory of a product should only be altered when the order is processed or ships:
# No callbacks needed!
class Product < ApplicationRecord
has_many :line_items
has_many :orders, through: :line_items
def reservations
line_items.joins(:order)
.where
.not(line_items: {
order: Order.statuses[:shipped]
})
.sum(:quantity)
end
def availibity
stock - reservations
end
end
You've got
before_action :set_cart, only: [:show, :destroy]
rescue_from ActiveRecord::RecordNotFound, with: :invalid_cart
As soon as the CartsController#destroy method is invoked the private method set_cart is called. What it tries to do is to initialize an instance variable #cart = Cart.find(params[:id]).
The first line of your #destroy method is #cart.destroy if #cart.id == session[:cart_id]. Isn't the #cart = Cart.find(params[:id]) a problem here? What is the value of params[:id]? I guess it's not the same as session[:cart_id] and might probably be a nil or some Intreger value by which the DB cannot find a Cart record, hence the error.
Edit 1:
The same applies to the product = Product.find params[:product_id] as Steve mentioned in his answer.
Max posted a very informative report on how it should be done properly. If you have the time stick to his answer and try to redesign your app in accordance to his suggestion.
So the basis of my code so far is:
a customer has_one calendar
a calendar belongs_to a customer
a calendar has_many events
an event belongs_to a calendar
I am trying to, when creating a new event, specify the customer and calendar it belongs to but it throws error "undefined method `Calendar'":
class EventsController < ApplicationController
def new
#event = Event.new
#currentcalendar = current_customer.calendar # this is where it is failing
end
def create
if #event = #currentcalendar.build.event(event_params)
redirect_to '/main'
else
redirect_to '/compose'
end
end
private
def event_params
params.require(:event).permit(:calendar_id, :name, :starts_at, :ends_at)
end
end
this is my current_customer method within application_controller:
def current_customer
if (customer_id = session[:customer_id])
#current_customer ||= Customer.find_by(id: customer_id)
elsif (customer_id = cookies.signed[:customer_id])
customer = Customer.find_by(id: customer_id)
if customer && customer.authenticated?(cookies[:remember_token])
session[:customer_id] = customer.id #log in
#current_customer = customer
end
end
end
Here are the related controller files. Customer:
class CustomersController < ApplicationController
def new
#customer = Customer.new
#businesses = Business.all
#calendar = Calendar.new
end
def create
#customer = Customer.create(customer_params)
#calendar = #customer.build_calendar
#customer.save!
session[:customer_id] = #customer.id
redirect_to '/'
rescue ActiveRecord::RecordInvalid => ex
render action: 'new', alert: ex.message
end
private
def customer_params
params.require(:customer).permit(:first_name, :last_name, :business_no, :email, :password, :business_id)
end
Calendar:
class CalendarsController < ApplicationController
def new
#calendar = Calendar.new(calendar_params)
end
def create
#calendar = Calendar.new(calendar_params)
end
private
def calendar_params
params.require(:customer_id)
end
end
I'm very new to Ruby/ Rails and so can't figure this out by myself. Is this problem occurring because I have wrongly created my calendar? I wanted it to be created when its user is created, which works, but I just don't know how to get to the calendar and user within the events controller.
Thanks for your help!
EDIT: these are the model classes.
customer:
class Customer < ActiveRecord::Base
belongs_to :business
has_one :calendar
has_secure_password
attr_accessor :remember_token
#remembers a user in the database for use in persistent sessions
def remember
self.remember_token = Customer.new_token
update_attribute(:remember_digest, Customer.digest(remember_token))
end
def Customer.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
def forget
update_attribute(:remember_digest, nil)
end
def Customer.new_token
SecureRandom.urlsafe_base64
end
#returns true if the given token matches the digest
def authenticated?(remember_token)
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
end
calendar:
class Calendar < ActiveRecord::Base
belongs_to :customer
has_many :events
end
event:
class Event < ActiveRecord::Base
belongs_to :calendar
end
Your current_customer can be nil at times. To avoid this you can add a before_filter callback that checks if there is a customer that is logged in or not.
In your application_controller create a method called customer_found?
def customer_found?
current_customer.present?
end
Change your events controller to
class EventsController < ApplicationController
before_filter :customer_found?
before_filter :prepare_calendar, only: [:new, :create]
def new
#event = Event.new
end
def create
if #event = #current_calendar.build.event(event_params)
redirect_to '/main'
else
redirect_to '/compose'
end
end
private
def prepare_calendar
#current_calendar = current_customer.calendar
end
def event_params
params.require(:event).permit(:calendar_id, :name, :starts_at, :ends_at)
end
end
Since you did not assign your #current_calendar in your create method then you are gonna get undefined method build for nil class. You need to initialize the variable since it can not get it from the new method. Each action has its own independent variables so make sure to prepare all necessary variables before using them.