1

I'm trying to get my head around the best way to add a record to a join table through alternative controllers in rails.

I have various models in my app that will require this, but I'm focusing on these two first before I transcribe the method into others, so shall use this as the example. I have a Venue and Interest model which are to be connected through VenuesInterests model (it has a couple of extra optional attributes so isn't a HABTM relationship). A user can admin a Venue instance and/or an Interest instance and therefore there should be an ability to select Venues to attach to an Interest and likewise Interests to attach to a Venue. This should be done with an Add Venues link on the Interest instance view and an Add Interests link on the Venue instance view. This would then take the user to a list of the relevant instances for them to select ones they would like to select.

Here are my models:

Venue.rb

class Venue < ActiveRecord::Base


has_many :interests, through: :venue_interests
    has_many :venues_interests, dependent: :destroy

    accepts_nested_attributes_for :venues_interests, :allow_destroy => true

end

Interest.rb

class Interest < ActiveRecord::Base

has_many :venues, through: :venue_interests
    has_many :venues_interests, dependent: :destroy

end

VenuesInterests.rb

class VenuesInterest < ActiveRecord::Base

    belongs_to :interest
belongs_to :venue
validates :interest_id, presence: true
validates :venue_id, presence: true

end

This all seems fine, however it's the controller and views that I'm struggling with. I've tried adding an extra method add_interest to the Venues controller to do the job of the create method in the VenuesInterests controller, so that there will be a different view when adding Venues to an Interest than there would be adding Interests to a Venue, otherwise I don't know how I would do this. My current Venues controller is as follows:

VenuesController.rb:

class VenuesController < ApplicationController
before_filter :authenticate_knocker!, only: [:new, :edit, :create, :update, :destroy]

respond_to :html, :json

def index
    @venues = Venue.all.paginate(page: params[:page]).order('created_at DESC')
end

def show
    @venue = Venue.find(params[:id])
    @hash = Gmaps4rails.build_markers(@venue) do |venue, marker|
        marker.lat venue.latitude
        marker.lng venue.longitude
        marker.infowindow venue.name
    end
end

def new
    @venue = Venue.new
end

def edit
    @venue = Venue.find(params[:id])
end

def create
    @venue = current_knocker.venues.create(venue_params)
    respond_to do |format|
        if @venue.save!
            format.html { redirect_to @venue, notice: 'Venue was successfully created.' }
            format.json { render json: @venue, status: :created, location: @venue }
        else
            format.html { render action: "new" }
            format.json { render json: @venue.errors, status: :unprocessable_entity }
        end
    end
end

def update
    @venue = Venue.find(params[:id])
    @venue.update_attributes(venue_params)
    respond_to do |format|
        if @venue.update_attributes(venue_params)
            format.html { redirect_to(@venue, :notice => 'Your Venue was successfully updated.') }
            format.json { respond_with_bip(@venue) }
        else
            format.html { render :action => "edit" }
            format.json { respond_with_bip(@venue) }
        end
    end
end

def destroy
end

def add_interests
    @venues_interests = VenuesInterest.new
    @interests = Interests.all.paginate(page: params[:page]).order(:name)
end

private

def venue_params
    params.require(:venue).permit(:admin... etc)
end

end

This isn't currently working as I'm not sure how to reference other classes within a controller, but the important thing I'd like to know is is there a better way to do this or am I (kind of) on the right track? If anyone has a good method (perhaps a jQuery plugin) for allowing multiple selection of instances for the view, that would be great too!

Dave C
  • 367
  • 5
  • 19
  • 1
    Your update method is open to privilege escalation: `@venue = Venue.find(params[:id])` --> `current_knocker.venues.find(params[:id])` – Damien Roche Jun 15 '14 at 18:15
  • If you build your form properly, you should have `params[:venue][:interest_ids]`, which will then assign interests to venue if you update your venue_params to permit `:interest_ids => []`. On the frontend, that would involve a multi-select (see [chosen](http://harvesthq.github.io/chosen/)). – Damien Roche Jun 15 '14 at 18:19
  • Chosen seems like a good option, but what I'm really looking for the ability to show an avatar and details of a model (ie the Interest) so will need to load a new page for the select, probably into a modal. Would the best approach be to include a form in an `add_interest.html.erb` view and render a list of partials, then use an add_interest method in the Venue controller? – Dave C Jun 15 '14 at 18:48

1 Answers1

1

In my opinion, I would take advantage of the existing update method to add the relationship between Interest and Venue. I can do like this:

def update
    @venue = Venue.find(params[:id])
    @venue.update_attributes(params[:venue_params])
    if params[:interest_ids].present?
       @venue.interests = Interest.where(id: params[:interest_ids])
       @venue.save
    end
   #more code to handle the rendering
end
The Lazy Log
  • 3,564
  • 2
  • 20
  • 27
  • Would I then keep the add_interests.html.erb for the view? If so, how would I relate this to the update method? – Dave C Jun 15 '14 at 14:00
  • Finally worked out how to implement this properly. For anyone looking, try [this link](http://stackoverflow.com/questions/8826407/rails-3-multiple-select-with-has-many-through-associations). – Dave C Jun 18 '14 at 00:11