Mike Whitehead
Joined
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.
Posted in What's the best way to install a robust system which checks if an event is over-booked in Rails?
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!!