Building a relation between a cart and an order - ruby-on-rails

I'm trying to integrate eCommerce functionality into my rails app, and am having trouble creating a new order. I start with a cart, which has_many orders, which has_many transactions. The first field in my order database is cart_id. I need to be able to access information in the cart (such as total_price) from the view/order/new.html.erb.
Where would be the best place to build this relation, and how? I can find the cart through the session id, but I don't know how to build the relationship. I was thinking in the order model, in the new action, somthing like so?
def new
#order = Order.new
current_cart.#order.build
Defined in my application controller is the function current_cart
def current_cart
Cart.find(session[:cart_id])
rescue ActiveRecord::RecordNotFound
cart = Cart.create
session[:cart_id] = cart.id
cart
end
UPDATE
Here is my new and create function, and where I need the value
def new
#order = Order.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #order }
end
end
# POST /orders
# POST /orders.json
def create
#order = Order.new(params[:order])
# THIS IS WHERE I HAVE TRIED TO BUILD THE RELATIONSHIP
# I have tried current_cart.orders.build, #order.cart = current_cart, and
# #order = current_cart.build_order(params[:order])
#order.ip_address = request.remote_ip
if #order.save
if #order.purchase
render :action => "success"
else
render :action => "failure"
end
else
render :action => 'new'
end
end
and this is where I need to access the cart in the model
def price_in_cents
(cart.total_price*100).round
end
And I always get an exception caught for an undefined function, either the build function, or the total price function

In the Order model you have the cart_id, so define the relation there:
belongs_to :cart
You can also define the relation in the Cart model, additionally:
has_many :orders
After that you can simply add new orders to your current basket:
#order = Order.new
#order.cart = current_cart
EDIT:
Maybe there is an other Problem with the current_cart method.
Try:
#order.cart_id = session[:cart_id]

I made a video about this: http://www.ror-e.com/info/videos/6
I actually separate the cart from the order. So basically The cart has_many cart_item and the order has_many order_items. I'd love to help out more. Feel free to contact me directly. I'd love to discuss the pro's & con's of different approaches.

In your create action:
#order = current_cart.build_order(order_params)
and add strong params:
private
def order_params
params.require(:order).permit(:first_name, :last_name, :card_type, :card_expires_on)
end

Related

Problems with Active Record Rails 4

I'm trying to call some ActiveRecord methods in OrdersController#new action.
I've tested the code in Rails Console and it works the way I intended.
But in the controller action it does not produce the same
order has_many cart_items
cart has_many cart_items
cart_items belong_to order cart_items
belong_to cart
cart_items belong_to product
product has_many cart_items
def new
#order.new
#order.cart_items = current_cart.cart_items
#order.save
current_cart.cart_items.destroy_all
end
Now current_cart is an application_controller method that checks if the current user has a shopping cart. If it does it pulls that cart from the database and if the user does not then it creates a new cart for the user. What I am trying to do here is when the user finalizes their order I'm trying to transfer the cart_items from current_cart to orders then clear the shopping cart.
When I do this in rails console, it gives me what I want. Order with cart_items that were in current_cart, and after I run destroy_all on the cart I have an empty active record association array.
When I test this in my controller both Order and Cart return an empty active association array.
What is going on here?
#application controller method of finding current_users cart
def current_cart
# if user is logged in
if current_user
#user = current_user
# checking user to see if account is confirmed and verified
if #user.confirmed_at != nil
# checking if user already has cart in cart database
if Cart.find_by(users_id: #user.id) != nil
# find a row in the database where users_id: equal to #user.id
# where clause does not work here
cart = Cart.find_by(users_id: #user.id)
session[:cart_id] = cart.id
cart.save
#establish Cart session cart for user
Cart.find(session[:cart_id])
else
# create a new Cart Object for user.assign current_user's id to cart object
cart = Cart.new
cart.users_id = #user.id
# save it to get cart id assign session[:cart_id] == cart.id
cart.save
session[:cart_id] = cart.id
end
end
end
end
class CartItemsController < ApplicationController
before_action :set_cart_item, only: [:show, :edit, :update, :destroy]
# scope for most_recent and subtotal
# find out if rails sorts on update column cuz this is annoying.
def create
# grabbing cart from application controller current_cart method
#cart = current_cart
# session[:cart_id] = #cart.id
# individual product items get added to cart item and added to cart and saved
#cart_item = #cart.cart_items.build(cart_item_params)
#cart.save
end
def update
#cart = current_cart
# finding cart_items by cart_id
#cart_item = #cart.cart_items.find(params[:id])
# #cart_items.order(:id)
#cart_item.update_attributes(cart_item_params)
#cart_items = #cart.cart_items.order(:id)
# redirect 'cart_show_path'
#cart.save
end
def destroy
#cart = current_cart
#cart_item = #cart.cart_items.find(params[:id])
#cart_item.destroy
#cart_items = #cart.cart_items
#cart.save
end
private
def set_cart_item
#cart_item = CartItem.find(params[:id])
end
def cart_item_params
params.require(:cart_item).permit(:cart_id, :product_id, :unit_price, :quantity, :total_price)
end
end
When you say you are transferring the cart_items from current_cart you are passing on the objects, you are not creating new cart_items (which means the database ids are same) and when you do current_cart.cart_items.destroy_all it is deleting them from the database. See ActiveRecord::Relation#destroy_all
For you use case, its enough if you just do
def new
#order.new
#order.cart_items = current_cart.cart_items
#order.save
current_cart.cart_items = []
end
Alright, figured it out.
I was using #order.save which was not processing the error message!!!!!!!!
After #order.save! it gave me the validation error in another model.
I commented that out and it worked.
I iterated current.cart_items and assigned the cart_id to nil and order_id to #order.id essentially clearing the cart and "transferring" the items over.
I couldn't figure out a way using destroy_all though. I think this is impossible like #Sri said.
Thanks alot!
So the end code was this:
#order = Order.create!(users_id: current_cart.users_id)
current_cart.cart_items.each do |item|
item.order_id = #order.id
item.save!
end
if #order.save
current_cart.cart_items.each do |item|
item.cart_id = nil
item.save!
end

How to break down complex controller methods into parts and execute them in the console

I would like to break down a complex controller methods into parts and execute them in the rails console in order to see what do they return and how the flow works.
The following example involves:
cart creation
finding the product
line_item creation
Finding the product in rails console is easy:
2.2.2 :001 > product = Product.find(2)
How to execute this line in the console?
#line_item = #cart.line_items.build(product: product)
It would be better if you can provide me some sources so i can cope with the future scenarios.
Please See The Implementation Details below :
Line_item controller :
def create
product = Product.find(params[:product_id])
#line_item = #cart.line_items.build(product: product)
respond_to do |format|
if #line_item.save
format.html { redirect_to #line_item.cart,
notice: 'Line item was successfully created.' }
else
format.html { render action: 'new' }
end
end
end
cart module concerns
module CurrentCart
extend ActiveSupport::Concern
private
def set_cart
#cart = Cart.find(session[:cart_id])
rescue ActiveRecord::RecordNotFound
#cart = Cart.create
session[:cart_id] = #cart.id
end
end
Cart Controller:
def create
#cart = Cart.new(cart_params)
private
def cart_params
params[:cart]
Associassions:
Cart has_many :line_items
product has_many :line_items
Line_items belongs_to :product
Line_items belong_to :cart

I can't figure out my rails line_items controller create action error?

I am getting this error when trying to add a product to a cart:
ActiveRecord::RecordNotFound in LineItemsController#create
Couldn't find Cart with 'id'=2
So something is wrong with the create action, but I'm not sure what it is... Create action:(the gap between #product and quantity isn't there in my actual code, can't get it formatted right.)
def create
#product = Product.find(params[:product_id])
#line_item = LineItem.create!(:cart => current_cart, :product => #product,
:quantity=> 1, :unit_price => #product.price)
flash[:notice] = "Added #{#product.name} to cart."
redirect_to cart_url(current_cart)
end
current_cart method from application controller:
def current_cart
session[:cart_id] ||= Cart.create!.id
#current_cart ||= Cart.find(session[:cart_id])
end
Thanks for the help.
Your code is failing in the ApplicationController#current_cart method. Do you already have a cart ID in the session? If so, the code will try to find a Cart record with that ID and if it's been deleted, it'll fail with the above message.
So, firstly, figure out what you have in your session. Secondly, figure out how to create a new cart without supplying your application stale or invalid data.
Hope this helps.

Understanding rails active record association

I was reading a book for agile web development and I found this code
application controller
private
def current_cart
Cart.find(session[:cart_id])
rescue ActiveRecord::RecordNotFound
cart = Cart.create
session[:cart_id] = cart.id
cart
end
line items controller
def create
#cart = current_cart
product = Product.find(params[:product_id])
#line_item = #cart.line_items.build(:product => product)
respond_to do |format|
if #line_item.save
format.html { redirect_to(#line_item.cart,
:notice => 'Line item was successfully created.') }
I don't understand the following two lines
#line_item = #cart.line_items.build(:product => product)
format.html { redirect_to(#line_item.cart,
:notice => 'Line item was successfully created.') }
#cart holds the value of cart_id then what #cart.line_items point and what is the use of build method here?
Also what #line_item.cart means here? which action will be called?
Okay, let's start with #cart. As phoet says, it is not a simple integer. It is set by Cart.find(session[:cart_id]), so it is an instance of the Cart model based off a row in the database.
The Cart model presumably inherits from ActiveRecord::Base which has all the logic for has_many (and other) associations (relations) between models. So, I would expect to see at least
def Cart < ActiveRecord::Base
has_many :line_items
end
in the Cart model.
So, when you call #cart.line_items you get something that is conceptually a list of that cart's line items through the association set up by the call to has_many. Calling build on that results in a new (as of yet, unpersisted) instance of the LineItem model that has its cart_id value set to #cart.id.
I hope that clears things up a bit. Rails associations are tricky business.
Your assumption is not correct. #cart holds a Cart object from the current_cart method. It's either new, or the one held in the session.
So #cart.line_items works with the has_many relation of Cart and line_items.build will setup a new LineItem to be saved to the database on #line_tem.save.

How to restrict foreign keys in Rails' update controller action?

In my Rails app I have invoices which in turn can have many projects.
model:
class Invoice < ActiveRecord::Base
attr_accessible :project_id
end
controller:
class InvoicesController < ApplicationController
before_filter :authorized_user, :only => [ :show, :edit, :destroy ]
before_filter :authorized_project, :only => [ :create, :update ]
def create # safe
#invoice = #project.invoices.build(params[:invoice])
if #invoice.save
flash[:success] = "Invoice saved."
redirect_to edit_invoice_path(#invoice)
else
render :new
end
end
def update # not safe yet
if #invoice.update_attributes(params[:invoice])
flash[:success] = "Invoice updated."
redirect_to edit_invoice_path(#invoice)
else
render :edit
end
end
private
def authorized_user
#invoice = Invoice.find(params[:id])
redirect_to root_path unless current_user?(#invoice.user)
end
def authorized_project
#project = Project.find(params[:invoice][:project_id])
redirect_to root_path unless current_user?(#project.user)
end
end
My biggest concern is that a malicious user might, one day, create an invoice that belongs to the project of another user.
Now thanks to the help of some people on this board I managed to come up with a before_filter that makes sure that this won't happen when a project is created.
The problem is I don't understand how to apply this filter to the update action as well.
Since the update action does not make use of Rails' build function, I simply don't know how to get my #project in there.
Can anybody help?
In your case I would start from current_user, not #project (provided User has_many :invoices):
current_user.invoices.build(params[:invoice])
Also instead of explicitly check current_user?(#invoice.user) you can do:
def find_invoice
#invoice = current_user.invoices.find(params[:id])
end
def find_project
#project = current_user.projects.find(params[:invoice][:project_id])
end
Wrong invoice or project will throw 500 which you may or may not want to handle.
If User has_many :invoices, :through => :projects and Project hence has_many :invoices then:
def find_invoice
#invoice = #project.invoices.find(params[:id])
end
The #project.invoices.build method creates a new Invoice that is automatically associated with that particular #project. You don't have to do any work, and there's no risk of it being linked to the wrong project.
You'll want to be sure that project_id is not an accessible attribute, though.

Resources