Rails - ArgumentError in BookingsController#create, Can't multiply a Money by a NilClass's value

Question!

I'm getting the error code above in my Rails app when attempting to implement a payment/reservation process which allows a user to be able to book & pay for spaces on an event. Previously my code would only allow for a user to pay for one event space at a time. My code below is an attempt for a user to be able to pay for any number of spaces they require, subject to availability. This is my model code -

Booking.rb

   class Booking < ActiveRecord::Base

    belongs_to :event
    belongs_to :user

    validates :quantity, presence: true, numericality: { greater_than: 0 }
    validates :total_amount, presence: true, numericality: { greater_than: 0 }
    validates :event, presence: true, numericality: {greater_than_or_equal_to: 0 }

    before_save :set_price_to_zero_if_free

    def set_price_to_zero_if_free
       self.event.price >= 1    unless self.event.is_free
    end

    def reserve(stripe_token)
        # Don't process this booking if it isn't valid
        #if valid?

                # Free events don't need to do anything special
                if event.is_free?
                save!

                # Paid events should charge the customer's card
                else

                    begin
                        self.total_amount = event.price * quantity
                        charge = Stripe::Charge.create(
                            amount: total_amount,
                            currency: "gbp",
                            source: stripe_token, 
                            description: "Booking created for amount #{total_amount}")
                        self.stripe_charge_id = charge.id
                        save!
                    rescue Stripe::CardError => e
                    errors.add(:base, e.message)
                    false
                end
            end 
        #end
    end
end

And controller -

bookings_controller.rb

    class BookingsController < ApplicationController

    before_action :authenticate_user!

    def new
        # booking form
        # I need to find the event that we're making a booking on
        @event = Event.find(params[:event_id])
        # and because the event "has_many :bookings"
        @booking = @event.bookings.new(quantity: params[:quantity])
        # which person is booking the event?
        @booking.user = current_user
        #@booking.quantity = @booking.quantity
        #@total_amount = @booking.quantity.to_f * @event.price.to_f


    end

    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(booking_params['stripe_token'])
                flash[:success] = "Your place on our event has been booked"
                redirect_to event_path(@event)
            else
                flash[:error] = "Booking unsuccessful"
                render "new"
            end
    end


    private

    def booking_params
        params.require(:booking).permit(:stripe_token, :quantity, :event_id, :stripe_charge_id, :total_amount)
    end



end

I'm pretty sure this points to 'quality' having a nil value. What I'm unclear on is why the validation hasn't sorted this out? What else could be causing this and if the validation isn't working, why not?

Here's my Booking table in my schema. Do I need to add a default value here? How do I do the migration to add a default value? -

  create_table "bookings", force: :cascade do |t|
    t.integer  "event_id"
    t.integer  "user_id"
    t.string   "stripe_token"
    t.datetime "created_at",       null: false
    t.datetime "updated_at",       null: false
    t.integer  "quantity"
    t.integer  "total_amount"
    t.string   "stripe_charge_id"
  end


Answers

In BookingsController#create, on you call @event.bookings.new(bookings_params). This creates a new Booking object in memory, but it doesn't yet create a Booking record in the database. It's always perfectly valid to call Model.new() with no arguments, even if you have plenty of validations; you'll just receive a model object that isn't valid. You can then do what you like with it, such as calling methods on it, setting attributes, checking to see if it is valid (with the valid? method), etc. As long as you've made it valid before you call save (or save!), everything is fine as far as the model object is concerned.

So when, a few lines later, you call @booking.reserve(booking_params['stripe_token']), which in turn calls event.price * quantity, you have no guarantee that quantity is not nil (or even "apple") because you haven't performed validations yet.

There are a couple of ways you can work through this; the simplest would just be to check self.valid? from within Booking#reserve, before you do anything else with it.

By : philomory


Trying to handle an Error thrown from a thread seems to be a suspicious use case...

From Java Doc : "An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch. Most such errors are abnormal conditions. The ThreadDeath error, though a "normal" condition, is also a subclass of Error because most applications should not try to catch it."

The only time I needed to handle Error is when I use third parties that badly manage their exceptions, that throw Error rather than RuntimeException. In this specific cases, you need to catch the Error (or the Throwable) to avoid unexpected application crash.



A Future is an object that represents the result of running your operation, whether it be ready now or at some point in the future. Generally, one will call the get method on said future in order to get a result, particularly if you are passing a Callable rather than a Runnable into your ExecutorService.

If your Runnable/Callable throws an exception, this will be reflected in the Future object. You will be able to test whether the Runnable ran successfully by calling the get method. If it was a clean run, you will get (in this case) null back. If an exception was thrown, then get will throw an ExecutionException, with that exception marked as its cause.

Think of it like a coat check at a music venue. If they lose your coat, they probably won't tell you until you come with your ticket asking for your coat. The Future serves the purpose of your ticket here.

By : Joe C


This video can help you solving your question :)
By: admin