I've been on a big trouble for 2 days and need you help !! I have 2 models : garden and booking (with nested resources and using pundit). From the "show" of garden, I want to click on a button that "create"s the booking directly from the already created garden. But I don't succeed with the create action which always displays : param is missing or the value is empty: booking. How to make "booking" appearing in the params and be able to call for example @booking.booker ?? Do you have a solution without deleting the require(:booking) ?
I tried both either with or without a "new_booking" page but don't succeed.
Thank you soooo much !!
booking_controller
class BookingsController < ApplicationController
def index
end
def my_bookings
@bookings = BookingPolicy::Scope.new(current_user, Booking).scope.where(user: current_user)
end
def show
@booking.garden = @garden
end
def new
@garden = Garden.find(params[:garden_id])
@garden_id = @garden.id
@booker_id = current_user.id
@booking = Booking.new
authorize @booking
end
def create
@booking = Booking.new(booking_params)
authorize @booking
@booking.save
redirect_to root_path
end
private
def booking_params
params.require(:booking).permit(:garden_id, :booker_id)
end
end
gardens_controller
class GardensController < ApplicationController
before_action :set_garden, only: [:show, :destroy, :edit, :update ]
def new
@garden = Garden.new
authorize @garden
end
def create
@garden = Garden.new(garden_params)
authorize @garden
@garden.user = current_user
respond_to do |format|
if @garden.save
format.html { redirect_to @garden, notice: 'Garden was successfully created.' }
format.json { render :show, status: :created, location: @garden }
else
format.html { render :new }
format.json { render json: @garden.errors, status: :unprocessable_entity }
end
end
end
def index
@gardens = policy_scope(Garden)
end
def my_gardens
@gardens = GardenPolicy::Scope.new(current_user, Garden).scope.where(user: current_user)
end
def show
authorize @garden
end
def destroy
authorize @garden
@garden.destroy
respond_to do |format|
format.html { redirect_to gardens_url, notice: 'garden was successfully destroyed.' }
format.json { head :no_content }
end
end
def edit
authorize @garden
end
def update
authorize @garden
respond_to do |format|
if @garden.update(garden_params)
format.html { redirect_to @garden, notice: 'Garden was successfully updated.' }
format.json { render :show, status: :ok, location: @garden }
else
format.html { render :edit }
format.json { render json: @garden.errors, status: :unprocessable_entity }
end
end
end
private
def garden_params
params.require(:garden).permit(:title, :details, :surface, :address, :availabilities)
end
def set_garden
@garden = Garden.find(params[:id])
end
end
application_controller
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
before_action :authenticate_user!
include Pundit
# Pundit: white-list approach.
after_action :verify_authorized, except: [:index, :my_gardens], unless: :skip_pundit?
# after_action :verify_policy_scoped, only: [:index, :my_gardens], unless: :skip_pundit?
# Uncomment when you *really understand* Pundit!
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
def user_not_authorized
flash[:alert] = "You are not authorized to perform this action."
redirect_to(root_path)
end
private
def skip_pundit?
devise_controller? || params[:controller] =~ /(^(rails_)?admin)|(^pages$)/
end
end
pages_controller
class PagesController < ApplicationController
skip_before_action :authenticate_user!, only: [:home]
def home
end
end
application_record.rb
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
end
booking.rb
class Booking < ApplicationRecord
belongs_to :gardens
has_many :users, through: :gardens
end
garden.rb
class Garden < ApplicationRecord
belongs_to :user
has_many :bookings
end
user.rb
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :gardens
has_many :bookings, through: :gardens
end
application_policy.rb
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
@user = user
@record = record
end
def index?
false
end
def my_gardens?
false
end
def show?
scope.where(:id => record.id).exists?
end
def create?
false
end
def new?
create?
end
def update?
false
end
def edit?
update?
end
def destroy?
false
end
def scope
Pundit.policy_scope!(user, record.class)
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
@user = user
@scope = scope
end
def resolve
scope
end
end
end
booking_policy.rb
class BookingPolicy < ApplicationPolicy
class Scope < Scope
def resolve
scope.all
end
end
def create?
true
end
def update?
user_is_the_owner_or_admin?
end
def destroy?
user_is_the_owner_or_admin?
end
def index?
true
end
def show
scope.where(:id => record.id).exists?
end
private
def user_is_the_owner_or_admin?
user.admin || record.booker == user
end
end
garden_policy.rb
class GardenPolicy < ApplicationPolicy
class Scope < Scope
def resolve
scope.all #>> permet d'afficher tous les restos dans index
end
end
def create?
true
end
def update?
user_is_the_owner_or_admin?
end
def destroy?
user_is_the_owner_or_admin?
end
def index?
true
end
def show
scope.where(:id => record.id).exists?
end
private
def user_is_the_owner_or_admin?
user.admin || record.user == user
end
end
new_booking.html.erb
<h1>My booking</h1>
<%= simple_form_for [@garden, @booking] do |f| %>
<%= f.input :booker_id, disabled:true %>
<%= f.input :garden_id, disabled:true %>
<%= f.button :submit %>
<% end %>
show_garden.html.erb
<h1>My Garden</h1>
<ul>
<li>Title : <%= @garden.title %></li>
<li>Address : <%= @garden.address %></li>
<li>Details : <%= @garden.details %></li>
<li>Surface : <%= @garden.surface %></li>
<p> Here will appear the availabilities to select</p>
<% if policy(@garden).destroy? %>
<li><%= link_to "Delete" %> //
<% end %>
<% if policy(@garden).edit? %>
<%= link_to "Edit" %></li>
<% end %>
<li><%= link_to "home", gardens_path %></li>
<li><%= link_to "My gardens", my_gardens_path %></li>
<li><%= link_to "Book the Garden", new_garden_booking_path(@garden) %></li>
</ul>
routes.rb
Rails.application.routes.draw do
devise_for :users
delete 'gardens/:id', to: "gardens#destroy", as: :delete_garden
resources :gardens, except: [:delete] do
resources :bookings, only: [:new, :create, :show]
end
get "my_gardens", to: "gardens#my_gardens", as: :my_gardens
root to: 'pages#home'
end
The problem lies in the way you are creating a booking. I have a couple of suggestions. First, you don't need a form
to create a booking
since you are only passing values you can use a link_to
with aPOST
method instead.
First update your routes:
# ...
resources :gardens, except: [:delete] do
resource :booking, only: :create # notice resource is singular
end
Change the book the garden link to:
<%= link_to "Book the Garden", garden_booking_path(@garden), method: :post %>
To able to call booking.booker
you need to update your Booking
model association:
class Booking < ApplicationRecord
belongs_to :garden # notice garden is singular
belongs_to :booker, class_name: "User"
end
Then update your BookingsController
create action:
def create
@garden = Garden.find params[:garden_id]
@booking = @garden.bookings.build(booker: current_user)
# ... your pundit authorizations
if @booking.save
redirect_to @garden
end
end
Notice that after a booking is created the controller redirects to the garden. This will allow you to show the booking. I think is better this way but you can redirect to the root_path
if you prefer.