6

I have successfully been able to implement the addition of new artist entries which was not included in Ryan Bates railscast #258 http://railscasts.com/episodes/258-token-fields

So in other words, a user can enter an artist name which will autocomplete using jquery tokinput. However, I'd like the autocomplete results to only display the artist names which were created by that individual user.

Does this make sense? An better and more understandable example would be for a collection.rb, where users create posts and specify a 'collection' for the post to belong to, but they should only be able to add posts to the collections which they created themselves.

This is the post form with artist_tokens as a virtual attribute:

<%= form_for @post, :validate => true, :html => {:multipart => true} do |f| %>
  <%= render 'shared/error_messages', :object => f.object %>
    <div class="field">
      <%= f.label :title, 'Title:' %><br /> 
      <%= f.text_field :title %><br />

      <%= f.label :artist_tokens, "Artists" %><br />
      <%= f.text_field :artist_tokens, "data-pre" => 
       @post.artists.map(&:attributes).to_json %>

    </div>
  <div class="actions">
    <%= f.submit "Submit" %>
  </div>
<% end %>

This finds the artist by the value entered into the artist_tokens field on the post form, and I also added the option "Add {params[:q]}" to add new entries.

class ArtistsController < ApplicationController

  def index
    @artists = Artist.where("name like ?", "%#{params[:q]}%")
    results = @artists.map(&:attributes)
    results << {:name => "Add: #{params[:q]}", :id => "CREATE_#{params[:q]}_END"}

    respond_to do |format|
      format.html
      format.json { render :json => results }
    end
  end

I added the additional code to parse the 'new' entries by id and then create a new artist with them. Then the artist_ids are assigned again.

post.rb
 def artist_tokens=(ids)
    ids.gsub!(/CREATE_(.+?)_END/) do
      Artist.create!(:name => $1).id
    end
    self.artist_ids = ids.split(",")
  end

Everything works great except the ability to narrow the json results by only the current_user's entries. How would I go about doing this? Do I need to store the user_id of the entries creator in the table? How can I do this?

EDIT: associations for models

# app/models/user.rb

class User < ActiveRecord::Base
  has_many :posts
  has_many :artists, :through => :posts

end



# app/models/post.rb

class Post < ActiveRecord::Base

  belongs_to :user
  has_many :artisanships
  has_many :artists, :through => :artisanships

end

# all/models/artist.rb

class Artist < ActiveRecord::Base

  has_many :artisanships
  has_many :users, :through => :artisanships
  has_many :posts, :through => :artisanships

end


# app/models/artisanship.rb

class Artisanships < ActiveRecord::Base

  belongs_to :post
  belongs_to :artist
  has_one :user, :through => :post

end

EDIT: posts_controller.rb

class PostsController < ApplicationController

  before_filter :authenticate_user!, :only => [:create, :edit, :update, :destroy]
  before_filter :authorized_user, :only => [:destroy, :edit, :update]

  def create
    @user = current_user
    @post = current_user.posts.build(params[:post])
    if @post.save
      flash[:success] = "Post created!"
      redirect_to root_path
    else
       @feed_items = current_user.feed.paginate(:per_page => "10", :page => params[:page])
      render 'pages/home'
    end
  end

  def index
    @posts = Post.paginate(:page => params[:page])
  end

  def show
    @post = Post.find(params[:id])
  end

  def edit
    @post = Post.find(params[:id])
  end

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

  def destroy
    @post.destroy
    redirect_to root_path
  end

  def likers
     @title = "Likers"
     @post = Post.find(params[:id])
     @likers = @post.likers.paginate(:page => params[:page])
     render 'show_likers' 
   end

   def search
     if params[:q]
       query = params[:q]
       @search = Post.search do
         keywords query
       end
       @posts = @search.results
     end
   end


private
  def authorized_user
    @post = Post.find(params[:id])
    redirect_to root_path unless current_user?(@post.user)
  end

Edit: Attempted alias_method_chain to set post's user_id attribute first. (didn't work to fix the NULL db entries) referneced from: Rails 3: alias_method_chain still used?

 def attributes_with_user_id_first=(attributes = {})
    # Make sure not to accidentally blank out the important_attribute when none is passed in
    if attributes.include?(:user_id)
      self.user_id = attributes.delete(:user_id)
    end

    self.attributes_without_user_id_first = attributes
  end
  alias_method_chain :attributes=, :user_id_first
Community
  • 1
  • 1
trying_hal9000
  • 4,343
  • 8
  • 43
  • 62
  • What is `collection.rb` you mentioned it earlier but skipped it completely later. How are `Post` and `Artist` related? What exactly do you want to do? – rubish Jul 22 '11 at 07:43
  • Post and artist have a joined table called artisanships (using artist_ids and post_ids). If you are familiar with jquery tokeninput, I added the ability to add new entries. I'd like the auto-completion to only include entries created by the user. – trying_hal9000 Jul 22 '11 at 09:58
  • Collection.rb doesnt exist it's just an example to better understand how I'd like to use jquery tokeninput, you can think of collections in place of artists. A user creates a post and types in a collection name, if its a new collection they select "add: "new collection name" if its an old collection they just select it. If a user creates a collection they can only select from their own collections. Make more sense? – trying_hal9000 Jul 22 '11 at 10:02
  • Can you update the question with code defining relationships between `User`, `Post` and `Artist` – rubish Jul 23 '11 at 00:21

2 Answers2

3

If you don't want to modify anything in your models, then you can do it like this:

def index
  @artists = current_user.posts.join("artisianships").join("artists").
               where("artisianships.post_id = posts.id").
               where("artists.name like ?", "#{params[:q]}").
               select("artists.name as name, artists.id as id")
  results = @artists.map(&:attributes)
  results << {:name => "Add: #{params[:q]}", :id => "CREATE_#{params[:q]}_END"}

  respond_to do |format|
    format.html
    format.json { render :json => results }
  end
end

Notice that there are a lot of joins going on here, so not recommended.

To debug why Artist.create!(:name => $1, :user_id => self.user_id) would not work, it would be great if we can see some more code, particularly the action to create a Post

UPDATE: Did you try changing PostController#create to following, although I feel curent code should work as it is,

@post = current_user.posts.build
if @post.update_attributes(params[:post])
   # do something
else
   # do something else
end

I am not a rails pro and don't understand the alias_method_chain so can't comment on why it didn't work.

rubish
  • 10,887
  • 3
  • 43
  • 57
  • I think you're on the right track here. I tried debugging the the :user_id => self.user_id issue and thought it was due to the order that the post attributes were set. I tried using alias_method_chain on the :attributes= method to set the user_id attribute on the post first so when self.user_id was called it wasn't NULL. This didn't seem to work. I'll update the code above with my posts_controller.rb and the code I tried using alias_method_chain. – trying_hal9000 Jul 25 '11 at 00:37
  • I still cant seem to get it working, I read elsewhere that this is because the post is only a ruby object until its saved and becomes an ActiveRecord object, so until its saved it doesn't have an id. I think I need to use accepts_nested_attributes_for but confused as to how. Any ideas? Thanks again – trying_hal9000 Jul 27 '11 at 02:52
  • That's not true. It is an `ActiveRecord` object, just not tied up with any row in database. Can you put a extra line `puts @post.inspect` after the line `@post = current_user.posts.build` and post the output from console. – rubish Jul 27 '11 at 04:19
  • I just tried your code above for the PostController#create method and I must have been doing it wrong before because now its all working. The user_id is being stored in the artist table. I'm in total shock but so happy. Thanks so much. Could you explain a little how your code '@post = current_user.posts.build if @post.update_attributes(params[:post])' is different from '@post = current_user.posts.build(params[:post]) if @post.save' – trying_hal9000 Jul 27 '11 at 04:44
  • Essentially, they both are same, it was just an arrow fired in the dark. If it didn't worked earlier, it might be due to ordering of things in internals of `build` method. Most probably, it assigns the passed attributes before setting the associated object ID. – rubish Jul 27 '11 at 05:27
  • Ah okay, that's what I was trying to fix with the alias_method_chain by setting the the object id first but couldn't seem to get it working 200%. I guess its a lucky break that this order of things solved it. Thanks again – trying_hal9000 Jul 27 '11 at 07:10
2

if you're using some authentication solution like devise, clearance or authlogic, you probably already have current_user method. Therefore add a column user_id:integer to the Artist model and do the following:

#User model
has_many :artists

#Artist model
belongs_to :user

#Artists controller
def index
    @artists = current_user.artists.where("name like ?", "%#{params[:q]}%") #this answers your question!
    ... blah blah
end

And of course do not forget to set the user_id column when you create an artist.

Tadas T
  • 2,492
  • 20
  • 20
  • Hi, thanks for the help. I think you're about halfway to answering my question! I've tried unsuccessfully to populate the user_id column for the artist model. Could you explain how I'd add the user_id when I create an artist? I tried adjusting the artist_tokens method with Artist.create!(:name => $1, :user_id => self.user_id).id but the db keeping showing NULL entries. – trying_hal9000 Jul 23 '11 at 23:20
  • How do you assign user_id to a new post in controller? If you do something like current_user.build_post(params[:post]) in PostsController#create, then user_id should be assigned and your attempt should succeed. If it does not, restart your server. If it does not help, add controller code to your question. – Tadas T Jul 24 '11 at 12:34
  • I added controller code above but I need help with adding the user_id to artist model not the post model. The user_id is assigned to the post model but I can't get the user_id to assign to the artist model, I keep getting null entries. – trying_hal9000 Jul 25 '11 at 01:15