acts-as-taggable-on
acts-as-taggable-on copied to clipboard
Eager loading tags
Is there any way to eager load tags for each taggable? For example:
taggables = Taggable.find(:all, :include => [:base_tags]) taggables.each do |taggable| taggable.all_tags_list # << How do you prevent a separate SQL call for each taggable? end
I am on rails 2.3.5 so the base_tags.where call that retrieves all_tags_list is backported via named_scope with conditions, which I imagine causes a SQL hit for each record, regardless of any eager loading. Please let me know if this can be achieved somehow or if this is not possible/supported?
Best regards,
Anton
I'm also interested in this feature... nice for showing all the tags on a set of objects.
something i'd like to see as well.
this would rock
I am also trying to figure out how to eager load tags with a query -- is there a way?
I got it! It's very simple actually. I have a taggable model called Movie, so I do:
movies = Movie.all :include => [:scenes, :tags]
movies.each do |movie| movie.tags.each { |tag| puts tag.name } end
Hope this help, Vitor
vkiyoshi -- I don't think that will work in all cases, such as when you are searching across multiple joins -- and want to eager load the tags among other eager loads. If you examine the SQL in the situations I described, you will see that while the LEFT OUTER JOIN is added by :include => [:tags]
, the separate queries will still be run for each returned record.
I don't know, I've never tested that with more complex queries... But in most cases - like displaying tags for a list of taggables - calling tags method for each item, instead of tag_list will work.
Best regards,
Vitor Kiyoshi Arimitsu web developer | vitork.com
Em 18/05/2011, às 19:54, ms-ati escreveu:
vkiyoshi -- I don't think that will work in all cases, such as when you are searching across multiple joins -- and want to eager load the tags among other eager loads. If you examine the SQL in the situations I described, you will see that while the LEFT OUTER JOIN is added by
:include => [:tags]
, the separate queries will still be run for each returned record.Reply to this email directly or view it on GitHub: https://github.com/mbleigh/acts-as-taggable-on/issues/91#comment_1200483
Eager loading of tags would be very useful.
Did anyone find a way to do this?
What is the different between the methods tag and tag_list apart from one returns the string repr of the tag and the other the full tag object. Why is it possible to eager load on tag but not on tag_list?
Using Ernie Miller's Squeel AR Adaptor I can do in the console
ruby-1.9.2-p180 :013 > Log.includes{context.taggings.tag}.limit(4).each do |l| puts l.context.inspect end; nil
Log Load (3.3ms) SELECT "logs".* FROM "logs" ORDER BY date_of_entry DESC LIMIT 4
SQL (0.3ms) SELECT "taggings"."id" AS t0_r0, "taggings"."tag_id" AS t0_r1, "taggings"."taggable_id" AS t0_r2, "taggings"."taggable_type" AS t0_r3, "taggings"."tagger_id" AS t0_r4, "taggings"."tagger_type" AS t0_r5, "taggings"."context" AS t0_r6, "taggings"."created_at" AS t0_r7, "tags"."id" AS t1_r0, "tags"."name" AS t1_r1 FROM "taggings" LEFT OUTER JOIN "tags" ON "tags"."id" = "taggings"."tag_id" WHERE "taggings"."taggable_type" = 'Log' AND "taggings"."taggable_id" IN (1127, 1126, 1125, 1124) AND (taggings.tag_id = tags.id AND taggings.context = 'context')
ActsAsTaggableOn::Tagging Load (4.0ms) SELECT "taggings".* FROM "taggings" WHERE "taggings"."tag_id" IN (252, 256, 254)
ActsAsTaggableOn::Tag Load (0.2ms) SELECT "tags".* FROM "tags" WHERE "tags"."id" IN (252, 256, 254)
[#<ActsAsTaggableOn::Tag id: 252, name: "general">]
[#<ActsAsTaggableOn::Tag id: 252, name: "general">, #<ActsAsTaggableOn::Tag id: 256, name: "tired">]
[#<ActsAsTaggableOn::Tag id: 254, name: "eating">]
[#<ActsAsTaggableOn::Tag id: 252, name: "general">]
=> nil
And you can see the preloading works perfect but replacing context with context_list I see the db hit on every iteration. Is this a bug?
ruby-1.9.2-p180 :014 > Log.includes{context.taggings.tag}.limit(4).each do |l| puts l.context_list.inspect end; nil
Log Load (3.4ms) SELECT "logs".* FROM "logs" ORDER BY date_of_entry DESC LIMIT 4
SQL (0.3ms) SELECT "taggings"."id" AS t0_r0, "taggings"."tag_id" AS t0_r1, "taggings"."taggable_id" AS t0_r2, "taggings"."taggable_type" AS t0_r3, "taggings"."tagger_id" AS t0_r4, "taggings"."tagger_type" AS t0_r5, "taggings"."context" AS t0_r6, "taggings"."created_at" AS t0_r7, "tags"."id" AS t1_r0, "tags"."name" AS t1_r1 FROM "taggings" LEFT OUTER JOIN "tags" ON "tags"."id" = "taggings"."tag_id" WHERE "taggings"."taggable_type" = 'Log' AND "taggings"."taggable_id" IN (1127, 1126, 1125, 1124) AND (taggings.tag_id = tags.id AND taggings.context = 'context')
ActsAsTaggableOn::Tagging Load (4.1ms) SELECT "taggings".* FROM "taggings" WHERE "taggings"."tag_id" IN (252, 256, 254)
ActsAsTaggableOn::Tag Load (0.2ms) SELECT "tags".* FROM "tags" WHERE "tags"."id" IN (252, 256, 254)
ActsAsTaggableOn::Tag Load (0.1ms) SELECT "tags".* FROM "tags" INNER JOIN "taggings" ON "tags"."id" = "taggings"."tag_id" WHERE "taggings"."taggable_id" = 1127 AND "taggings"."taggable_type" = 'Log' AND (taggings.context = 'context' AND taggings.tagger_id IS NULL)
["general"]
ActsAsTaggableOn::Tag Load (0.1ms) SELECT "tags".* FROM "tags" INNER JOIN "taggings" ON "tags"."id" = "taggings"."tag_id" WHERE "taggings"."taggable_id" = 1126 AND "taggings"."taggable_type" = 'Log' AND (taggings.context = 'context' AND taggings.tagger_id IS NULL)
["general", "tired"]
ActsAsTaggableOn::Tag Load (0.1ms) SELECT "tags".* FROM "tags" INNER JOIN "taggings" ON "tags"."id" = "taggings"."tag_id" WHERE "taggings"."taggable_id" = 1125 AND "taggings"."taggable_type" = 'Log' AND (taggings.context = 'context' AND taggings.tagger_id IS NULL)
["eating"]
ActsAsTaggableOn::Tag Load (0.1ms) SELECT "tags".* FROM "tags" INNER JOIN "taggings" ON "tags"."id" = "taggings"."tag_id" WHERE "taggings"."taggable_id" = 1124 AND "taggings"."taggable_type" = 'Log' AND (taggings.context = 'context' AND taggings.tagger_id IS NULL)
["general"]
=> nil
Not sure why the code is so complicated but I would change
class_eval %(
def #{tag_type}_list
tag_list_on('#{tags_type}')
end
def #{tag_type}_list=(new_tags)
set_tag_list_on('#{tags_type}', new_tags)
end
)
to something like
def tag_cache
@tag_cache ||= {}
end
class_eval %(
def #{tag_type}_list
tag_cache[tag_type] ||= send(tag_type).map(&:name)
end
def #{tag_type}_list=(new_tags)
tag_cache[tag_type] = new_tags
set_tag_list_on('#{tags_type}', new_tags)
end
)
I whipped up a new gem to try and fix the eager loading problems of acts_as_taggable_on. With the same db schema as acts_as_taggable_on but a rewrite with a cleaner style
https://github.com/bradphelan/rocket_tag
My current tests show perfect eager loading of tags. I'm also using
https://github.com/ernie/squeel
which makes the code very neat and short and at only 155 lines of code it's easy to dive into. There are a few basic specs at the moment but I think it shows promise.
For what it might be worth to someone, here's a quick and dirty solution. I needed to load a bunch of Taggables and display several tags from different contexts for each of them, so I added this to my model:
def preloaded_tag_list_on(context)
unless @tag_cache
@tag_cache = HashWithIndifferentAccess.new
taggings.each do |tagging|
@tag_cache[tagging.context] ||= []
@tag_cache[tagging.context] << tagging.tag.name
end
end
@tag_cache[context] || []
end
and in my controller:
TaggableModel.all(:include => {:taggings => :tag})
The taggings and tags are loaded with one query a piece, and the first time the new model method is called (in my case, from a loop in the view), Ruby will dump all of the tags into a hash grouped by context. On subsequent calls to the method, Ruby will just read out of the hash.
@bradphelan rocket_tag looks nice! Is it still maintained?
+1
+1
+1
@mbleigh just wondered if there was any sign of eager loading tags being applied.
+1 Any progress being made with eager loading of tags? Anyway I can help?
+1
If somebody wants to take over the repository they are welcome as I now longer maintain the project.
On Thu, 19 Mar 2015 17:36 Patrick [email protected] wrote:
+1
— Reply to this email directly or view it on GitHub https://github.com/mbleigh/acts-as-taggable-on/issues/91#issuecomment-83653259 .
Ooops. .. thought this was a post wrt to my library rockettag. I don't own the repository to acts as taggable on.
On Thu, 19 Mar 2015 17:37 Brad Phelan [email protected] wrote:
If somebody wants to take over the repository they are welcome as I now longer maintain the project.
On Thu, 19 Mar 2015 17:36 Patrick [email protected] wrote:
+1
— Reply to this email directly or view it on GitHub https://github.com/mbleigh/acts-as-taggable-on/issues/91#issuecomment-83653259 .
:smile_cat:
+1
the n+1 performance is so bad that it disrupts my develop/test/debug cycle.
I have the following comments in a project of mine.
# inside a controller
# this statement will preload tags, but accessing later via tag_list ignores the preload :(
#@songs = ImportedSong.tagged_with(ImportedSong.all_tags, any: true).includes(:tags).references(:tags)
# inside a view
<%# this reference to tag_list results in a DB call and n+1 perf %>
<%# tag_list cannot be accessed using includes/references preloading %>
<%= f.text_field :tag_list, 'data-role'=>'tagsinput' %>
+1
+1
+1
5 years later ... Any news on this ?
+1 anyone ?
found a solution !!! with PG , will update soon