I'm using jQuery Tokeninput as shown in this Railscast. I'd like to combine this functionality in a nested form but get the error
undefined method `artists' for #<SimpleForm::FormBuilder:0x007febe0883988>
For some reason its not recognizing the track parameter in my form builder which is stopping me to get a hold of albums I have on record.
<div class="input">
<%= f.input :artist_tokens, label: 'Featured Artists', input_html: {"data-pre" => f.artists.map(&:attributes).to_json} %>
</div>
Keep in mind this works in my track
form but just not in my album
form since its nested. What should I do to get this to work?
class ArtistsController < ApplicationController
def index
@artists = Artist.order(:name)
respond_to do |format|
format.html
format.json {render json: @artists.tokens(params[:q])}
end
end
end
Models
class Artist < ActiveRecord::Base
has_many :album_ownerships
has_many :albums, through: :album_ownerships
has_many :featured_artists
has_many :tracks, through: :featured_artists
def self.tokens(query)
artists = where("name like ?", "%#{query}%")
if artists.empty?
[{id: "<<<#{query}>>>", name: "Add New Artist: \"#{query}\""}]
else
artists
end
end
def self.ids_from_tokens(tokens)
tokens.gsub!(/<<<(.+?)>>>/) {create!(name: $1).id}
tokens.split(',')
end
end
class Albums < ActiveRecord::Base
attr_reader :artist_tokens
accepts_nested_attributes_for :tracks, :reject_if => :all_blank, :allow_destroy => true
has_many :albums_ownerships
has_many :artists, through: :albums_ownerships
def artist_tokens=(ids)
self.artist_ids = Artist.ids_from_tokens(ids)
end
end
class Track < ActiveRecord::Base
attr_reader :artist_tokens
belongs_to :album
has_many :featured_artists
has_many :artists, through: :featured_artists
def artist_tokens=(ids)
self.artist_ids = Artist.ids_from_tokens(ids)
end
end
class AlbumOwnership < ActiveRecord::Base
belongs_to :artist
belongs_to :album
end
class FeaturedArtist < ActiveRecord::Base
belongs_to :artist
belongs_to :track
end
Album Form
<%= simple_form_for(@album) do |f| %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<h1>Tracks</h1>
<%= f.simple_fields_for :tracks do |track| %>
<%= render 'track_fields', :f => track %>
<% end %>
<div id='links'>
<%= link_to_add_association 'Add Field', f, :tracks %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Track Partial
<div class="field">
<%= f.input :name %><br>
</div>
<div class="input">
<%= f.input :artist_tokens, label: 'Featured Artists', input_html: {"data-pre" => f.artists.map(&:attributes).to_json} %>
</div>
JS
$(function() {
$('#track_artist_tokens').tokenInput('/artists.json', {
prePopulate: $("#track_artist_tokens").data("pre"),
theme: 'facebook',
resultsLimit: 5
});
});
UPDATE
As mentioned by nathanvda, I needed to use f.object
in order for the artists to be recognized. So in my partial I now have:
<%= f.input :artist_tokens, label: 'Featured Artists', input_html: {"data-pre" => f.object.artists.map(&:attributes).to_json, class: 'test_class'} %>
In my js I also needed to call the token input method before/after insertion:
$(function() {
$('.test_class').tokenInput('/artists.json', {
prePopulate: $(".test_class").data("pre"),
theme: 'facebook',
resultsLimit: 5
});
$('form').bind('cocoon:after-insert', function(e, inserted_item) {
inserted_item.find('.test_class').tokenInput('/artists.json', {
prePopulate: $(".test_class").data("pre"),
theme: 'facebook',
resultsLimit: 5
});
});
});
The only remaining issue I have is the the tracks_attributes
not being saved. I ran into an issue similar to this in the past in this post but the two main difference is the second level of nesting involved and that I used a join table within my nested form. I'm not entirely sure if or how any of that code would translate over but I believe this is most likely problem. As far as the permitted params of my albums_controller
here's what they looks like.
def album_params
params.require(:album).permit(:name, :artist_tokens, tracks_attributes: [:id, :name, :_destroy, :track_id])
end
If you need to acces the object of a form, you need to write
f.object
, so I think you should just writef.object.artists
.