How to use Selectize.js to find or create a rails belongs_to association?
Asked Answered
C

2

6

I'm having a hard time figuring out how to combine Selectize.js with a belongs_to association in rails. I want to do something like this photo:

enter image description here

I've attempted using accepts_nested_attributes, but that doesn't seem to work with a belongs_to relationship.

I tried doing an auto-complete association like this railscast episode.

What I'd really like to do is use a Selectize style collection select to create the "Speaker" association if it's already in the database, but add a new one if it doesn't yet exist. Selectize enables me to add a new one, but I'm having trouble passing that through the form to create the new record in the associated model.

Here are my models:

class Quote < ApplicationRecord
  belongs_to :speaker,  class_name: "Artist"
  belongs_to :topic,    class_name: "Artist"
end

Quote.rb

class Artist < ApplicationRecord
  has_many :spoken_quotes, class_name: "Quote", foreign_key: :speaker_id
  has_many :topic_quotes,  class_name: "Quote", foreign_key: :topic_id
end

Artist.rb

And my form:

<%= f.label :speaker, 'Who said it?' %>
<%= f.collection_select :speaker_id, Artist.order(:name), :id, :name, {prompt: 'Select an artist'}, {class: 'form-control select-artist'} %>

_form.html.erb

Controllers:

quotes_controller.rb

artists_controller.rb

How can I create a new Artist (as "Speaker") through the Quote.new form through the Selective-style collection select? The Selectize behavior is the user experience I'm looking for, I just can't figure out how to create the new Artist through the Quote form.

Cutpurse answered 31/7, 2018 at 4:36 Comment(3)
Why don't you add your code (relevant only, if possible) to the question itself instead of attaching the links?Quoin
Thanks @JagdeepSingh I edited the question with the relevant code.Cutpurse
where does it break? could you clearly distinct each step in this process and what are our clues on this malfunctions. 1) the user fills in the form (does it work? are the params submitted via http?) 2) the rails controller receives the request (what does not work here exactly? or is this totally outside your controller logic and inside the gem?) 3) model instance and saving (errors ..?)Vanna
D
5

If you must use the selectize user experience, you might need to create the artist/speaker via ajax using javascript. With the selectize-rails gem, jquery-rails and a bit of javascript code, you can either:

  1. create the artist/speaker via ajax and assign the value and id to the quotes form input - see demo or

  2. pop up a modal with the artist form, submit the form via ajax and assign the value and id to the quotes form input

I've attempted to scaffold this simple rails app with a basic structure of what you're trying to achieve to show you an example of option 1. I've included setup instructions and a demo in the readme.

Major changes required are:

Gem changes:

Add the selectize-rails and jquery-rails gems to your Gemfile and run bundle install.

HTML Form changes:

Add the selectize class to the collection_select input tag

# /views/quotes/_form.html.erb

<%= f.collection_select :artist_id, Artist.order(:name), :id, :name, {prompt: 'Select an artist'}, {class: 'selectize'} %>

Javascript changes:

Create /assets/javascript/quotes.js and make the following changes.

# /assets/javascript/quotes.js

$(document).on("turbolinks:load", function(){
  $(".selectize").selectize({
    create: function(input, callback) {
      $.post('/artists.json', { artist: { name: input } })
        .done(function(response){
          console.log(response)
          callback({value: response.id, text: response.name });
        })
    }
    });
})

Modify your artists_controller

Modify the artists_controller#create action method to be able to render json response.

  # POST /artists
  # POST /artists.json
  def create
    @artist = Artist.new(artist_params)

    respond_to do |format|
      if @artist.save
        format.html { redirect_to @artist, notice: 'Artist was successfully created.' }
        format.json { render :show, status: :created, location: @artist }
      else
        format.html { render :new }
        format.json { render json: @artist.errors, status: :unprocessable_entity }
      end
    end
  end

You can also watch this GoRails video to see how to achieve option 2.

Disgusting answered 3/8, 2018 at 13:39 Comment(3)
Really appreciate this! Most detailed response I've seen on Stackoverflow. I'm definitely attempting to achieve Option 1. I wasn't aware of how to create the artist through Ajax, so you've definitely pointed me in the right direction. First issue I ran into is that I have to pass speaker_id to the collection_select or else I get a method missing error on quotes/new, and then after that I had to remove protect_from_forgery with: :exception from my ApplicationController (as it also is in your demo app) or else I get this error—ActionController::InvalidAuthenticityToken. Is that secure?Cutpurse
Glad it helped in pointing you in the right direction. Actually, the demo app was made with rails 5.2 which has ActionController::Base.default_protect_from_forgery set to true by default. Can you add a link of what your code currently looks like so I can see if I can replicate the new errors you're having?Disgusting
Appreciate your help. Ended up switching to Option 2, with the modal because I realized there is another attribute other than just Name that needs to be set when creating a new artist.Cutpurse
T
1

If you're providing create-on-missing functionality, and the only needed value is the one in the selector input, consider skipping the id entirely:

class Quote < ApplicationRecord
  def speaker_name
    speaker&.name
  end

  def speaker_name=(name)
    self.speaker = name.presence && Artist.find_or_initialize_by(name: name)
  end

And over in the view:

<%= f.label :speaker, 'Who said it?' %>
<%= f.collection_select :speaker_name, Artist.order(:name), :name, :name, {prompt: 'Select an artist'}, {class: 'form-control select-artist'} %>

(with a matching change in QuotesController#quote_params)

Then when you Selectize-ify the input, allowing new values to be typed, it should Just Work. Notably, this avoids eagerly creating the artist record via AJAX, which can create orphans if someone types an entry but then never submits the form.

Tem answered 5/8, 2018 at 6:26 Comment(1)
This makes sense. Didn't realize I could create the association without the ID. Thanks for pointing that out.Cutpurse

© 2022 - 2024 — McMap. All rights reserved.