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 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!
The basic structure of a polymorphic association (PA)sets up 2 columns in the comment table. (This is different from a typical one-to-many association, where we'd only need one column that references the id's of the model it belongs to). For a PA, the first column we need to create is for the selected model.
Polymorphic relationship in Rails refers to a type of Active Record association. This concept is used to attach a model to another model that can be of a different type by only having to define one association.
2.9 Polymorphic Associations A slightly more advanced twist on associations is the polymorphic association. With polymorphic associations, a model can belong to more than one other model, on a single association. For example, you might have a picture model that belongs to either an employee model or a product model.
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 love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With