0

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

2 Answers 2

1

If you need to acces the object of a form, you need to write f.object, so I think you should just write f.object.artists.

Sign up to request clarification or add additional context in comments.

10 Comments

Would you happen to know where I'd put this? Right now I have this in my _track_fields partial but get the error: uninitialized constant Track::FeaturedArtist with this code: <%= f.input :artist_tokens, label: 'Featured Artists', input_html: {"data-pre" => f.object.artists.map(&:attributes).to_json} %>
In your model you write that the artists are linked through featured artists, and that is the model that is not found. Can you check that track.artists works at all? You also have not shown use the definition for featured_artists.
Unfortunately not. I get the error: undefined local variable or method 'track' for #<#<Class:0x007fdd978285f8>:0x007fdd97832d28>. FeaturedArtist serves only as a join table between artists and tracks
Either you use a habtm which uses an implicit join table, or define the class FeaturedArtist. I would opt for the latter. Using the f.object solves your question imho. Please note that you can easily test your code using the rails console, so you can verify individual parts work (like retrieving the artists for a track).
Isn't FeaturedArtist already defined though? Would I need to add any additional code to what I already have besides the two associations: belongs_to :artist belongs_to :track. Everything seems to work fine in the console. Its the view code I'm having the worst time with.
|
0

Your "data-pre" => f.artists... is calling the artists method on f which is the form builder and doesn't have an #artists method.

Try this instead:

In the album form, change the render partial line to this:

<%= render 'track_fields', :f => track, :artists => @artists %>

And then use this in the track partial:

<%= f.input :artist_tokens, label: 'Featured Artists', input_html: {"data-pre" => artists.map(&:attributes).to_json} %>

UPDATED

Let's back up a step. From your code it looks like you need to populate a data-pre attribute with the attributes of a collection of artists.

The problem is you're calling f.artists where f is the FormBuilder and doesn't know anything about artists. This is why you're getting undefined method 'artists'...

The solution is make a collection of artists available to the view and its partials. One way to do this:

class AlbumsController < ApplicationController
  ...
  def new
    @album = Album.new
    @artists = Artist.order(:name) # or some other subset of artists
  end

  ...

  def edit
    @album = Album.find params[:id]
    @artists = Artist.order(:name) # or perhaps "@artists = @album.artists", or some other subset of artists
  end
end

and then in new.html.erb and edit.html.erb, pass @artists to the form partial:

... # other view code
  <%= render 'form', album: @album %>
... # other view code

and then in your form partial:

... # other view code
<%= f.simple_fields_for :tracks do |track_form| %>
  <%= render 'track_fields', :f => track_form %>
<% end %>
... # other view code

finally, in your track partial:

... # other view code
<div class="input">
  <%= f.input :artist_tokens, label: 'Featured Artists', input_html: {"data-pre" => @artists.map(&:attributes).to_json} %>
</div>
... # other view code

Does that make sense?

6 Comments

With this I got: undefined method 'map' for nil:NilClass
You showed the index action for the controller, but then some of your view code uses @album, which is not set in the index action. Is the problem in new.html.erb or index.html.erb? In either case, the corresponding controller action will need to set both @artists(like you did in the index action) and @album. If this doesn't help, can you post the controller action and corresponding view so I can take a look at both?
The problem is technically in my albums _form.html.erb which is rendered inside of my new and edit views. The reason I posted the artist's new action was to show how the json was retrieved for the jquery token functionality. I could post the rest of the controllers but none of the code was altered when they were generated via scaffold (not sure if that helps you or not). Are you recommending setting up an @albums variable in one of the controllers?
Still no luck. Despite creating those two instance variables its still complaining: undefined local variable or method 'artists' I created a repo to give you a better idea of what's going on. github.com/TKB21/nested_tokens
the problem occurs in app/views/albums/new.html.erb?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.