I'm currently making a website in Rails that will allow the user to create and account and then when logged in he/she will be get a list of all the "rooms" in the database. And then the user will be able to book a one of the rooms. Like a booking system.
So first when the user logges in it will get redirected to the controller booker index. That will get all the rooms from the database and display the information in a table.
The model situation is built up like the User has_many reservations, and the reservation belongs_to User.
So what I want to do is that next to the table (can be on the same row) there should be a click button. And when the user click the button the reservations is created and linked to the user without leaving the current page.
Atm this is what I came up with (in the index.html.erb)
<%= form_for(#new_reservation) do |new_res| %>
<% #all_rooms.each do |r| %>
<tr>
<td><%= r.room %></td>
<td><%= r.room_size %></td>
<td><%= r.building %></td>
<td><%= r.room_description %></td>
<td><%= new_res.submit "Create" %></td>
</tr>
<% end %>
<% end %>
and my controller looks like
class BookerController < ApplicationController
before_action :require_user, only: [:index, :show]
def index
#new_reservation = Reservation.new
#all_rooms = Room.all
end
def create
end
end
and route
Rails.application.routes.draw do
#Log in / Logout functionality
get '/login' => 'sessions#new'
post 'login' => 'sessions#create'
delete 'logout' => 'sessions#destroy'
#The start screen
root 'welcome#index'
#The signup
get '/signup' => 'users#new'
get '/book' => 'booker#index'
resources :users
resources :reservations
You need a few things to make this happen:
If you want your user to stay on the page, you need to make your form
asynchronous with remote: true
If you want to create the Reservation in your BookerController#create method, it needs to be accessible with a HTTP POST
A simple, straightforward way to do this is to have in your view a form for each potential reservation the user might do
Working example:
app/views/booker/index.html.erb
<% #all_rooms.each do |r| %>
<tr>
<td><%= r.room_size %></td>
<td><%= r.building %></td>
<td><%= r.room_description %></td>
<td>
<%= form_for(Reservation.new, remote: true, format: :js, url: {controller: "booker", action: "create"}) do |new_res| %>
<%= new_res.hidden_field :room_id, value: r.id %>
<%= new_res.hidden_field :user_id, value: current_user.id %>
<%= new_res.submit "Book" %>
<% end %>
</td>
</tr>
<% end %>
Here, replace current_user.id by whatever gives you the currently logged in user
app/controllers/booker_controller.rb
class BookerController < ApplicationController
def index
#all_rooms = Room.all
end
def create
new_reservation = Reservation.new(reservation_params)
new_reservation.save
respond_to do |format|
format.json { head :no_content }
end
end
private
def reservation_params
params.require(:reservation).permit(:room_id, :user_id)
end
end
app/config/routes.rb
Rails.application.routes.draw do
get '/book' => 'booker#index'
post '/book' => 'booker#create'
end
And when the user click the button the reservations is created and linked to the user without leaving the current page.
You need to learn up on how to make Ajax request in Rails.
http://ericlondon.com/2014/03/13/rails-4-submit-modal-form-via-ajax-and-render-js-response-as-table-row.html
Basically you will need to something like this
<%= form_for(#new_reservation, remote: remote) do |new_res| %>
Which will send data asynchronosly, you will create the reservation , and render a view for display
Related
Doing nested routes for the first time and I cannot figure out the link_to paths for the nested routes that I have. Using rails scaffold for each
resources :venues do
resources :events
end
I edited to have the following in the index.html.erb of the venue model
<%= link_to 'Events', venue_events_path(venue) %>
Which brings me to the correct index.html.erb of the events model (venues/1/events)
But I just can't seem to figure out the correct link_to path on the show page of event to bring me back to venues/1/events
With the default scaffold, its
<%= link_to 'Back', events_path %>
I have tried different paths like venue_events_path(venue) but rails keeps saying that it cannot find venue without an ID.
I am assuming that is because clicking on the default show link brings me to localhost:3000/events/1 instead of localhost:3000/venues/1/events/1
What would be the correct paths for nested attributes besides the difficulty that I am facing?
For nested routes I would use the following setup:
# routes.rb
resources :venues do
resources :events
end
# venues controller
class VenuesController < ApplicationController
def index
#venues = Venue.all
end
def show
#venue = Venue.find(params[:id])
end
end
# venues index.html.erb
<% #venues.each do |venue| %>
<%= link_to 'Venue', venue_path(venue) %>
<% end %>
# venues show.html.erb
# link to the events index page from the current venue
<%= link_to 'Events', venue_events_path(#venue) %>
# link to each individual event from the current venue
<% #venue.events.each do |event| %>
<%= link_to 'Event', venue_event_path(#venue, event)
<% end %>
# events controller
class EventsController < ApplicationController
def index
#venue = Venue.find(params[:venue_id])
#events = #venue.events
end
def show
#event = Event.find(params[:id])
end
end
# events index.html.erb
# link back to venue
<%= link_to 'Venue', venue_path(#venue) %>
# link to each event
<% #events.each do |event| %>
<%= link_to 'Event', venue_event_path(event.venue, event) %>
<% end %>
# events show.html.erb
# link back to events index
<%= link_to 'Events', venue_events_path(#event.venue) %>
# link back to venue show
<%= link_to 'Venue', venue_path(#event.venue) %>
Keep in mind that this will only work if your events have an venue id. You said you have in your event model: belongs_to :venue, optional: true the optional true make the venue_id not required for an event. If that is what you want then nested routes doesn't really makes sense, because an NOT nested event is not going to be linked nested.
I'm trying to update a record with a button, by having and admin user tyoe confirm a record by clicking the confirm submit button with this form, but it would just update the first record in the database, not the specific one. Can i put "hour.id" in the form_for section to make it update the record i want it to?
index.html.erb form view
<% #allhours.each do |hour| %>
<tr id="dashfield">
<td><%= hour.user.first_name + " " +hour.user.last_name %></td>
<td><%= hour.assignment %></td>
<td><%= hour.hours %></td>
<td><%= hour.supervisor %></td>
<td><%= hour.date %></td>
<td><%= link_to "confirm", edit_hour_path(hour.id, :status => 'confirmed')%></td>
</tr>
<% end %>
Controller
def index
#allhours = HourLog.where(status:'pending')
end
def update
#hour = HourLog.find_by(status:'pending')
#hour.update_attribute(update_hour_params)
redirect_to '/dashboard/hours'
end
def edit
#hour = HourLog.find(params[:id])
#hour.update_attributes(update_hour_params)
redirect_to '/dashboard/hours'
end
hour_log model
class HourLog < ActiveRecord::Base
belongs_to :user
end
routes
Rails.application.routes.draw do
root 'welcome#index'
get '/signup' => 'users#new'
resources :users
get '/dashboard/users' => 'users#index'
put '/dashboard/users' => 'users#update'
get '/login' => 'sessions#new'
post '/login' => 'sessions#create'
delete '/logout' => 'sessions#destroy'
get '/dashboard' => 'hours#new'
get '/dashboard/hours' => 'hours#index'
put '/dashboard/hours' => 'hours#update'
resources :hours
Thanks!
This line :
#hour = HourLog.find_by(status:'pending')
will find the first Hourlog that is in pending status. You should pass the hourlog id in the form and use it in the controller
change the line in the routes :
put '/dashboard/hours/:id' => 'hours#update'
in the view :
<%= form_tag dashboard_hours_path(id:hour.id), method: :put do %>
<%= hidden_field_tag :role, :value => 'confirm'%>
<%= submit_tag 'Confirm'%>
<% end %>
in the controller :
#hour = HourLog.find(params[:id])
I have made my form:
<tbody>
<% #player.bases.each do |basis| %>
<td><%= basis.id %></td>
<td><%= image_tag(basis.image_url(:thumb), class: 'thumbnail') %></td>
<td><%= link_to basis.name, basis %></td>
<td><%= basis.cost %></td>
<td><%= basis.short_info %></td>
<td>
<%= form_for #player, url: {:controller => 'players', :action => :remove_detail} do |f| %>
<%= f.hidden_field :type, :value => 'basis' %>
<%= f.hidden_field :detail_id, :value => basis.id %>
<%= f.submit 'Remove',class: 'btn btn-danger' %>
<% end %>
</td>
<% end %>
</tbody>
In my routes, I have added this:
resources :players do
collection do
get 'search'
post 'remove_detail'
end
end
I have remove_detail in my players_controller.rb, and I have added this action to before_action to get current player. However when I press on my Remove button, it throws me error and tries to run update action of my controller. Why?
My before_action:
before_action :set_player, only: [:show, :edit, :update, :destroy, :remove_detail]
My remove_detail:
def remove_detail
type = params['type']
id = params['detail_id']
if type == 'basis'
basis = Basis.find(id)
name = basis.name
#player.bases.delete(basis)
end
redirect_to #player, notice: "#{name} detail is removed"
end
To fix that, try as follows:
First of all, I'd redefine your routes as follows:
resources :players do
member do
delete 'remove_detail'
end
collection do
get 'search'
end
end
This will generate proper url for deleting a detail for a "single Player":
/players/:id/remove_detail
Because of REST-y nature of Rails, we defined the url to be accessible by performing delete request.
Your form change accordingly:
<%= form_for #player, { url: { action: "remove_detail" }, method: :delete } do |f| %>
Changing your routes to use delete method is more to keep the convention of Rails. Post would make your application work too, but - its just Rails-y way.
Good luck!
I have users that have posts.
<p id="notice"><%= notice %></p>
<h1>Listing Posts</h1>
<table>
<thead>
<tr>
<th>Comment</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% #posts.each do |post| %>
<tr>
<td><%= post.content %></td>
<td><%= link_to 'Show', post %></td>
<td><%= link_to 'Edit', edit_post_path(post) %></td>
<td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New Post', new_user_post_path %>
And in controller
def destroy
#user = #post.user
#post.destroy
respond_to do |format|
format.html { redirect_to user_posts_url(#user), notice: 'Post was successfully destroyed.' }
format.json { head :no_content }
end
end
What's the proper way to implement a link and controller action to destroy all posts for a particular user?
Edit:
config/routes.rb
resources :users do
resources :posts, shallow: true
end
Edit 2:
resources :users do
#resources :posts, shallow: true
resources :posts, shallow: true do
delete :destroy_all, on: collection
end
end
gives no block given (yield) error
aww my bad.. Just found the error.. forgot to add : to collection
I would pass an array of post IDs only if selected posts need to be deleted. If you want to delete all posts for a particular user, then here's how I would approach it:
config/routes.rb
resources :users do
resources :posts do
delete :destroy_all, on: :collection
end
end
Here, on: :collection means that the route applies to the collection of posts; the route therefore looks like this:
/users/:user_id/posts/destroy_all
You can read more about adding member and collection routes in the Rails Guides:
http://guides.rubyonrails.org/routing.html#adding-more-restful-actions
app/controllers/posts_controller.rb
def destroy_all
user = User.find(params[:user_id])
user.posts.destroy_all
# redirect somewhere
end
app/views/posts/index.html.erb
<%= link_to(
"Delete all posts!",
destroy_all_user_posts_path,
method: :delete
) %>
If you want to delete all posts for the current_user, modify like so:
config/routes.rb
resources :posts do
delete :destroy_all, on: :collection
end
app/controllers/posts_controller.rb
def destroy_all
current_user.posts.destroy_all
# redirect somewhere
end
app/views/posts/index.html.erb
<%= link_to(
"Delete all posts!",
destroy_all_posts_path,
method: :delete
) %>
Hope that helps.
I would create a separate controller method that accepts an array of post ids.
posts_controller.rb
def destroy_all
posts = Post.where(:id => params[:post_ids])
posts.delete_all
redirect_to :back
end
You will also need to supply the ids to the view method.
posts_controller.rb
def index
...
#posts_ids = Post.find(... how ever you need to select all posts...).pluck(:id)
...
end
views/posts/index.html.erb
...
<%= link_to destroy_all_posts_path(:post_ids => #posts_ids), :method => :destroy %>
...
You will also need to supply the route.
routes.rb
resources :users do
resources :posts
delete :destroy_all
end
end
And that should be it :)
You can use:
def destory_posts(user)
user.posts.destroy_all
render :nothing => true
end
add this method to your routes file.
Create a link like destory_posts_path(current_user) from where you want to delete the posts.
I'm using the thumbs_up gem in my application and I'm trying to figure out the best way to save votes in my controller while iterating.
Here is my model:
class Vendor < ActiveRecord::Base
has_many :inventory_items
has_many :items, through: :inventory_items
has_many :shopping_lists, through: :inventory_items
acts_as_voteable
end
my current controller:
def vote_for_vendor
# not sure what to put in here
end
def vote_against_vendor
# not sure what to put in here
end
my current view:
<% provide(:title, 'Stores') %>
<table class="table table-condensed table-hover">
<tr>
<th>Name</th>
<th>Address</th>
<th>Favorite?</th>
</tr>
<% #vendors.each do |v| %>
<tr>
<td><%= v.name %></td>
<td><%= v.address %></td>
<td>
<% if current_user.voted_for(v) %>
<%= link_to 'unlike', vote_against_vendor_vendors_path(vendor_id: v.id), :remote => true %>
<% else %>
<%= link_to 'like', vote_for_vendor_vendors_path(vendor_id: v.id), :remote => true %>
<% end %>
</td>
</tr>
</table>
Most of the examples I've seen have used params([]) to pass the relevant information to the controller. I don't really have params because this is just my index page that shows all vendors. How can I save votes using this gem while iterating? Thanks in advance!
Updated Controller w/ help from MrYoshi
class VendorsController < ApplicationController
def index
#vendors = Vendor.all
end
def vote_for_vendor
vendor = Vendor.find(params[:vendor_id])
current_user.vote_for(vendor)
respond_to do |format|
format.js
end
end
def vote_against_vendor
vendor = Vendor.find(params[:vendor_id])
current_user.vote_against(vendor)
respond_to do |format|
format.js
end
end
end
my routes:
resources :vendors do
collection { post :vote_for_vendor }
collection { post :vote_agaist_vendor }
end
Current Server Error
Started GET "/vendors/vote_for_vendor?vendor_id=4" for 127.0.0.1 at 2013-09-06 10:07:29 -0700
AbstractController::ActionNotFound (The action 'show' could not be found for VendorsController):
...........
Rendered /Users/#Myname/.rvm/gems/ruby-2.0.0-p195/gems/actionpack-3.2.13/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb within rescues/layout (0.5ms)
I give you the start of what you want, and you will be able to do the rest by yourself I think:
View:
<% if current_user.voted_for(v) %>
<%= link_to 'unlike', vote_against_vendor_vendors_path(vendor_id: v.id), :remote => true %>
<% else %>
<%= link_to 'like', vote_for_vendor_vendors_path(vendor_id: v.id), :remote => true %>
<% end %>
Controller:
def vote_for_vendor
vendor = Vendor.find(params[:vendor_id])
current_user.vote_for(vendor)
render :nothing => true
end
And the vote_against is pretty simple to guess now that you have this one above ;)
For your 'current server error' (AbstractController::ActionNotFound (The action 'show' could not be found for VendorsController):), you need to add a member route to your config/routes.rb file, like this:
resources :vendors do
member do
post 'vote_for_vendor'
post 'vote_against_vendor'
end
end
You are currently using collection routes which are for resources that don't need a particular ID to work. I highly recommend reading the Rails Guide on routes:
Rails Guide Documentation on Routes