You can use the tag_ids
method on a Post
instance.
In your controller create the tag hash. Better still cache the tag hash.
Add this to your application_controller.rb
.
def all_tags
@all_tags ||=Rails.cache.fetch('Tag.all', :expire_in => 15.minutes)){ Tag.all }
# without caching
#@all_tags ||= Tag.all
end
def all_tags_hash
@all_tags_hash ||= all_tags.inject({}){|hash, tag| hash[tag.id]=tag;hash}
end
def all_tags_by_ids ids
ids ||= []
ids = ids.split(",").map{|str| str.to_i} if ids.is_a?(string)
all_tags_hash.values_at(*ids)
end
helper_method :all_tags, :all_tags_hash, :all_tags_by_id
Now your partial can be rewritten as
# posts/_item.html.haml
- all_tags_by_ids(post.tag_ids).each do |tag|
%li
%a{:href => "/tags/#{tag.name}"}= tag.name.titleize
My solution caches the Tag
models for 15 minutes. Make sure you add an observer/filter on Tag model to invalidate/update the cache during create/update/delete.
In your config\environment.rb
config.active_record.observers = :tag_observer
Add a tag_observer.rb
file to your app\models
directory.
class TagObserver < ActiveRecord::Observer
def after_save(tag)
update_tags_cache(tag)
end
def after_destroy(tag)
update_tags_cache(tag, false)
end
def update_tags_cache(tag, update=true)
tags = Rails.cache.fetch('Tag.all') || []
tags.delete_if{|t| t.id == tag.id}
tags << tag if update
Rails.cache.write('Tag.all', tags, :expire_in => 15.minutes)
end
end
Note: Same solution will work with out the cache also.
This solution still requires you to query the tags
table for Tag
ids. You can further optimize by storing tag ids as a comma separated string in the Post
model(apart from storing it in post_tags
table).
class Post < ActiveRecord::Base
has_many :post_tags
has_many :tags, :through => :post_tags
# add a new string column called tag_ids_str to the `posts` table.
end
class PostTag < ActiveRecord::Base
belongs_to :post
belongs_to :tag
after_save :update_tag_ids_str
after_destroy :update_tag_ids_str
def update_tag_ids_str
post.tag_ids_str = post.tag_ids.join(",")
post.save
end
end
class Tag < ActiveRecord::Base
has_many :post_tags
has_many :posts, :through => :post_tags
end
Now your partial can be rewritten as
# posts/_item.html.haml
- all_tags_by_ids(post.tag_ids_str).each do |tag|
%li
%a{:href => "/tags/#{tag.name}"}= tag.name.titleize