Rails update product quantity - ruby-on-rails

I followed Ryan Bates screencasts using paypal standard payments and i basically now have to send checkout info from the Cart model.
I'm a bit stuck with trying to update product quantity after the transaction is completed.
I tried using a callback but to no avail. Any help would be appreciated
The closest i got was to use this update quantity callback but for some reason, it is updating the wrong cart. Not sure whether it picks up the wrong line item or its when it checks the cart it goes wrong
class PaymentNotification < ActiveRecord::Base
belongs_to :cart
serialize :params
after_create :mark_cart_as_purchased, :update_quantity
private
def mark_cart_as_purchased
if status == "Completed"
cart.update_attribute(:purchased_at, Time.now)
end
end
def update_quantity
#line_item = LineItem.find(params[:id])
#line_item.upd
end
end
Line Item Class
class LineItem < ActiveRecord::Base
belongs_to :order
belongs_to :product
belongs_to :cart
belongs_to :stock
after_create :stock_stat
def total_price
product.price * quantity
end
def upd
if cart.purchased_at
product.decrement!(quantity: params[:quantity])
end
end
end

The params hash is only available in the controller. You cannot access it in the model. You have to pass params[:quantity] to the upd method as a method parameter as such:
def update_quantity
#line_item = LineItem.find(params[:id])
#line_item.upd(params[:quantity])
end
def upd(quantity)
if cart.purchased_at
product.decrement!(quantity: quantity)
end
end
Also, you should consider using Time.current instead of Time.now in order to account for the time zone your application is configured with in application.rb unless you just want to use whatever time is local.

Related

Rails Where vs Join vs Union

Having trouble with my activerecord searches. I thought I had my models setup correctly, but I’m poor with my joins (not sure if a join or union is the correct way to go? It shouldn’t be this difficult).
I have guides creating bids on trips that have start_dates. I want to create a list of bids that have expired (ie. the start date is in the past). Guides can also have LosingBids if a bid has been declined
In a perfect world I would have one resultset that includes both losing bids and expired bids for that guide, but I’m find with 2 different result sets. Unfortunately I can’t get any of the “expired bids” to work. Results/errors in the comments of the code.
class GuidesController < ApplicationController
def expired_declined
#this declined_bids call works
#declined_bids = LosingBid.where("guide_id = ?", current_guide.id.to_s)
#this expired_bids call returns Trips, not Bids
#expired_bids = Bid.where("guide_id = ?", current_guide.id.to_s).expired
#this expired_bids call gives me the following error:
#SQLite3::SQLException: no such column: trips.start_date: SELECT 1 AS one FROM #”bids" WHERE (guide_id = '1') AND (trips.start_date < '2018-05-30') LIMIT ?
#expired_bids = Bid.where("guide_id = ?", current_guide.id.to_s).where("trips.start_date < ?", Date.today)
end
end
class Guide < ApplicationRecord
has_many :bids
has_many :losing_bids
end
class Trip < ApplicationRecord
has_many :bids
end
class Bid < ApplicationRecord
belongs_to :trip
belongs_to :guide
def self.expired
Trip.where("start_date <= ?", Date.today) #.where("guide_id = ?", current_guide.id.to_s)
end
end
class LosingBid < ApplicationRecord
belongs_to :trip
belongs_to :guide
end
Trip.where("start_date <= ?", Date.today).bids will return you the expired bids.
You should move the expired scope in the Trip, rather than on the Bid.
If you want a scope on Bid you can define.
class Bid
scope :expired, -> { joins(:trip).where('trips.start_date <= ?', Date.current) }
end
I would really question if you need to have a separate LosingBid model or if its just creating duplication and unnecessary complexity. Instead just add an enum column to bids which contains the status:
class Bid
enum status: [:pending, :declined, :expired, :accepted]
end
This is just a simple integer column that acts as a bit mask.
This will simply let you query by:
Bid.pending
Bid.expired
Bid.where(status: [:pending, :accepted])
Bid.where.not(status: :accepted)
You can simply reject a bid by:
class BidsController
# PATCH /bids/decline
def reject
#bid.declined!
redirect_to bid, notice: 'Bid declined'
end
end
You could then setup scheduled task which runs once per day to automatically expire tasks (example with the whenever gem):
every 1.days do
runner "BidExpiryService.perform"
end
# app/services/bid_expiry_service.rb
module BidExpiryService
def self.perform
bids = Bid.pending
.joins(:trip)
.where('trips.start_date <= ?', Date.current)
bids.update_all(status: Bid.statuses[:expired])
# #todo notify guides that bid has expired
end
end

Rails :: in nested model form, update belongs_to "parent" attributes

I am building a Rails application and I am in front of an issue with form I can't fix.
I have an order model which belongs_to customer so when I build the form I do #order = #customer.orders.build
This works well to save orders attributes but I also want to update customer attributes as well which never work.
How can I save order and update "parent" customer attributes in the same process ?
Thanks for your help !
Edit:
Customer model:
class Customer < ActiveRecord::Base
has_many :orders
end
Order model:
class Order < ActiveRecord::Base
belongs_to :customer
accepts_nested_attributes_for :customer
end
My project:
Ruby On Rails 4.2.6 / Ruby 2.2.2
Devise 3.5.9
Simple form 3.1.0
You should update your customer in your create method. How about:
def create
#order = current_customer.orders.build order_params
if #order.save
#order.customer.update_attributes(order_params[:customer_attributes])
...
else
...
end
end
In your update action, you can do something like this:
def update
#customer = Customer.find(params[:id])
if #customer.update_attributes(customer_params.merge({ order_attributes: order_params}))
render #customer
end
end
Where customer_params and order_params are methods that use strong params to whitelist parameters sent by the form.
Obviously, I haven't tested the code, but ti should give you some direction.
Hope that helps!

Use controller action to retrieve data from one model and save data to another model - Rails 4

CartItem Controller and CartItem model handle adding items to CartItems table. Cart view is rendered by calling CartItem model and controller. When clicking ‘Checkout’ I would like to transfer all items saved for a given card in the CartItem table to the OrderItem table.
When clicking ‘Checkout’ on the cart view page the following things should happen:
Call Orders controller from Cart view
<%= button_to "Checkout", controller: 'orders', action: 'create' %>
Orders Controller
def create
#order = Order.new
if logged_in?
#order[:user_id] = current_user.id #This works
end
#Need help: call Order_Items_Controller create action
if #order.save
flash[:success] = "Order created"
else
flash[:danger] = "Failed"
end
redirect_to checkout_address_path
end
OrderItems Controller
def create
#retrieve all records from Cart_Items where cart_id matches cookies[:cart_id]
#order_item = #order.order_items.build!(CartItem.where(:cart_id => cookies[:cart_id]))
#Need help: add order_id for current order to each record
#Need help: save the cart_items to the order_items table and handle exceptions
end
Orders Model
class Order < ActiveRecord::Base
has_many :order_items
end
OrderItems Model
class OrderItem < ActiveRecord::Base
belongs_to :order
validates :order_id, presence: true
end
Why do I want to do this? Once an order is successfully processed the corresponding CartItems data will be deleted, so that the card is empty. OrderItems stores the data for completed orders, so that shoppers can view order history online.
Have you thought about the setup of your models/data structure?
Specifically, duplicating data in one model to another is highly inefficient. You'll be MUCH better using a foreign_key to link your Order model to your cart:
#app/models/cart.rb
class Cart < ActiveRecord::Base
#stores cart items for processing
belongs_to :order
has_many :cart_items
end
#app/models/order.rb
class Order < ActiveRecord::Base
#stores final values (IE delivery service id, cart id, payment id etc)
belongs_to :user
belongs_to :delivery
belongs_to :payment
has_one :cart
end
This way, you'll be able to call the following:
#app/controllers/carts_controller.rb
class CartsController < ApplicationController
def checkout
#cart = current_user.cart #-> or however you call it
#order = #cart.order.new order_params
if #order.save
#-> remove cart from session etc etc
end
end
private
def order_params
params.require(:order).permit(:delivery_id, :user_id, :payment_id)
end
end
Obviously very high-level, but quite descriptive -- an Order becomes a glorified join model, allowing you to compile a "cart" with the relative quantities & items, whilst using the Order to manage the delivery and payments etc.

Rails 4 how to make enrolment to a course for users (controller, view, model)

I am currently trying to build a simple learning app with rails and I am facing problems. When a user signs up to a course i try to make a viewed_course record for them so that I can display all the viewed_course on their profile page.
I can't figure out how to set my different controllers so that it creates the viewed_record when I am on the Course Controller /index.
How can I modify the course/index view so that it creates a new viewed record for the current signed in user.
I am using devise.
class Course
has_many :lessons
end
class Lesson
#fields: course_id
belongs_to :course
end
class User
has_many :viewed_lessons
has_many :viewed_courses
end
class ViewedLesson
#fields: user_id, lesson_id, completed(boolean)
belongs_to :user
belongs_to :lesson
end
class ViewedCourse
#fields: user_id, course_id, completed(boolean)
belongs_to :user
belongs_to :course
end
Thank you so much in advance for helping me out!
Something like this should do it, this is in courses controller show action (index action should be the list of all courses, show action should be viewing an individual course):
class CoursesController < ApplicationController
def index
#courses = Course.all
end
def show
#course = Course.find(params[:id])
current_user.viewed_courses.create(course: #course)
end
end
One gotcha is that this would create a new viewed course every time they viewed the page, you would need some logic to only add each course once.
Perhaps something like this:
def show
#course = Course.find(params[:id])
current_user.viewed_courses.create(course: #course) unless ViewedCourse.exists?(user_id: current_user.id, course_id: #course.id)
end

How to perform calculations on nested form items?

I am fairly new to Rails and I have these two models...
class Invoice < ActiveRecord::Base
has_many :items
accepts_nested_attributes_for :items
...
end
class Item < ActiveRecord::Base
belongs_to :invoice
def self.total
price * quantity
end
...
end
... and a nested (!) form that creates new invoice records and their associated items.
However, I find it very difficult to perform calculations on these items. For example next to each item I would like to put the total for this item using the total method above.
Unfortunately, it's not working. In my form I put this next to each item:
<%= #invoice.items.amount %>
which is derived from my controller:
def new
#invoice = Invoice.new
3.times {#invoice.items.build}
end
It keeps throwing an error saying undefined local variable or method price
What am I missing here??
Thanks for any help.
You have created a class method on Item, when I think what you want is an instance method.
class Item < ActiveRecord::Base
belongs_to :invoice
def total
price * quantity
end
...
end
which you can call on an individual item #item.total or, if you do you the total of all the items, then you'd need to do something like this:
class Item < ActiveRecord::Base
belongs_to :invoice
def self.total
all.collect { |item| item.price * item.quantity }
end
...
end
#invoice.items.total
Hope that helps.

Resources