this is the schema and my model for Visit (visit's status can be: Confirmed, Current, Expired and To be approved)
schema.rb
create_table "visits", force: true do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.date "start"
t.date "end"
t.integer "idVisit"
t.integer "employee_id"
t.integer "visitor_id"
t.string "status", default: "Confirmed"
end
Visit.rb
class Visit < ActiveRecord::Base
belongs_to :employee
belongs_to :visitor
default_scope -> { order(:created_at) }
validates :start, presence: true, uniqueness: {scope: [:end, :visitor_id]}
validates :end, presence: true
validates :visitor_id, presence: true
validates :employee_id, presence: true
validate :valid_date_range_required
def valid_date_range_required
if (start && end) && (end < start)
errors.add(:end, "must be after start")
end
end
end
Now my problem is that I need to compare for each visit, after each time I do show action in employees_controller.rb, the start and end date to Date.today (except for To be approved status); according to it I will change the status of visits in the database.
Here is what I did but probably there will be some mistakes since for now an error occurs at least, so I hope you could help me to fix it.
In Visit.rb I created this:
def check_visit_status(visit)
if visit.status != 'To be confirmed'
if visit.start <= Date.today && visit.end >= Date.today
visit.status = 'Current'
end
if visit.end < Date.today
visit.status = 'Expired'
end
end
end
Now in employees_controller.rb I have (I won't post it all):
class EmployeesController < ApplicationController
after_action :update_status, only: :show
def show
if logged_in?
#employee = Employee.find(params[:id])
#indirizzimac = current_employee.indirizzimacs.new
#visitor = current_employee.visitors.new
#visit = current_employee.visits.new
#visits = current_employee.visits.all
if params[:act]=='myData'
render 'myData'
elsif params[:act]=='myNetwork'
render 'myNetwork'
elsif params[:act]=='temporaryUsers'
render 'temporaryUsers'
elsif params[:act]=='guestsVisits'
render 'guestsVisits'
elsif params[:act]=='myAccount'
render 'myAccount'
else
render 'show'
end
else
render 'static_pages/errorPage'
end
end
def update_status
if #visits.any?
#visits.each do |visit|
check_visit_status(visit)
end
end
end
end
Thank you a lot in advance
I really have to thank eeeeeean for his immense help.
I figured out my problem so I want to post here my solution in order to help someone looking for the same thing I was asking for.
employees_controller.rb
class EmployeesController < ApplicationController
after_action :update_status, only: :show
def show
[...]
end
def update_status
if #visits.any?
#visits.each do |visit|
visit.check_visit_status
end
end
end
end
Visit.rb
def check_visit_status
if self.status != 'To be confirmed'
if self.start <= Date.today && self.end >= Date.today
self.update_attribute :status, 'Current'
end
if self.end < Date.today
self.update_attribute :status, 'Expired'
end
end
end
You need to call check_visit_status on an instance of Visit, but right now it's being called on self, which in this scope refers to the employees controller. Try this:
visit.rb
def check_visit_status
if self.status != 'To be confirmed'
if self.start <= Date.today && end >= Date.today
self.status = 'Current'
end
if self.end < Date.today
self.status = 'Expired'
end
end
end
Then call it like this:
employees_controller.rb
#visits.each do |visit|
visit.check_visit_status
end
That should get you out of that particular error.
Related
To practice, I'm trying to create a small web game with Ruby On Rails, however, I need some information in my model and also in my controller.
I explain myself. For example, in my model, I have the following method:
def cost(building)
{
wood: BUILDING[building]['wood'] * (BUILDING[building]['factor'] ** self.level(building)),
stone: BUILDING[building]['stone'] * (BUILDING[building]['factor'] ** self.level(building)),
iron: BUILDING[building]['iron'] * (BUILDING[building]['factor'] ** self.level(building))
}
end
Then in my view I use the following to display the cost of upgrading the building :
<%= #buildings.cost(building[0])[:wood] %>
But I also have an "upgrade" button in my view that allows to upgrade this building, except that on the controller side I also need to get the costs of the building but I'm not sure of the right approach.
[EDIT] :
To give more informations :
#buildings = #buildings = Building.find_by(kingdom_id: current_kingdom.id) #Inside BuildingsController
building[0] It is a string in a yaml that corresponds to the name of a building
level it recovers the current level of the building for the player
EDIT 2 :
models/building.rb :
class Building < ApplicationRecord
def cost(building)
{
wood: BUILDING[building]['wood'] * (BUILDING[building]['factor'] ** self.level(building)),
stone: BUILDING[building]['stone'] * (BUILDING[building]['factor'] ** self.level(building)),
iron: BUILDING[building]['iron'] * (BUILDING[building]['factor'] ** self.level(building))
}
end
def consumption(building)
BUILDING[building]['coal']
end
def time(building)
resources_needed = cost(building)[:wood] + cost(building)[:stone] + cost(building)[:iron]
time = (resources_needed / (2500 * 4 * SERVER['rate']).to_f * 3600).round
if time >= 3600
"#{time / 60 / 60} h #{time % 3600 / 60} min"
elsif time >= 60
"#{time / 60} min #{time % 60 } sec"
else
"#{time} sec"
end
end
def level(building)
self[building.to_sym]
end
def upgrade?(building, kingdom_resources)
cost(building)[:wood] <= kingdom_resources[:wood] &&
cost(building)[:stone] <= kingdom_resources[:stone] &&
cost(building)[:iron] <= kingdom_resources[:iron]
end
end
buildings_controller.rb :
class BuildingsController < ApplicationController
def index
#buildings = Building.find_by(kingdom_id: current_kingdom.id)
#kingdom_resources = kingdom_resources
#kingdom_queue = BuildQueue.where(kingdom_id: current_kingdom.id)
end
def add_to_queue
building = params[:building]
# If we can upgrade
# Add building to the queue
end
private
def cost(building)
{
wood: BUILDING[building]['wood'] * (BUILDING[building]['factor'] ** self.level(building)),
stone: BUILDING[building]['stone'] * (BUILDING[building]['factor'] ** self.level(building)),
iron: BUILDING[building]['iron'] * (BUILDING[building]['factor'] ** self.level(building))
}
end
def building_level(building)
Building.find_by(kingdom_id: current_kingdom.id)[building.to_sym]
end
def time(building)
resources_needed = cost(building)[:wood] + cost(building)[:stone] + cost(building)[:iron]
(resources_needed / (2500 * 4 * SERVER['rate']).to_f * 3600).round
end
def upgrade?(building)
cost(building)[:wood] <= kingdom_resources[:wood] &&
cost(building)[:stone] <= kingdom_resources[:stone] &&
cost(building)[:iron] <= kingdom_resources[:iron]
end
end
method inside app/controllers/application_controller.rb,
To get the current_kingdom :
def current_kingdom
return nil unless current_user
return #_kingdom if #_kingdom
#_kingdom = Kingdom.find_by(user_id: current_user.id)
end
And current_user :
def current_user
return nil if !session[:auth] || !session[:auth]['id']
return #_user if #_user
#_user = User.find_by_id(session[:auth]['id'])
end
And current kingdom_resources :
def kingdom_resources
return #kingdom if #kingdom
#kingdom = {
wood: current_kingdom.wood,
stone: current_kingdom.stone,
iron: current_kingdom.iron,
coal: current_kingdom.coal,
food: current_kingdom.food
}
end
Thank's in advance,
Regards
fA user has a kingdom. A kingdom has buildings. We can set that up simple enough. A kingdom apparently also has build queues and resources.
class User < ApplicationRecord
has_one :kingdom
has_many :buildings, through: :kingdom
end
create_table :kingdoms do |t|
t.belongs_to :user, foreign_key: true
t.name :string, null: false
t.wood :integer, null: false
t.stone :integer, null: false
t.iron :integer, null: false
t.coal :integer, null: false
t.food :integer, null: false
end
class Kingdom < ApplicationRecord
belongs_to :user
has_many :buildings
has_many :build_queues
def resource(material)
self[material]
end
end
class BuildQueue < ApplicationRecord
belongs_to :kingdom
belongs_to :building
end
Now you can directly ask a player for its buildings: current_user.buildings and a kingdom for its build queues: kingdom.build_queues.
If you want to find a building by name: current_user.buildings.find_by(name: building_name).
Your Building model is strange. It seems like a single Building object represents all buildings. And information about the cost of a building is stored in a global.
Instead, the information about each building should instead be stored in a table row.
create_table :buildings do |t|
t.belongs_to :kingdom, foreign_key: true
t.string :name, null: false
t.cost_wood :integer, null: false
t.cost_stone :integer, null: false
t.cost_iron :integer, null: false
t.consumption_coal :integer, null: false
t.cost_factor :float, default: 1, null: false
t.level :integer, default: 1, null: false
end
class Building < ApplicationRecord
belongs_to :kingdom
has_one :player, through :kingdom
MATERIALS = [:wood, :stone, :iron].freeze
private def base_cost(material)
self[:"cost_#{material}"]
end
def cost(material)
base_cost(material) * cost_factor ** level
end
def build_cost
MATERIALS.sum { |m| cost(m) }
end
def build_time
# ActiveSupport::Duration will do the formatting for you.
ActiveSupport::Duration.build(
(build_cost / (2500 * 4 * SERVER['rate']).to_f * 3600).round
)
end
end
Now the BuildingsController can use what's been set up in the models.
class BuildingsController < ApplicationController
def index
# A user already has associations to its kingdom and buildings
# This is so simple there's no need for a current_kingdom in
# the ApplicationController
#kingdom = current_user.kingdom
#buildings = current_user.buildings
end
def show
if params[:id].present?
#building = current_user.buildings.find_by(id: params[:id])
else
#building = current_user.buildings.find_by(name: params[:name])
end
end
end
If you want to take action that involves multiple models, make a little object to do it. Like queuing a building upgrade.
# BuildingUpgradeQueuer.new(
# building: building,
# kingdom: kingdom
# ).queue_upgrade
class BuildingUpgradeQueuer
include ActiveModel::Model
MATERIALS = [:wood, :stone, :iron].freeze
attr_accessor :buiding, :kingdom
def queue_upgrade
return unless upgrade?
kingdom.build_queues.create!(building: building)
end
def upgrade?
MATERIALS.all? { |material|
building.cost(material) <= kingdom.resources(material)
}
end
end
You could do this in the Building or Kingdom object, but if you do that your models get complex and fat.
Rails version: 5.2.2.1
DB
db/migrate/20190520050333_create_posts.rb
class CreatePosts < ActiveRecord::Migration[5.2]
def change
create_table :posts do |t|
t.string :title
t.text :body
t.timestamps
end
end
end
db/migrate/20190520050350_create_post_copies.rb
class CreatePostCopies < ActiveRecord::Migration[5.2]
def change
create_table :post_copies do |t|
t.string :title
t.text :body
t.timestamps
end
end
end
Model
app/models/post.rb
class Post < ApplicationRecord
after_save :save_post_copy
private
def save_post_copy
if title_changed?
post_copy = PostCopy.new
post_copy.id = self.id
post_copy.title = self.title
post_copy.body = self.body
post_copy.save!
end
end
end
Console
post = Post.first
post.title = 'change title'
post.title_changed? # => true
post.save!
PostCopy.first
=> nil
Here want to auto save the record to post_copies when the title been changed in posts. But after the record saved in posts, can't find anything in post_copies.
Probably id should not be explicitly set, as rails automatically assign the value of the id
def save_post_copy
if self.title_changed?
post_copy = PostCopy.new
#post_copy.id = self.id
post_copy.title = self.title
post_copy.body = self.body
post_copy.save!
end
end
Or alternatively
after_save :save_post_copy, if: : saved_change_to_title
def save_post_copy
post_copy = PostCopy.new
post_copy = self.dup
post_copy.save
end
I'm running into an issue with my rails build. I've created a hotel application and when trying to check out a room at any future time or date it gives me an error described in the picture below. I'm open to any feedback and your help is sincerely appreciated! I've attached a screenshot of the issue and all my code that I can think of that might be related.
PICTURE OF THE PROBLEM
reservation.rb
class Reservation < ActiveRecord::Base
belongs_to :user
belongs_to :room
attr_accessor :checkin_date, :checkin_time, :checkout_date, :checkout_time
validates_presence_of :checkin_date
validates_presence_of :checkin_time
validates_presence_of :checkout_date
validates_presence_of :checkout_time
validates_presence_of :number_of_rooms
validate :future_checkin_date
validate :future_checkin_time
validate :future_checkout_date
validate :future_checkout_time
validate :no_of_rooms_greater_then_0, if: lambda { number_of_rooms.present? }
def future_checkin_date
if checkin_date.present? && checkin_date.to_date < DateTime.now.to_date
errors.add(:checkin_date, 'must be a valid current or future date')
end
end
def future_checkin_time
if checkin_time.present? && checkin_time.to_time < Time.now
errors.add(:checkin_time, 'must be a valid current or future time')
end
end
def future_checkout_date
if checkin_date.present? && checkout_date.present? &&
checkout_date.to_date < checkin_date.to_date
errors.add(:checkout_date, 'must be a valid date after your check in ' \
'date')
end
end
def future_checkout_time
if checkin_datetime.present? && checkout_datetime.present? &&
checkout_datetime <= checkin_datetime
errors.add(:checkout_time, 'must be a valid time after your check in ' \
'time.')
end
end
def no_of_rooms_greater_then_0
errors.add(:number_of_rooms, 'must be 1 or more to make a reservation') if
number_of_rooms <= 0
end
def convert_to_datetime
if self.checkin_date.present? && self.checkin_time.present?
self.checkin_datetime = self.merge_datetime(
self.checkin_date,
self.checkin_time
)
end
if self.checkout_date.present? && self.checkout_time.present?
self.checkout_datetime = self.merge_datetime(
self.checkout_date,
self.checkout_time
)
end
end
def merge_datetime(date1, time1)
res_date = Date.parse(date1)
res_time = Time.parse(time1)
merged_datetime = DateTime.new(
res_date.year,
res_date.month,
res_date.day,
res_time.hour,
res_time.min,
res_time.sec
)
end
def room_name
room.room_type.name
end
def decrease_room_inventory
room.update(inventory: (room.inventory -= number_of_rooms))
end
def self.default_checkin_date
DateTime.now.strftime('%Y-%m-%d')
end
def self.default_checkin_time
(DateTime.now.midday + 3.hours).strftime('%H:%M')
end
def self.default_checkout_date
DateTime.now.tomorrow.strftime('%Y-%m-%d')
end
def self.default_checkout_time
(DateTime.tomorrow.midday).strftime('%H:%M')
end
def self.users_reservations(user)
where('user_id = ?', user)
end
def reservation_date(booking_datetime)
booking_datetime.strftime('%Y-%m-%d')
end
def reservation_time(booking_datetime)
booking_datetime.strftime('%H:%M')
end
def self.reservations_checkin_setter(reservations)
reservations.each do |reservation|
reservation.checkin_date = reservation.reservation_date(
reservation.checkin_datetime
)
reservation.checkin_time = reservation.reservation_time(
reservation.checkin_datetime
)
end
end
def self.reservations_checkout_setter(reservations)
reservations.each do |reservation|
reservation.checkout_date = reservation.reservation_date(
reservation.checkout_datetime
)
reservation.checkout_time = reservation.reservation_time(
reservation.checkout_datetime
)
end
end
def hotel_name
room.hotel.name
end
def increase_room_inventory
room.update(inventory: (room.inventory += number_of_rooms))
end
def self.reservation_checkin_setter(reservation)
reservation.checkin_date = reservation.reservation_date(
reservation.checkin_datetime
)
reservation.checkin_time = reservation.reservation_time(
reservation.checkin_datetime
)
end
def self.reservation_checkout_setter(reservation)
reservation.checkout_date = reservation.reservation_date(
reservation.checkout_datetime
)
reservation.checkout_time = reservation.reservation_time(
reservation.checkout_datetime
)
end
def alter_room_inventory(orginal_number)
if number_of_rooms != orginal_number.to_i
room = Room.find(room_id)
room.update(inventory: (room.inventory += orginal_number.to_i))
answer = self.room_available?(room.room_type)
if answer[0]
self.decrease_room_inventory
else
room.update(inventory: (room.inventory -= orginal_number.to_i))
end
answer
else
false
end
end
def room_available?(room_type)
if Room.find(room_id).inventory == 0
message = "Unfortunately, all of those #{room_type.name} rooms have "\
"been reserved. Please select another room"
return false, message
elsif number_of_rooms > Room.find(room_id).inventory
message = "Unfortunately, your desired quantity of the " \
"#{room_type.name} room is not available. Please select another " \
"room, or reserve less rooms of this type"
return false, message
else
[true]
end
end
def user_view_reservation_date(booking_datetime)
booking_datetime.to_date.strftime('%A, %B %d, %Y')
end
def user_view_reservation_time(booking_datetime)
booking_datetime.to_time.strftime('%l:%M %P')
end
def total_nights
(checkout_datetime.to_date - checkin_datetime.to_date).to_i
end
def total_price
nights = self.total_nights == 0? 1 : self.total_nights
cost = nights * room.room_rate * number_of_rooms
taxes = cost * 0.15
cost + taxes
end
end
_reservation.html.erb
<div>
<h3><%= reservation.room_name %> at <%= reservation.hotel_name %></h3>
<ul>
<strong>Check In: </strong>
<%= reservation.user_view_reservation_time(reservation.checkin_time) %> on
<%= reservation.user_view_reservation_date(reservation.checkin_date) %>
<br />
<strong>Check out: </strong>
<%= reservation.user_view_reservation_time(reservation.checkout_time) %> on
<%= reservation.user_view_reservation_date(reservation.checkout_date) %>
<br />
<strong>Rooms: </strong>
<%= reservation.number_of_rooms %><br />
</ul>
<h3>Your Rate</h3>
<ul>
<strong>Total Nights: </strong>
<%= reservation.total_nights %><br />
<strong>Average Nightly Rate: </strong>
<%= number_to_currency(reservation.room.room_rate) %><br />
<strong>Estimated Total Price: </strong>
<%= number_to_currency(reservation.total_price) %><br />
</ul>
<%= button_to 'Edit',
edit_reservation_path(reservation, reservation.id), method: 'get',
class: 'btn btn-large btn-primary' %><br />
<%= button_to 'Delete', reservation_path(reservation), method: 'delete',
data: { confirm: 'Are you sure you want to delete this reservation?' },
class: 'btn btn-large'%>
</div>
reservations_controller.rb
class ReservationsController < ApplicationController
before_action :require_login
before_action :set_reservation, only: [:edit, :update, :destroy]
def index
#reservations = Reservation.users_reservations(current_user)
Reservation.reservations_checkin_setter(#reservations)
Reservation.reservations_checkout_setter(#reservations)
end
def create
#reservation = Reservation.new(reservation_params)
#reservation.checkin_date=(reservation_params[:checkin_date])
#reservation.checkin_time=(reservation_params[:checkin_time])
#reservation.checkout_date=(reservation_params[:checkout_date])
#reservation.checkout_time=(reservation_params[:checkout_time])
#reservation.convert_to_datetime
#room_type = RoomType.find(params[:reservation][:room_type_id])
result = #reservation.room_available?(#room_type)
if result[0]
if #reservation.save
#reservation.decrease_room_inventory
redirect_to reservations_path,{notice: "Your reservation " \
"for the #{#reservation.room_name} has been made, $0 are due today"}
else
render :'room_types/show'
end
else
redirect_to room_path(#reservation.room.hotel),
{alert: "#{result[1]}"}
end
end
def edit
if #reservation.user_id == current_user.id
Reservation.reservation_checkin_setter(#reservation)
Reservation.reservation_checkout_setter(#reservation)
render :edit
else
flash[:alert] = "You don't have permission to edit that reservation."
redirect_to reservations_path
end
end
def update
#reservation.checkin_date=(reservation_params[:checkin_date])
#reservation.checkin_time=(reservation_params[:checkin_time])
#reservation.checkout_date=(reservation_params[:checkout_date])
#reservation.checkout_time=(reservation_params[:checkout_time])
#reservation.convert_to_datetime
#reservation.number_of_rooms = reservation_params[:number_of_rooms]
result = #reservation.alter_room_inventory(
params[:reservation][:orginal_number_of_rooms]
)
if !result || result[0]
if #reservation.save
redirect_to reservations_path,{notice: "Your reservation " \
"for the #{#reservation.room_name} has been updated."}
else
render :edit
end
else
redirect_to reservations_path, {alert: "#{result[1]}"}
end
end
def destroy
#reservation.increase_room_inventory
#reservation.delete
redirect_to reservations_path,{notice: "Your reservation for " \
"#{#reservation.checkin_datetime.strftime('%A, %B %d, %Y')} has " \
"been deleted."}
end
private
def set_reservation
#reservation = Reservation.find_by(id: params[:id])
end
def reservation_params
params.require(:reservation).permit(
:checkin_date,
:checkin_time,
:checkout_date,
:checkout_time,
:number_of_rooms,
:room_id,
:user_id
)
end
end
user.rb
class User < ActiveRecord::Base
has_many :reservations, dependent: :destroy
has_many :rooms, through: :reservations
has_many :addresses, dependent: :destroy
validates_presence_of :name
has_secure_password
validates_associated :addresses, unless:
Proc.new { |user| user.provider.present?}
def addresses_attributes=(addresses_attributes)
addresses_attributes.values.each do |address_attributes|
if address_attributes.keys.include?('id')
address = self.addresses.find(address_attributes[:id])
address.update_attributes(address_attributes)
else
self.addresses.build(address_attributes)
end
end
end
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
user.provider = auth.provider
user.uid = auth.uid
user.name = auth.info.name
user.password = SecureRandom.hex
end
end
def update_room_inventory
reservations.each do |reservation|
reservation.increase_room_inventory
end
end
end
routes.rb
Rails.application.routes.draw do
get '/auth/:provider/callback', to: 'sessions#github'
resources :addresses, only: [:new, :create, :destroy]
get '/login', to: 'sessions#new'
post '/login', to: 'sessions#create'
delete '/logout', to: 'sessions#destroy'
resources :users
resources :reservations, only: [:index, :edit, :update, :destroy]
resources :rooms, only: :show do
resources :room_types, only: :show
resources :reservations, only: :create
end
root 'hotels#index'
end
create_reservations.rb
class CreateReservations < ActiveRecord::Migration
def change
create_table :reservations do |t|
t.references :room, index: true, foreign_key: true
t.references :user, index: true, foreign_key: true
t.integer :number_of_rooms, default: 1
t.datetime :checkin_datetime
t.datetime :checkout_datetime
t.timestamps null: false
end
end
end
I see that the error is from
def future_checkin_time as the error message says
Checkin time must be a valid current or future time
Also as per your code in the error message checkin time value must be shown. Can you please print and check if the value is being captured properly?
I'm building an Events app in Rails and I've hit the error above which relates to this method in my Model -
def validate_availability
errors.add(:base, 'event is fully booked') if booking.count >= event.number_of_spaces
end
The purpose of the method is to avoid over-booking of an event whereby a specific number of spaces are available. In my Controller I have the following code -
Controller#Create
def create
#event = Event.find(params[:event_id])
#booking = #event.bookings.new(booking_params)
#booking.user = current_user
if
#booking.set_booking
flash[:success] = "Your place on our event has been booked"
redirect_to event_booking_path(#event, #booking)
else
flash[:error] = "Booking unsuccessful"
render "new"
end
if #event.is_free?
#booking.save(booking_params)
end
if booking.count >= #event.number_of_spaces
flash[:error] = "Sorry, this event is now fully booked"
render "new"
end
end
I need to define booking.count in my controller but not sure what would work - tried a few things but nothings working. I have the following in my schema -
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", default: 1
t.integer "total_amount"
t.string "stripe_charge_id"
t.string "booking_number"
end
The booking.count would rely upon the quantity of spaces/bookings a user wishes to make versus the number of spaces remaining but how do I express this? Do I need a total_bookings column in my table or a separate method?
UPDATE -
Booking.rb
class Booking < ActiveRecord::Base
belongs_to :event
belongs_to :user
before_create :set_booking_number
validates :quantity, presence: true, numericality: { greater_than_or_equal_to: 0 }
validates :total_amount, presence: true, numericality: { greater_than_or_equal_to: 0 }
validate(:validate_booking)
validate(:validate_availability)
def set_booking_number
self.booking_number = "MAMA" + '- ' + SecureRandom.hex(4).upcase
end
def set_booking
if self.event.is_free?
self.total_amount = 0
save!
else
self.total_amount = event.price_pennies * self.quantity
begin
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
# if this fails stripe_charge_id will be null, but in case of update we just set it to nil again
self.stripe_charge_id = nil
# we check in validatition if nil
end
end
end
def validate_booking
# stripe_charge_id must be set for not free events
unless self.event.is_free?
return !self.stripe_charge_id.nil?
end
end
private
def validate_availability
errors.add(:base, 'event is fully booked') if event.bookings.count >= event.number_of_spaces
end
end
For the counts of booking table, you should have a booking_count field in events table. Use the counter cache for this. For more details check http://guides.rubyonrails.org/association_basics.html. This is very helpful when records are large.
Your migration for adding column should be similar as below:
def change
add_column :events, :bookings_count, :integer, default: 0
Event.reset_column_information
Event.all.each do |e|
Event.update_counters e.id, :bookings_count => e.bookings.length
end
end
I'm creating a rails app to manage events and I want to add validation to check if an event overlaps with another.
Here's my custom validation code to check for overlapping events:
class Event < ActiveRecord::Base
attr_accessible :full_date, :start_hour, :end_hour, :address
validate :no_overlapping_events
def no_overlapping_events
overlap = false
same_date = Event.where(:full_date => self.full_date).where("id != ?", self.id).where(:address => self.address)
same_date.each do |t|
if self.start_hour <= t.start_hour
if self.end_hour >= t.start_hour
overlap = true
break
end
end
end
if overlap == true
errors.add(:base, "Can't have overlapping events!")
end
end
end
I can still create events that are overlapping and they're still being saved. What am I doing wrong here?
Your custom validation method "ends" too soon. Try:
class Event < ActiveRecord::Base
attr_accessible :full_date, :start_hour, :end_hour, :address
validate :no_overlapping_events
def no_overlapping_events
overlap = false
same_date = Event.where(:full_date => self.full_date).where("id != ?", self.id).where(:address => self.address)
same_date.each do |t|
if self.start_hour <= t.start_hour
if self.end_hour >= t.start_hour
overlap = true
break
end
end
end
if overlap == true
errors.add(:base, "Can't have overlapping events!")
end
end
end
Also, as a logic error, what about the case when:
self.start_hour > t.start_hour and self.start_hour < t.end_hour