开发者

Rails 3: Polymorphic liking of Entities by User, how?

Background:

I followed the tutorial here to setup a polymorphic User favorites data model in my application. This allows me to let a User make pretty much any Entity in the system which I add 'has_many :favorites, :as => :favorable' line to its model a favorite. I plan on using this to implement a Facebook style 'Like' system as well as several other similar systems.

To start off I added the favoritability to a Post model (each user can create status updates like on Facebook). I have it all done and unit tested so I know the data model is sound and functioning from either side of the relationship (User and Post).

Details:

  • I have a Home controller with a single index method and view.

  • on the index view I render out the posts for the user and the user's friends

  • I want the user to be able to like posts from their friends

  • The Posts controller has only a create and a destroy method with associated routes (not a full fledged resource) and through the Post method via AJAX posts are created and deleted without issue

Where I am stuck

  • How do I add the link or button to add the post to the user's Favorites?

  • According to the tutorial the way to create a new Favorite through the polymorphic association is to do it from the Post.favorites.build(:user_id => current_user.id). From this direction the build handles pulling out the Post's ID and TYPE and all开发者_如何学C I have to do is pass in the user's id

  • Do I use an AJAX form post to a Favorites controller with a Create and Destroy method similar to the Post controller?

  • I am still struggling to uncross the wires in my brain from ASP.Net N-Tier web application development over to Rails MVC. Hasn't been too bad until now ;)

  • I bet there are Gems out there that might do this but I need to learn and the best way is to suffer through it. Maybe a tutorial or sample code from someone who has implemented liking functionality within their application would be helpful.

Thanks in advance for the assistance!


Jaap, I appreciate your comment on my question. After writing the question I pretty much didn't want to wait because the real learning takes place through trial and error, so I errored it up ;)

It turns out that what you suggested was pretty much in line with exactly what I ended up doing myself (it's always nice to find out that what you decide to do is what others would do as well, I love the sanity check value of it all).

So here is what I did and it is all working through post-backs. Now I just need to implement AJAX and style it:

My favorite model because my Polymorphic Favorites model requires that an Entity can only be favorited once by a user I added to the validations 'Scopes' which indicate that for each attribute it has to be unique in the scope of the other 2 required attributes. This solves the issue of multiple favorites by the same user.

class Favorite < ActiveRecord::Base
  before_save :associate_user

  belongs_to :favorable
  belongs_to :user

  # Validations
  validates :user_id, :presence => true,
            :uniqueness => {:scope => [:favorable_id, :favorable_type], :message => "item is already in favorites list."}
  validates :favorable_id, :presence => true,
            :uniqueness => {:scope => [:user_id, :favorable_type], :message => "item is already in favorites list."}
  validates :favorable_type, :presence => true,
            :uniqueness => {:scope => [:favorable_id, :user_id], :message => "item is already in favorites list."}

  # Callbacks
  protected

  def associate_user
    unless self.user_id
      return self.user_id = session[:user_id] if session[:user_id]
      return false
    end
  end

end

My User Model (that which is relevant): I added 2 methods, the get_favorites which is the same as favorable one from the tutorial and a Favorite? method which checks to see if the Entity in question has already been added to the user's favorites.

class User < ActiveRecord::Base  
  # Relationships
  has_many  :microposts, :dependent => :destroy
  has_many  :favorites

  # Methods
  def favorite?(id, type)
    if get_favorites({:id => id, :type => type}).length > 0
      return true
    end
    return false
  end

  def get_favorites(opts={})
    # Polymorphic Favoritability: allows any model in the
    # application to be favorited by the user.
    # favorable_type
    type = opts[:type] ? opts[:type] : :topic
    type = type.to_s.capitalize

    # add favorable_id to condition if id is provided
    con = ["user_id = ? AND favorable_type = ?", self.id, type]

    # append favorable id to the query if an :id is passed as an option into the
    # function, and then append that id as a string to the "con" Array
    if opts[:id]
      con[0] += " AND favorable_id = ?"
      con << opts[:id].to_s
    end

    # Return all Favorite objects matching the above conditions
    favs = Favorite.all(:conditions => con)

    case opts[:delve]
    when nil, false, :false
      return favs
    when true, :true
      # get a list of all favorited object ids
      fav_ids = favs.collect{|f| f.favorable_id.to_s}

      if fav_ids.size > 0
        # turn the Capitalized favorable_type into an actual class Constant
        type_class = type.constantize

        # build a query that only selects
        query = []
        fav_ids.size.times do
          query << "id = ?"
        end
        type_conditions = [query.join(" AND ")] + fav_ids

        return type_class.all(:conditions => type_conditions)
      else
        return []
      end
    end
  end

end

My Micropost Model (that which is relevant): note the Polymorphic association in the has_many relationship titled :favorites.

class Micropost < ActiveRecord::Base
  attr_accessible :content

  # Scopes
  default_scope :order => 'microposts.created_at DESC'

  # Relationships
  belongs_to  :user
  has_many  :favorites, :as => :favorable # Polymorphic Association

  # Validations
  validates :content, :presence => true, :length => { :minimum => 1, :maximum => 140 }
  validates :user_id, :presence => true

end

My Micropost Form: as you can see I am passing in the entity that will be mapped to the Favorite model as a local variable to the 2 Favorite forms as 'local_entity'. This way I can pull out the ID and the TYPE of the Entity for the Polymorphic association.

<div class="post">
    <span class="value">
      <%= micropost.content %>
    </span>
    <span>
        <% if current_user.favorite?(micropost.id, micropost.class.to_s) %>
            <%= render :partial => 'favorites/remove_favorite', :locals => {:local_entity => micropost} %>
        <% else %>
            <%= render :partial => 'favorites/make_favorite', :locals => {:local_entity => micropost} %>
        <% end %>
    </span>
    <span class="timestamp">
        Posted <%= time_ago_in_words(micropost.created_at) %> ago.
    </span>
    <div class="clear"></div>
</div>

My Make Favorite Form:

<%= form_for current_user.favorites.build do |f| %>
    <div><%= f.hidden_field :favorable_id, :value => local_entity.id %></div>
    <div><%= f.hidden_field :favorable_type, :value => local_entity.class.to_s %></div>
    <div class="actions"><%= f.submit "make favorite" %></div>
<% end %>

My Remove Favorite Form:

<%= form_for current_user.get_favorites(
                     {:id => local_entity.id,
                      :type => local_entity.class.to_s}),
                      :html => { :method => :delete } do |f| %>
    <div class="actions"><%= f.submit "remove favorite" %></div>
<% end %>


If you don't want to call this on the current_user, you would have to have these routes in your config/routes.rb to make nested routes for favorites on a user. I assume you have a Favorite model which belongs_to :user:

resources :users do
  resources :favorites
end

Then make sure your favorites controller loads the user in some kind of before_filter:

def load_user
  @user = User.load params[:user_id]
end

And then you can render a remote form to create a new favorite for any kind of object (it will only show a button):

<%= remote_form_for [@user, Favorite.new] do |f| -%>
  <%= f.hidden_field :favorable_type, object.class.to_s %>
  <%= f.hidden_field :favorable_id, object.id %>
  <%= f.submit 'Like' %>
<%- end -%>

You would have to render that form as a partial sending along an object (e.g. a Post) and then it will create an AJAX POST call to /users/:id/favorites/ which will create the favorite object and render some kind of javascript response in a create.rjs file.

I hope this helps. The code itself is untested, but it might get you moving.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜