In my application I have a "bookings" table, and an "extras" table.
This is a many-many relationship. Therefore I have created a middle table called "additions"
I've used the "has_many :through" to establish the relationship between the tables:
class Booking < ActiveRecord::Base
has_many :additions
has_many :extras, :through => :additions
class Extra < ActiveRecord::Base
has_many :additions
has_many :extras, :through => :additions
class Addition < ActiveRecord::Base
belongs_to :booking
belongs_to :extra
This seems to work. I added a few extras to some existing bookings manually (by adding numbers to the additions table), and wrote code so that when you click to show a booking, it lists all associated extras.
Now I need to make it so that when you make a booking - the "extras" are saved into the middle (additions) table.
I have checkboxes on my bookings form page:
<%= f.label 'Extras:' %>
<%= f.collection_check_boxes :extra_ids, Extra.all, :id, :extra_info %>
But obviously, the choices just get discarded when the user clicks on save.
I need some code to go (in the controller?) to make it save these "extras" into the "additions table" ?
Any ideas, as I can't work out how to do this?!
Thanks!
class BookingsController < ApplicationController
respond_to :html, :xml, :json
before_action :find_room
# before_action :find_extra
def index
@bookings = Booking.where("room_id = ? AND end_time >= ?", @room.id, Time.now).order(:start_time)
respond_with @bookings
end
def new
@booking = Booking.new(room_id: @room.id)
end
def create
@booking = Booking.new(params[:booking].permit(:room_id, :start_time, :length, :user_id))
@booking.room = @room
if @booking.save
redirect_to room_bookings_path(@room, method: :get)
else
render 'new'
end
end
def show
@booking = Booking.find(params[:id])
end
def destroy
@booking = Booking.find(params[:id]).destroy
if @booking.destroy
flash[:notice] = "Booking: #{@booking.start_time.strftime('%e %b %Y %H:%M%p')} to #{@booking.end_time.strftime('%e %b %Y %H:%M%p')} deleted"
redirect_to room_bookings_path(@room)
else
render 'index'
end
end
def edit
@booking = Booking.find(params[:id])
end
def update
@booking = Booking.find(params[:id])
# @booking.room = @room
if @booking.update(params[:booking].permit(:room_id, :start_time, :length, :user_id))
flash[:notice] = 'Your booking was updated succesfully'
if request.xhr?
render json: {status: :success}.to_json
else
redirect_to resource_bookings_path(@room)
end
else
render 'edit'
end
end
private
def save booking
if @booking.save
flash[:notice] = 'booking added'
redirect_to room_booking_path(@room, @booking)
else
render 'new'
end
end
def find_room
if params[:room_id]
@room = Room.find_by_id(params[:room_id])
end
end
# def find_extra
# if params[:extra_id]
# @extra = Extra.find_by_id(params[:extra_id])
# end
# end
# If resource not found redirect to root and flash error.
def resource_not_found
yield
rescue ActiveRecord::RecordNotFound
redirect_to root_url, :notice => "Booking not found."
end
def booking_params
params.require(:booking).permit(:user_id, :extra_id)
end
end
------------------------
class AdditionsController < ApplicationController
before_action :set_addition, only: [:show, :edit, :update, :destroy]
# GET /additions
def index
@additions = Addition.all
end
# GET /additions/1
def show
end
# GET /additions/new
def new
@addition = Addition.new
end
# GET /additions/1/edit
def edit
end
# POST /additions
def create
@addition = Addition.new(addition_params)
if @addition.save
redirect_to @addition, notice: 'Addition was successfully created.'
else
render :new
end
end
# PATCH/PUT /additions/1
def update
if @addition.update(addition_params)
redirect_to @addition, notice: 'Addition was successfully updated.'
else
render :edit
end
end
# DELETE /additions/1
def destroy
@addition.destroy
redirect_to additions_url, notice: 'Addition was successfully destroyed.'
end
private
# Use callbacks to share common setup or constraints between actions.
def set_addition
@addition = Addition.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def addition_params
params.require(:addition).permit(:booking_id, :extra_id, :extra_name)
end
end
--------------------------------------
# @author Stacey Rees <https://github.com/staceysmells>
class ExtrasController < ApplicationController
# @see def resource_not_found
around_filter :resource_not_found
before_action :set_extra, only: [:show, :edit, :update, :destroy]
def index
@extras = Extra.all
end
def show
end
def new
@extra = Extra.new
end
def edit
end
def create
@extra = Extra.new(extra_params)
if @extra.save
redirect_to @extra, notice: 'Extra was successfully created.'
else
render :new
end
end
def update
if @extra.update(extra_params)
redirect_to @extra, notice: 'Extra was successfully updated.'
else
render :edit
end
end
def destroy
@extra.destroy
redirect_to extras_url, notice: 'Extra was successfully destroyed.'
end
private
# Use callbacks to share common setup or constraints between actions.
def set_extra
@extra = Extra.find(params[:id])
end
# If resource not found redirect to root and flash error.
def resource_not_found
yield
rescue ActiveRecord::RecordNotFound
redirect_to root_url, :notice => "Room Category not found."
end
# Only allow a trusted parameter "white list" through.
def extra_params
params.require(:extra).permit(:extraimg, :name, :description, :quantity, :price, :extracat_id)
end
end
What you're doing here is working with nested form attributes. It's a bit complex, but it's also something people do often, so there are some good resources available.
I suggest you look at this post: http://www.sitepoint.com/complex-rails-forms-with-nested-attributes/
In particular, the section named 'More Complicated Relationships' specifically has an example of using nested attributes to set up a many-to-many association using has_many :through
.
The key pieces (which commenters have already pointed out) are going to be accepts_nested_attributes_for :extras
in your Booking model, and a f.fields_for :extras
block in the view. You'll also need to modify your booking_params
method to permit the nested values. There are a couple of strong parameters gotchas that you can potentially run into with that, so you may need to review the documentation.