How find the distance between two objects? - ruby-on-rails

Im using geocode. The idea is our partners can post products with an address. When they do so it fetches the latitude and longitude. Now when our customers go to buy that product they have to enter in a delivery address to tell us where to deliver the product. However if they delivery address is not within 20 miles of the product they are not allowed to get the product delivered.
Im getting an error message saying this "undefined method `latitude' for nil:NilClass"
Like I said the product.longitude, product.latitude is already set when the users are trying to order.
Not sure if it's because the order.delivery_address(lat, long) is not submitted into the database yet and its trying to check the distance. Here my code below
So My question is how can is how can i find the distance between the product address and order address and I want to show a alert message to the user if the distance between the two is over 20 miles.
def create
product = Product.find(params[:product_id])
if current_user == product.user
flash[:alert] = "You cannot purchase your own property"
elsif current_user.stripe_id.blank? || current_user.phone_number.blank?
flash[:alert] = " Please update your payment method and verify phone number please"
return redirect_to payment_method_path
elsif Geocoder::Calculations.distance_between([product.latitude, product.longitude], [#order.latitude, #order.longitude]) < 20
flash[:alert] = "The delivery address you provided is outside the delivery zone. Please choose a different product."
else
quantity = order_params[:quantity].to_i
#order = current_user.orders.build(order_params)
#order.product = product
#order.price = product.price
#order.total = product.price * quantity + product.delivery_price
# #order.save
if #order.Waiting!
if product.Request?
flash[:notice] = "Request sent successfully... Sit back and relax while our licensed dispensary fulfil your order :)"
else
#order.Approved!
flash[:notice] = "Your order is being filled and it will delivered shortly:)"
end
else
flash[:alert] = "Our licensed dispensary cannot fulfil your order at this time :( "
end
end
redirect_to product
end

You set #order in the following line:
#order = current_user.orders.build(order_params)
But you try to call its longitude and latitude methods above this, before you even set #order variable. To simply fix this problem, you can move this line up, it can even be located at the beginning of create method, since it doesn't depend on product or anything like that:
def create
#order = current_user.orders.build(order_params)
# ...
end
Although, there are number of problems in your code, like method names starting with capital letters (you can do it, but you shouldn't, it's against the convention) or overall complexity of the method.

You should move the business logic to the model where it belongs.
So lets start by creating a validation for the product distance:
class Order < ApplicationRecord
validates :product_is_within_range,
if: -> { product.present? } # prevents nil errors
# our custom validation method
def product_is_within_range
errors.add(:base, "The delivery address you provided is outside the delivery zone. Please choose a different product.") if product_distance < 20
end
def product_distance
Geocoder::Calculations.distance_between(product.coordinates, self.coordinates)
end
end
Then move the calculation of the total into the model:
class Order < ApplicationRecord
before_validation :calculate_total!, if: ->{ product && total.nil? }
def calculate_total!
self.total = product.price * self.quantity + product.delivery_price
end
end
But then you still have to deal with the fact that the controller is very broken. For example:
if current_user == product.user
flash[:alert] = "You cannot purchase your own property"
Should cause the method to bail. You´re not actually saving the record either. I would start over. Write failing tests for the different possible conditions (invalid parameters, valid parameters, user is owner etc) then write your controller code. Make sure you test each and every code branch.

Related

ActiveRecord pessimistic locking a record for reservation system

I have a reservation system where I need to lock a record so that two users cannot book it at the same time.
The model Seat has three statuses: available, reserved and booked.
While being reserved the user has 5 minutes to complete the booking.
I can do this with a pessimistic locking. I add a lock_version column to the table and do
def reservation
#seat = Seat.available.order(created_at: :desc).first
if #seat
#seat.reserved!
redirect_to 'continue to confirm booking'
else
redirect_back with message "no seats available"
end
rescue ActiveRecord::StaleObjectError
retry
end
end
Since in this system the possibility of conflicts is very likely, I'd like to use a pessimistic locking intead of an optimistic one, but I didn't succeed.
This is the code I tried (note the addition of lock):
def reservation
#seat = Seat.available.order(created_at: :desc).lock.first # executes a SELECT FOR UPDATE
if #seat
#seat.reserved!
redirect_to 'continue to confirm booking'
else
redirect_back with message "no seats available"
end
end
The problem is that two records are still being selected at the same time even though the query is
SELECT * from seats where status = 'available' order by created_at desc limit 1 for update.
I am not sure how to implement an optimistic locking for such case.
After some hours of research I found out what was the issue.
Looking again at the optimistic code, the issue is that there's no transaction defined.
Adding a transaction fixes the problem. Here is the corrected code:
def reservation
Seat.transaction do
#seat = Seat.available.order(created_at: :desc).lock.first
if #seat
#seat.reserved!
redirect_to 'continue to confirm booking'
else
redirect_back with message "no seats available"
end
end
end
now the select for update and update of the record itself are within the same transaction.

How to refactor this code to make it more readable and efficient?

I need help refactoring the code.I ve tried by best landed with the following code. Is there anything That I can do
class OrdersController < ApplicationController
before_action :get_cart
before_action :set_credit_details, only: [:create]
# process order
def create
#order = Order.new(order_params)
# Add items from cart to order's ordered_items association
#cart.ordered_items.each do |item|
#order.ordered_items << item
end
# Add shipping and tax to order total
#order.total = case params[:order][:shipping_method]
when 'ground'
(#order.taxed_total).round(2)
when 'two-day'
#order.taxed_total + (15.75).round(2)
when "overnight"
#order.taxed_total + (25).round(2)
end
# Process credit card
# Check if card is valid
if #credit_card.valid?
billing_address = {
name: "#{params[:billing_first_name]} # .
{params[:billing_last_name]}",
address1: params[:billing_address_line_1],
city: params[:billing_city], state: params[:billing_state],
country: 'US',zip: params[:billing_zip],
phone: params[:billing_phone]
}
options = { address: {}, billing_address: billing_address }
# Make the purchase through ActiveMerchant
charge_amount = (#order.total.to_f * 100).to_i
response = ActiveMerchant::Billing::AuthorizeNetGateway.new(
login: ENV["AUTHORIZE_LOGIN"],
password: ENV["AUTHORIZE_PASSWORD"]
).purchase(charge_amount, #credit_card, options)
unless response.success?
#order.errors.add(:error, "We couldn't process your credit
card")
end
else
#order.errors.add(:error, "Your credit card seems to be invalid")
flash[:error] = "There was a problem processing your order. Please try again."
render :new && return
end
#order.order_status = 'processed'
if #order.save
# get rid of cart
Cart.destroy(session[:cart_id])
# send order confirmation email
OrderMailer.order_confirmation(order_params[:billing_email], session[:order_id]).deliver
flash[:success] = "You successfully ordered!"
redirect_to confirmation_orders_path
else
flash[:error] = "There was a problem processing your order. Please try again."
render :new
end
end
private
def order_params
params.require(:order).permit!
end
def get_cart
#cart = Cart.find(session[:cart_id])
rescue ActiveRecord::RecordNotFound
end
def set_credit_details
# Get credit card object from ActiveMerchant
#credit_card = ActiveMerchant::Billing::CreditCard.new(
number: params[:card_info][:card_number],
month: params[:card_info][:card_expiration_month],
year: params[:card_info][:card_expiration_year],
verification_value: params[:card_info][:cvv],
first_name: params[:card_info][:card_first_name],
last_name: params[:card_info][:card_last_name],
type: get_card_type # Get the card type
)
end
def get_card_type
length, number = params[:card_info][:card_number].size, params[:card_info][:card_number]
case
when length == 15 && number =~ /^(34|37)/
"AMEX"
when length == 16 && number =~ /^6011/
"Discover"
when length == 16 && number =~ /^5[1-5]/
"MasterCard"
when (length == 13 || length == 16) && number =~ /^4/
"Visa"
else
"Unknown"
end
end
end
Products with a price attribute. We have shopping Carts that have many Products through the OrderedItems join table. An OrderedItem belongs_to a Cart and a Product. It has a quantity attribute to keep track of the number of products ordered.
The OrderedItem also belongs_to an Order
I wanted to know if it can be refactored further.
First of all you should move all that business logic from the controller into models and services (OrderProcessService, PaymentService). All the controller's private methods belong to a PaymentService.
Split the code into smaller methods.
If doing that on the model level some things that come into my mind when reading your code are the following:
#order.add_items_from_cart(#cart)
#order.add_shipping_and_tax(shipping_method)
Orders should be first saved (persisted in DB), then processed (purchased with changing their status).
#order.save might fail after a successful payment, so a client will lose the money and not get their order.
the purchasing is an important and critical process, so you should make sure everything is ready for it (the order is valid and saved)
a client should be able to purchase later or after the payment page is accidentally reloaded without filling the form again
normally when a payment is performed you should send an order ID to the payment system. The payment system will store the ID and you will always know which order the payment belongs to.
There are a lot of other things to consider. You have a lot of work to do.

Rails - how to set default values in a model for quantity

I'm trying to allow a User to book events for more than one space at a time, so if one space at an event costs £10 and a User wants to book four spaces then they would need to pay £40.
I've implemented a method in my Booking model to cater for this -
Booking.rb
class Booking < ActiveRecord::Base
belongs_to :event
belongs_to :user
def reserve
# Don't process this booking if it isn't valid
return unless valid?
# We can always set this, even for free events because their price will be 0.
self.total_amount = quantity * event.price_pennies
# Free events don't need to do anything special
if event.is_free?
save
# Paid events should charge the customer's card
else
begin
charge = Stripe::Charge.create(amount: total_amount, currency: "gbp", card: #booking.stripe_token, description: "Booking number #{#booking.id}", items: [{quantity: #booking.quantity}])
self.stripe_charge_id = charge.id
save
rescue Stripe::CardError => e
errors.add(:base, e.message)
false
end
end
end
end
When I try to process a booking I get the following error -
NoMethodError in BookingsController#create
undefined method `*' for nil:NilClass
This line of code is being highlighted -
self.total_amount = quantity * event.price_pennies
I need to check/make sure that quantity returns a value of 1 or more and event.price_pennies returns 0 if it is a free event and greater than 0 if it is a paid event. How do I do this?
I did not set any default values for quantity in my migrations. My schema.rb file shows this for price_pennies -
t.integer "price_pennies", default: 0, null: false
This is whats in my controller for create -
bookings_controller.rb
def create
# actually process the booking
#event = Event.find(params[:event_id])
#booking = #event.bookings.new(booking_params)
#booking.user = current_user
if #booking.reserve
flash[:success] = "Your place on our event has been booked"
redirect_to event_path(#event)
else
flash[:error] = "Booking unsuccessful"
render "new"
end
end
So, do I need a method in my booking model to rectify this or should I do a validation for quantity and a before_save callback for event?
I'm not quite sure how to do this so any assistance would be appreciated.
Just cast to integer, in this case you seem to be done:
self.total_amount = quantity.to_i * event.price_pennies.to_i
Migrations are used to modify the structure of you DB and not the data.
In your case I think you need to seed the DB with default values, and for that purpose you use 'db/seeds.rb' file which is invoked once every time your application is deployed.
You would do something like this in seeds.rb
Booking.find_or_create_by_name('my_booking', quantity:1)
So when the application is deployed the above line of code is executed. If 'my_booking' exists in the table then nothing happens, else it will create a new record with name='my_booking' and quantity=1.
In your localhost you'll execute 'rake db:seed' to seed the DB.

How to retrieve sixth record from database in rails using Where query

I want to retrieve sixth, seventh and eighth record from database using ruby on rails, but it's possible only till fifth, after that undefined method sixth is coming. Please suggest me if there is any possible way.
Following is my code which I tried:
#reporting_masters_travel_requests4 = ReportingMastersTravelRequest.where(travel_request_id: #travel_request.id,status: nil).fifth
if #reporting_masters_travel_requests2 = ReportingMastersTravelRequest.where(travel_request_id: #travel_request.id,status: nil)[1]
ReportingMastersTravelRequest.where(reporting_master_id: #reporting_masters_travel_requests2.reporting_master_id).update_all(status: "true",daily_bill_comment: #comment)
TravelRequest.where(id: #travel_request.id).update_all(reporting_master_id: #reporting_masters_travel_requests3.reporting_master_id)
flash[:notice] = 'Daily Bill Request Send To Higher Authority For Approval'
elsif #reporting_masters_travel_requests3 = ReportingMastersTravelRequest.where(travel_request_id: #travel_request.id,status: nil)[2]
ReportingMastersTravelRequest.where(reporting_master_id: #reporting_masters_travel_requests3.reporting_master_id).update_all(status: "true",daily_bill_comment: #comment)
TravelRequest.where(id: #travel_request.id).update_all(reporting_master_id: #reporting_masters_travel_requests4.reporting_master_id)
flash[:notice] = 'Daily Bill Request Send To Higher Authority For Approval'
elsif #reporting_masters_travel_requests4 = ReportingMastersTravelRequest.where(travel_request_id: #travel_request.id,status: nil)[3]
ReportingMastersTravelRequest.where(reporting_master_id: #reporting_masters_travel_requests4.reporting_master_id).update_all(status: "true",daily_bill_comment: #comment)
TravelRequest.where(id: #travel_request.id).update_all(reporting_master_id: #reporting_masters_travel_requests5.reporting_master_id)
flash[:notice] = 'Daily Bill Request Send To Higher Authority For Approval'
else
flash[:alert] = 'No Reporting Manager is present'
end
I have used the above code and it works perfectly,but its not so dynamic as i manually need to specify for how many time if want to check in table via array [4].But problem is what if 10 records are present in database then for this the above logic will fail.
You are using where to get records from database and it returns array of objects so you can use something like this:
#reporting_masters_travel_requests4 = ReportingMastersTravelRequest.where(travel_request_id: #travel_request.id,status: nil)[5]
You can do as follows:
#reporting_masters_travel_requests = ReportingMastersTravelRequest.where(travel_request_id: #travel_request.id,status: nil)
#reporting_masters_travel_requests.each do |record|
#Something like record.update record.save
end
#set a msg like no more records after loop
msg="No more records"

Calculating and inserting value into table column using rails

Hi I'm a newbie to Rails. So pardon if this is a silly question.
I'm working on a project where I need to calculate the Bank Balance and Cashbox balance in a Transaction. So this depends upon the type of transaction "Debit/Credit" and type of payment "Online Payment/Cheque/Cash". There is only one model involved and that is Transaction Model and transactions table. So this is what I'm doing,
transactions_controller
def create
#transaction = Transaction.create(transaction_params)
#amount = transaction_params[:amount].to_f
#cashbox = transaction_params[:cashbox].to_f
#bank = transaction_params[:bank].to_f
if transaction_params[:t_type] == "Debit"
if transaction_params[:t_method] == "Cash"
#cashbox -= #amount
transaction_params[:cashbox] = #cashbox.to_s
else
#bank -= #amount
transaction_params[:bank] = #bank.to_s
end
elsif transaction_params[:t_type] == "Credit"
if transaction_params[:t_method] == "Cash"
#cashbox += #amount
transaction_params[:cashbox] = #cashbox.to_s
else
#bank += #amount
transaction_params[:bank] = #bank.to_s
end
end
if #transaction.save
redirect_to #transaction
else
render 'new'
end
end
def transaction_params
params.require(:transaction).permit(:date, :notes, :t_type, :t_method, :amount, :paid_by, :paid_to, :cashbox, :bank, :house_id, :expense_for)
end`
Transaction Model
class Transaction < ActiveRecord::Base
end
But when I submit the form containing all the values, the calculation part does't happen and rails inserts only the values submitted in the form.
How to calculate cashbox balance and bank balance on submit from the form and store the updated values in the table?
Also, kindly suggest if there are any better ways to do this.
You need to put this functionality to the model and update object before rendering (if i understood you right). Now you never save the changes into the object.
Your logic is confuse, let read more resful of rails.
- First: you can't save, after that create Transaction again.
- Second: you created transaction and below don't see updating that transaction should of course it be not changed.
Let try yourself:
def create
#amount = transaction_params[:amount].to_f
#cashbox = transaction_params[:cashbox].to_f
#bank = transaction_params[:bank].to_f
if transaction_params[:t_type] == "Debit"
if transaction_params[:t_method] == "Cash"
#cashbox -= #amount
transaction_params[:cashbox] = #cashbox.to_s
else
#bank -= #amount
transaction_params[:bank] = #bank.to_s
end
elsif transaction_params[:t_type] == "Credit"
if transaction_params[:t_method] == "Cash"
#cashbox += #amount
transaction_params[:cashbox] = #cashbox.to_s
else
#bank += #amount
transaction_params[:bank] = #bank.to_s
end
end
#transaction = Transaction.new transaction_params
if #transaction.save
redirect_to #transaction
else
render 'new'
end
end
def transaction_params
params.require(:transaction).permit(:date, :notes, :t_type, :t_method, :amount, :paid_by, :paid_to, :cashbox, :bank, :house_id, :expense_for)
end`
You've got a few things in a bit of a muddle I think:
1) if #transaction.save
Here you're trying to save the transaction and only continuing if you do so successfully. Where is #transaction getting set up though? Presumably in some kind of before_filter but I suspect you don't need that in this case.
2) #transaction = Transaction.create(transaction_params)
Now you're creating, and saving to the database, a new Transaction object base on the parameters that are being submitted from the form (but only if it passes validation).
3) Then you do a load of work to calculate some parameters that appear to be transaction parameters. At this point though you've already created the transaction object with the parameters that were submitted. You add all these to the params hash which is predominantly used to pass web request parameters in (yes, you can use it for other things but I'd avoid doing that just yet).
Now, the most simple fix is probably to move your Transaction.create line to after all your params changes because then you'll be creating a transaction with the parameters you want. I wouldn't advocate that though. It seems like this code would be better moved to the Transaction model and have a method to apply the values to a transaction after you've built it.
(I'd also question if you've got your modelling quite right. You seem to be passing in a #bank value as part of the web request and then applying the value of the transaction your creating to it to give your bank value on the transaction. Of course, I'm not sure of your exact requirements but this seems like a potentially problematic approach. One for a different question perhaps).

Resources