Mike Whitehead

Joined

1,040 Experience
0 Lessons Completed
1 Question Solved

Activity

I'm building an Events site using RoR which I've just upgraded to v5.0.1 (not moving to 5.1 just yet). My events show page has a comments section at the foot of the page which, so far, only has a create and delete functionality.
I want to add an Edit/Update function to comments so only the user who created the comment can edit the comment if they see fit. I want them to be able to edit their comment whilst on the same page. I don't want to use best_in_place gem as this appears to be on the verge of being dormant having not been updated for 3 years.

I'm trying to implement these changes using remote: true & Ajax but I've never done this before and there doesn't appear to be a clear guide via the internet/SO. Here's my views & controller code -

_comment.html.erb

<% if user_signed_in?  %>
         <p><%= link_to "Edit", remote: true %></p>
                <p><%= link_to 'Delete', [comment.event, comment],
                                method: :delete,
                                class: "button",
                                data: { confirm: 'Are you sure?' } %></p>
         <% end %>

*Comments_controller.rb *

def update
    @comment.user = current_user

    respond_to do |format|
        if @comment.update
            format.html { redirect_to @comment, notice: 'Comment was successfully updated.' }
            format.js   { }
            format.json { render :show, status: :created, location: @comment }
        else
            format.html { render :new }
            format.json { render json: @comment.errors, status: :unprocessable_entity }
        end
    end
end

My understanding is that I need to include a .js.erb file in my views/comments folder to deal with this ( edit.js.erb ?) but what I'm not clear on is exactly what javascript code I need to include in order for this to work. Also, I don't think my controller code above seems right - should this go in the Edit action? Do I also need an Event#show action in my events controller as this is where it sits in the views?

Any assistance would be greatly appreciated.

I need to create a system for my Events site that monitors the number of bookings being taken to ensure an Event does not become over-subscribed. What is the most effective (DRY) way to do this?

I've tried this in my controller but it didn't work -

  if @event.bookings.count >= @event.number_of_spaces
    flash[:warning] = "Sorry, this event is fully booked."
    redirect_to root_path


end

I also tried this transaction code, also in my controller, which didn't work either ( well, it sort of worked as the flash message did come up however it came up AFTER a payment was taken which is not good) -

  Booking.transaction do
    @booking.save!
       @event.reload
           if @event.bookings.count > @event.number_of_spaces
            flash[:warning] = "Sorry, this event is fully booked."
         raise ActiveRecord::Rollback, "event is fully booked"
    end 
 end

Is it more effective to do this in the Model? On my index page I have each event listed with a main image, event title and date. I also want to include a tracking message illustrating how many spaces are left e.g "50 spaces left - book now". So, before I can do that, I have to sort this out. Any assistance would be appreciated.

Finally (finally!!!!) I've got to the bottom of this. After a bit of re-jigging the key issue was that when I was updated the price with javascript in the view it was purely updating the 'text' rather than the actual server/database. So 'quantity' wasn't being passed through and, hence, only one amount was being collected. This is the key section in the view now -

  <div class="calculate-total">
                          <p>
                              Confirm number of spaces you wish to book here:
                                <input type="number" placeholder="1" name="booking[quantity]"  min="1" value="1" class="num-spaces">
                          </p>
                            <p>
                                Total Amount
                                £<span class="total" data-unit-cost="<%= @event.price %>">0</span>
                            </p>
                      </div>

They key part here is name="booking[quantity]" so the appropriate parameter is named.

Here's my current code in the Booking model -

  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 :quantity, :total_amount, presence: true


def reserve
    # Don't process this booking if it isn't valid
    self.valid?

    # We can always set this, even for free events because their price will be 0.
    #self.total_amount = booking.quantity * event.price

            # 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_pennies * self.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

Not sure if there's a system for marking a question as correct on here but this now works. Thanks again for all your assistance. This has set me back a long way but finally I can move forward.

Thanks so much for your help on this Chris. I'm really sorry its dragged out. I'll figure this out one way or the other and when I do I'll let you know on here what the final code is:)

By the way, I've only had chance to watch one of your screencasts but its great. Really clear and super easy to follow. Looking forward to watching/learning lots more.

It just seems to be bouncing around all over the place. i spotted one error earlier today but now I'm getting 'missing required param :amount'. I'm following your Stripe implementation video (haven't finished it yet) but its for subscriptions not charges so there may be some fundamental differences. There's something key here that I'm missing with Stripe. When you move code like this from a controller (where it worked fine in terms of processing a payment - except for taking multiple reservations) over to model, what are the key take overs I could be missing?

Still battling with this - one last thought before I go right back to square one and rebuild the whole payment structure - does the API version have any affect here? There's been an update recently, does this need me to re-hash my API keys or anything?

Great minds etc - just literally tried commenting out that line and now getting errors again so IT WAS this line -

      return unless valid?

Had to comment out the total_amount method because that wasn't working ( I've never been able to get that to work which is frustrating as hell) and now I need to provide a source or customer for my stripe code block. I assumed it was this -
:source (params[:stripeToken])

But no, so I'll have to figure this out but at least it's moving again. Not sure why its getting blocked at that first line, why would the booking not be valid? Never had an issue with that before. At least it's moving again!!!

I've had a go at debugging but nothing springing up as yet. My theory is that @booking.reserve is simply not being called at all, hence why the code is jumping straight to the error message on the if/else statement in the controller. Does there need to be a reference of some kind in my views? Here's the booking.new.html.erb code (is it as simple as a naming issue, should this be booking.create rather than booking.new or does the booking.reserve method need to be referenced in the new action in the controller? -

 <div class="col-md-6 col-md-offset-3" id="eventshow">
  <div class="row">
      <div class="panel panel-default">
          <div class="panel-heading">
                 <h2>Confirm Your Booking</h2>
         </div>
              <div class="calculate-total">
                          <p>
                              Confirm number of spaces you wish to book here:
                                <input type="number" placeholder="1"  min="1" value="1" class="num-spaces">
                          </p>
                            <p>
                                Total Amount
                                £<span class="total" data-unit-cost="<%= @event.price %>">0</span>
                            </p>
                      </div>





            <%= simple_form_for [@event, @booking], id: "new_booking" do |form| %>



             <span class="payment-errors"></span>

            <div class="form-row">
                <label>
                  <span>Card Number</span>
                  <input type="text" size="20" data-stripe="number"/>
                </label>
            </div>

            <div class="form-row">
              <label>
              <span>CVC</span>
              <input type="text" size="4" data-stripe="cvc"/>
              </label>
            </div>

            <div class="form-row">
                <label>
                    <span>Expiration (MM/YYYY)</span>
                    <input type="text" size="2" data-stripe="exp-month"/>
                </label>
                <span> / </span>
                <input type="text" size="4" data-stripe="exp-year"/>
            </div>
        </div>
        <div class="panel-footer">    

           <%= form.button :submit %>


             </div> 

   <% end %>
    <% end %>

         </div>
     </div>
   </div>  

    <script type="text/javascript">
        $('.calculate-total input').on('keyup change', calculateBookingPrice);

   function calculateBookingPrice() {
     var unitCost = parseFloat($('.calculate-total .total').data('unit-cost')),
         numSpaces = parseInt($('.calculate-total .num-spaces').val()),
         total = (numSpaces * unitCost).toFixed(2);

     if (isNaN(total)) {
       total = 0;
     }

     $('.calculate-total span.total').text(total);
   }

     $(document).ready(calculateBookingPrice)

   </script>



   <script type="text/javascript" src="https://js.stripe.com/v2/"></script>

  <script type="text/javascript">
     Stripe.setPublishableKey('<%= STRIPE_PUBLIC_KEY %>');
     var stripeResponseHandler = function(status, response) {
       var $form = $('#new_booking');

       if (response.error) {
       // Show the errors on the form
       $form.find('.payment-errors').text(response.error.message);
       $form.find('input[type=submit]').prop('disabled', false);
       } else {
       // token contains id, last4, and card type
       var token = response.id;
       // Insert the token into the form so it gets submitted to the server
       $form.append($('<input type="hidden" name="booking[stripe_token]"     />').val(token));
       // and submit
       $form.get(0).submit();
       }
     };

     // jQuery(function($)  { - changed to the line below
     $(document).on("ready page:load", function () {

       $('#new_booking').submit(function(event) {
        var $form = $(this);

        // Disable the submit button to prevent repeated clicks
         $form.find('input[type=submit]').prop('disabled', true);

         Stripe.card.createToken($form, stripeResponseHandler);

         // Prevent the form from submitting with the default action
         return false;
       });
     });
   </script>

Just to answer the last part - no, the payments aren't showing up in Stripe.

Many thanks. Should I set stripe_charge_id into my booking params? This is what I have so far -

  private

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

I'm looking at byebug to try and sort this out but not totally sure where I should plant it on my code to identify the issue. I could put it here on my Booking model -

    def reserve
    byebug
    # Don't process this booking if it isn't valid
    return unless valid?

Or here -

  # Paid events should charge the customer's card
    else
     byebug
        begin
        charge = Stripe::Charge.create(amount: total_amount, currency: "gbp", card: stripe_token, description: "Booking number #{Booking.id}", items: [{quantity: quantity}])
        self.stripe_charge_id = charge.id
        save

Or in my controller here -

 def create
    byebug
    # 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

I guess I may have to try them all. What's becoming obvious is that the @booking.reserve function isn't working, hence the flash error is being put into action. My gut feeling is that it must be to do with how the Stripe payment has been implemented and feel it may be hinging on this line -

  self.stripe_charge_id = charge.id

Chris, should I be doing something in addition to this line in order for the whole 'reserve' method to work? Do I need a charge.id column in my bookings table? Do I do something in my controller whereby I pass either charge.id or event.id to my create action? Or do I change charge.id with booking.id which is a column already in my bookings table?

(When I finally get this working I'm gonna party like its 1999!!!!)

Still getting 'Booking unsuccessful' - I'll try the byebug option and get to the bottom of it. I can't be far off now if it's not throwing up errors. I really appreciate your patience - this has had me stumped for longer than I care to admit...

Rather than @booking.stripe_token (etc) should it just be self.stripe_token or just stripe_token as its in the model rather than the controller?

I'll check out byebug.

Ah okay. I appear to be making progress - there's no error messages now but I'm getting "Booking unsuccessful" everytime I complete a test payment.

For the free events (as previously discussed above) should I do something like this -

before_save : set_price_to_zero_if_free

def set_price_to_zero_if_free
       self.event.price >= 1 ( or 0?)    unless self.event.is_free
end

I'm still not solving this. I seem to be bouncing from one issue to another. Can't believe how difficult it is to create a process whereby a user can make multiple bookings in one payment.

Do I need to put something in my seeds file to handle the default value for quantity? I've tried the following -

 Booking.find_or_create_by_name('my_booking', quantity:1)

But I get a 'No method error : undefined method error' on my command line when I do rake db:seed. As I said I'm bouncing all over the place with this so apologies if I keep coming back looking for answers but I'm really stabbing in the dark as I've never done this before and I'm struggling to find solutions on my own.

I've just noticed that price_pennies is set at this in my schema -

 t.integer  "price_pennies",      default: 0,     null: false

Does this change anything regarding the above discussion? I think the issue may surround quantity as there's no default value set for that.

I assumed I'd done this but obviously not. Where do I set the defaults for these - in the controller or model?

Okay, I've implemented the above but now I get this error -

  NoMethodError in BookingsController#create
  undefined method `*' for nil:NilClass

In relation to the total_amount method in my Booking model -

  self.total_amount = quantity * event.price_pennies

Should it be booking.quantity ?

I must be already doing this as they show as that now, although I've been building this thing for so long I've forgotten doing it haha!!