I've created a application where a user can reserve a vehicle (minibus) I've got the application working where a user can reserve a vehicle but the vehicle cannot be double booked. I've got a simple search working which allows the user to search for make or model. I'm looking to expand this search so a user can search for all vehicles which are available for a date the user wants to book. I'm not too sure how to approach this can someone please help!.
I've got a Vehicle table, User table and a Reservation table.
my schema look like (I've only included the tables concerned):
create_table "reservations", force: :cascade do |t|
t.date "startDate"
t.date "endDate"
t.integer "noOfDays"
t.integer "costs"
t.integer "vehicle_id"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["user_id"], name: "index_reservations_on_user_id"
t.index ["vehicle_id"], name: "index_reservations_on_vehicle_id"
end
create_table "users", force: :cascade do |t|
t.string "address"
t.string "city"
t.date "dateofbirth"
t.string "email"
t.string "fname"
t.string "postcode"
t.string "sname"
t.string "user_type"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "password_digest"
t.string "contact_no"
end
create_table "vehicles", force: :cascade do |t|
t.string "description"
t.integer "door"
t.string "fuel"
t.string "img"
t.string "make"
t.string "model"
t.date "motEnd"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.date "motDue"
t.date "motStart"
t.integer "price"
t.string "reg"
t.integer "seats"
t.string "transmission"
t.date "year"
t.decimal "engine"
end
I've already got the overlap booking working heres a like to the solution I used: Avoid double bookings for a minibus reservation system
I'm trying to expand my search so the user can search between two dates on the index page for all available vehicles during that time. I just don't know how I'd get this to work so the system shows available vehicles for that date.
My current simple search:
Vehicles controller:
def vehicle_search
vehicles = Vehicle.fuzzy_search(params[:search_string])
#vehicles = Kaminari.paginate_array(vehicles.order :make).page(params[:page]).per(3)
if #vehicles.empty?
flash.now[:alert] = "No records found - displaying all records ..."
#vehicles = Vehicle.order(:make).page(params[:page]).per(3)
end
render :action => "index"
end
vehicle.rb
def self.fuzzy_search(search_string)
search_string ||= ""
make = search_string
model = search_string
price = search_string
fuel = search_string
transmission = search_string
self.where("make LIKE ? OR model LIKE ? OR price LIKE ? OR fuel LIKE ? OR transmission LIKE ?", make, model, price, fuel, transmission)
end
Search partials
<%= form_tag my_path, :method => 'post', :multipart => true do %>
<!--vehicle details-->
<%= text_field_tag :search_string, "", class: 'form-control custom2' %>
<%= submit_tag 'Search', class: "btn btn-default" %>
<% end %>
vehicles/index.html.erb
<a>Make, Model, Fuel Type, Transmission: </a>
<%= render(:partial=>'/vehicle_search',:locals=> { :my_path => "/vehicles/vehicle_search" })%>
I will be working with the following logic to determine a time overlap:
(StartA <= EndB) and (EndA >= StartB)
If the above is true the periods overlap. You can check this out in the Stack Overflow question: Determine Whether Two Date Ranges Overlap
So with that out of the way let's create two scopes on Reservation. One to get overlapping reservations that overlap with one point in time, and one to get overlapping reservations that overlap with a time window.
class Reservation < ApplicationRecord
# code ...
# assuming Reservation has start_time and end_time attributes
# and you validate that start_time is always before end_time
# time param can also be a date or datetime
def self.overlap(time)
# Reservation start_time is less than or equal to the given time
# and reservation end_time is greater than or equal to the given
# time.
where(arel_table[:start_time].lteq(time))
.where(arel_table[:end_time].gteq(time))
end
def self.overlap_window(start_time, end_time)
# Reservation start_time is less than or equal to the given
# end_time and reservation end_time is greater than or equal
# to the given start_time.
where(arel_table[:start_time].lteq(end_time))
.where(arel_table[:end_time].gteq(start_time))
end
# code ...
end
Now you can fetch the vehicles quite easy by checking which vehicles are not occupied in for example a certain time window.
Vehicle.where.not(id: Reservation.select(:vehicle_id).overlap_window(Time.now, 4.hours.from_now))
Note: Start time must always be before the end time (for both the reservation and the time window) for this to work as explained (mainly in the comments) in the date overlap question linked at the start.
Related
I have a custom rails validator below that checks whether a new booking is available within the existing bookings based on the start and end date. I'm struggling to figure how I can add a separate 'room' validation. Each booking is for a room, so I want to make sure that the date range is only for the specified room and not for ALL bookings, just the bookings for the individual room. Any thoughts?
validates :start_date, :end_date, :presence => true, availability: true
class AvailabilityValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
bookings = Booking.where(void:'f')
date_ranges = bookings.map { |b| b.start_date..b.end_date }
date_ranges.each do |range|
if range.include? value
record.errors.add(attribute, "not available")
end
end
end
end
EDIT:
Following up on the schema. Fairly simple, all bookings are made by parent associated users ("user_id"), and room is simply another integer attribute within the record (there are 10 rooms).
create_table "bookings", force: :cascade do |t|
t.datetime "start_date"
t.datetime "end_date"
t.boolean "void", default: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.bigint "user_id"
t.integer "room"
t.index ["user_id"], name: "index_bookings_on_user_id"
end
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
I have 6 modules (1. ingredients; 2. restaurants; 3. sandwich_ingredients; 4. sandwiches; 5. stocks; 6. users) and the user should be able to create/personalise a sandwich using the ingredients of that specific restaurant. Therefore, I would like to create a form on the show.html.erb of the restaurant so that the user can pick up several ingredients and create their own sandwich.
I feel comfortable working with 3 modules in a many-to-many relationship but with 6 tables I just get confused and lost.
I tried form_for, form_tag, collection_check_boxes. When I used to work with a smaller project, I had no issues but now I'm not sure what approach should I take. I tried to create the form on the user show page first and then on the sandwich show page but it didn't seem quite right.
This is the code from the restaurant show.html.erb page:
<ul>
<%= form_tag('/restaurants/create_sandwich') do %>
<%= label_tag 'Ingredients' %>
<li><%= collection_check_boxes(:ingredient, :id, #restaurant.ingredients, :id, :name) %></li>
<%= submit_tag 'Create' %>
<% end %>
</ul>
The restaurants_controller.rb (messed up):
class RestaurantsController < ApplicationController
def index
#restaurants = Restaurant.all
end
def show
#restaurant = Restaurant.find(params[:id])
#user = User.new
#sandwich = Sandwich.new
end
def new
#sandwich = Sandwich.new
# res = Restaurant.find(params[:id])
# #restaurant_ingredients = res.ingredients
end
def select_ingredients
# byebug
#
# #restaurant_ingredients = Restaurant.find(params[:id]).ingredients
# byebug
# #restaurant_ingredients = res.ingredients
# #ingredients = Ingredient.all
end
def create_sandwich
# byebug
#user = User.find_or_create_by({name: params[:user_name]})
#sandwich = Sandwich.new({name: params[:recipe_name], user_id: #user.id})
#sandwich.sandwich_ingredient_ids = params[:ingredients]
if #sandwich.save
redirect_to sandwich_path(#sandwich)
else
render :new
end
end
def edit
#code
end
def update
#code
end
def delete
#code
end
end
If it can help, these are the routes:
Rails.application.routes.draw do
# get '/restaurants/:id', to: 'restaurants#create_sandwich'
post '/restaurants/create_sandwich', to: 'restaurants#create_sandwich'
resources :stocks
resources :restaurants
resources :sandwich_ingredients
resources :ingredients
resources :sandwiches
resources :users
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
and this is my schema:
ActiveRecord::Schema.define(version: 2019_04_15_174308) do
create_table "ingredients", force: :cascade do |t|
t.string "name"
t.integer "quantity"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "restaurants", force: :cascade do |t|
t.string "name"
t.string "location"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "sandwich_ingredients", force: :cascade do |t|
t.integer "sandwich_id"
t.integer "ingredient_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "sandwiches", force: :cascade do |t|
t.string "name"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "stocks", force: :cascade do |t|
t.integer "restaurant_id"
t.integer "ingredient_id"
t.integer "count"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "users", force: :cascade do |t|
t.string "name"
t.string "surname"
t.string "email"
t.string "phone_number"
t.integer "age"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
What I'm trying to achieve is a form that would create the association "SandwichIngredient" and to do so, the "User" and the "Sandwich" but at this point I feel just lost.
Lots of areas to comment on here, and the question is quite vague but I'll give some tips
Your 'new' action on your restaurant controller should be for a new restaurant, not a new sandwich.
Your 'new sandwich' form should probably be directing to the create action on your sandwiches controller. You're trying to do too much on the restaurants controller.
If your ingredients belong to a restaurant, they need a restaurant_id field
You probably don't want to create a new user each time you create a sandwich, perhaps you want to add devise or a similar user management gem to your project and have them sign in, and assign the sandwich to the current user, or have a drop down select of the users in the system as part of the sandwich form
As a general tip, I would completely ignore your stocks model for now, focus on getting sandwiches working first.
Not super related but would be good to have either one 'name' field for a user or 'first_name' and 'last_name' to avoid confusion
You may need to add a hidden field in the form to contain the restaurant_id to be able to pass this value through when you submit the form
Make sure you have a field on the sandwich form for the name of the sandwich
I would probably recommend sticking with 'form_for' for #sandwich, which by default should go to the sandwiches create action as mentioned above.
Feel free to post a link to this project on github or similar if you need further help.
I have two models:
class Order < ActiveRecord::Base
belongs_to :user
has_one :order_type
end
class OrderType < ActiveRecord::Base
belongs_to :order
end
my schema.rb:
create_table "order_types", force: :cascade do |t|
t.string "ort_name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "orders", force: :cascade do |t|
t.string "ord_name"
t.date "ord_due_date"
t.integer "user_id"
t.integer "ordertype_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "orders", ["ordertype_id"], name: "index_orders_on_ordertype_id"
add_index "orders", ["user_id"], name: "index_orders_on_user_id"
There is only one-direction association between them. The Order model has a column "ordertype_id" that links to the appropriate order_type.
My question is, what is the best practice to access the ort_name value for each #order in a view.
Currently, I am using:
<p>
<strong>Ord type:</strong>
<% OrderType.where(id: #order.ordertype_id).each do |t| %>
<%= t.ort_name %>
<% end %>
</p>
This solution results in many code repetitions. How I should change that? Can somebody advise, as I am not so experienced yet?
I tried this code, but it did not work:
#orders.order_type
There are many problems which you should address. It's ok to be a beginner, just take yourself time to learn and improve.
Schema
First off, your schema is set up badly. If you want to limit the order type to certain values, you should do this with a validation.
class Order
TYPES = %w[foo bar three four five]
validates :order_type, inclusion: { in: TYPES }
end
This way, you can easily add values in the future, and remove the complexity of adding a new model and its relations.
Column Names
Secondly, you should revise your column names. ord_name and ord_due_date is bad, it leads to ugly calls like order.ord_name. You should drop the prefix ord, it's superfluous.
Both steps would lead to this schema.rb
create_table "orders", force: :cascade do |t|
t.string "name"
t.date "due_date"
t.integer "user_id"
t.string "order_type"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Logic placement
My final advice is to never call queries from your view. Logic should always be in the controller / model & passed to the view via instance variables.
This is a big no no in rails:
<% OrderType.where(id: #order.ordertype_id).each do |t| %>
...
<% end %>
In the end, accessing the type is simply accomplished with:
#order.order_type
Update your Order model to this:
class Order < ActiveRecord::Base
belongs_to :user
has_one :order_type, foreign_key: 'ordertype_id`
end
then order_type should be easily accessible:
#order.order_type.ort_name
I am building a cinema application in Ruby on Rails and am currently working on the booking system. What I want to be able to do is select a film from a drop down menu, and then the show times for that film are displayed in a drop down menu, the user can select a show time and then the seats available are displayed in a drop down menu.
I have watched this video: https://www.youtube.com/watch?v=GYg6s-b1XGo and have looked at many other sites but what I want to do is a bit more complicated.
Ok, so my models/bookings.rb:
class Booking < ActiveRecord::Base
belongs_to :user
belongs_to :showing
belongs_to :seat
end
models/showing.rb:
class Showing < ActiveRecord::Base
belongs_to :film
has_many :bookings
belongs_to :screen
def showing_times
"#{show_date.strftime("%e %b %Y")} # #{show_time.strftime("%H:%M")}"
end
end
models/seats.rb:
class Seat < ActiveRecord::Base
belongs_to :screen
has_many :bookings
def seats_available
"#{row_letter}#{row_number}"
end
end
This is where it gets complicated, my views/bookings/_form.html.erb:
<%= form_for #booking do |f| %>
<%= f.hidden_field :user_id %>
<%= image_tag "thor_hammer.jpg",:size => "900x250" %>
<td width="300px">
<br><%= f.label :film_id, 'Film:' %>
<br><%= f.collection_select :film_id, Film.all, :id,:title_info %>
<br><%= f.label :showing_id, 'Showing:' %>
<br><%= f.collection_select :showing_id, Showing.all, :id,:showing_times %>
<br><%= f.label :seat_id, 'Seat ID:' %>
<br><%= f.collection_select :seat_id, Seat.all, :id,:seats_available %><br>
</td>
<td width="300px">
<div class="actions">
<%= f.submit %>
</div>
<br>
<%= render "/error_messages", :message_header => "Cannot save: ", :target => #booking %>
</td>
<% end %>
This is where it gets complicated because the line <%= f.collection_select :film_id, Film.all, :id,:title_info %> causes the error:
NoMethodError in Bookings#new
undefined method `film_id' for #<Booking:0x584e6b0>
Because the bookings data table does not store the film, it stores the showing time and this has the film associated with it, this is the same for the seats: the user needs to be able to select a showing to select the seat but the showings table is associated with the screens table, which is associated with the seats.
To make this clearer, here is the schema:
create_table "bookings", force: :cascade do |t|
t.integer "user_id"
t.integer "showing_id"
t.integer "seat_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "films", force: :cascade do |t|
t.string "title"
t.string "synopsis"
t.string "director"
t.string "cast1"
t.string "cast2"
t.string "cast3"
t.date "release_date"
t.string "warnings"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "image_url"
t.string "certificate_id"
t.integer "category_id"
t.integer "hours"
t.integer "minutes"
t.string "video_url"
end
create_table "screens", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "seats", force: :cascade do |t|
t.string "row_letter"
t.integer "row_number"
t.integer "screen_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "showings", force: :cascade do |t|
t.date "show_date"
t.time "show_time"
t.integer "film_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "screen_id"
end
Has anyone got any suggestions?
You are trying to set the film_id of an instance of the Booking class. You are correct to notice that you do not have a film_id in the bookings table.
The easiest way to accomplish what you desire is simply to add a film_id to the bookings table and to create a has_one and belongs_to association between Booking and Film.
Another way to accomplish this, that will require you to dive deeper into rails is to use accepts_nested_attributes_for. Here's a good tutorial and here's a newer tutorial if you run into any issues with the first one. This will allow you to not have to create redundant associations.
UPDATE 1
After looking closer at what you are trying to do I realized that you are not trying to create a new resource (film) so please disregard my previous comment about nested attributes.
The solution to what you are trying to do involves a few moving parts that are a little too long to give in an answer like this. However, here is a summary that may guide you while you try to figure it out:
You need a separate form to select the film first
Based on the film selection you can use ajax to fetch all the showings that belong to that film (For this you need to read up on ajax; you also need to read up on how to expose your data via a json endpoint in rails) You will end up passing an id of the film the user has selected and you will return the result of this query: Showing.where(film_id: <id>)
After you managed to get that data you need to populate it in a second form that ends up displaying only the showings associated with that film.
This seems a bit complicated for what you are trying to do. What I would recommend is that you either:
spend some extra time on figuring out how to structure your models a little differently
or that you don't allow the users to select the film and then the showings on the same page (dynamically). Make them select the film first. After they selected the film they are redirected to a page where they can select the showings. This would be a worse experience for the user but it would enable you to understand rails a little better (ajax and javascript wouldn't be required in this scenario).
I assume you're doing this to try to get a better grasp of rails, so I hope this information helps you. Good luck!
Here's my schema file..
ActiveRecord::Schema.define(:version => 20120505115340) do
create_table "clients", :force => true do |t|
t.string "name"
t.string "detail"
t.string "more_detail"
t.string "more_details"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "jobs", :force => true do |t|
t.string "name"
t.integer "number"
t.string "responsible"
t.string "monthly"
t.string "quarterly"
t.string "other"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
end
And here's my migration file's..
class CreateClients < ActiveRecord::Migration
def change
create_table :clients do |t|
t.string :name
t.string :detail
t.string :more_detail
t.string :more_details
t.timestamps
end
end
end
class CreateJobs < ActiveRecord::Migration
def change
create_table :jobs do |t|
t.string :name
t.integer :number
t.string :responsible
t.string :monthly
t.string :quarterly
t.string :other
t.timestamps
end
end
end
In my view file, I have it setup so that is pulls out the client.name and shows it to the user <%= link_to client.name, client_path(client) %>.
However, all im getting back when I create a new entry is /clients/1 instead of the name that I specified in my form.
When I try to migrate the DB nothing happens and then when I try to drop he DB to start afresh it tells me that it does even exist.
If I understand you correctly, you are concerned that your view displays a link to /clients/1 for your newly created object?
This is the default path when using Ruby on Rails, and is what will be produced by the path helper object_path(object) that you are using. This can be customized (see guides on routes.rb). If this is not a problem, then your application is working as intended.
BtW, the number used in the default path refers to the id given to the object. All objects stored using ActiveRecord will automatically get a unique id which can be used to identify the object. Just as the created_at and updated_at columns in your schema, the id column will be created regardless if you explicitly define it in your schema or not.
To reset your database (drop, recreate and migrate to current schema), use the following command:
rake db:reset
EDIT:
<%= link_to client.name, client_path(client) %>
Should result in the following HTML (where CLIENT_NAME is the name attribute of the client)
CLIENT_NAME