Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 260
Lee McAlilly Avatar asked Jul 31 '18 04:07

Lee McAlilly


2 Answers

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.

like image 173
e_a_o Avatar answered Oct 20 '22 04:10

e_a_o


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.

like image 45
matthewd Avatar answered Oct 20 '22 03:10

matthewd