acts-as-taggable-on icon indicating copy to clipboard operation
acts-as-taggable-on copied to clipboard

Eager loading tags

Open karant opened this issue 14 years ago • 41 comments

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

karant avatar Jun 27 '10 01:06 karant

I'm also interested in this feature... nice for showing all the tags on a set of objects.

bwlang avatar Nov 09 '10 15:11 bwlang

something i'd like to see as well.

ghost avatar Dec 12 '10 22:12 ghost

this would rock

jancel avatar Jan 31 '11 06:01 jancel

I am also trying to figure out how to eager load tags with a query -- is there a way?

ms-ati avatar Apr 07 '11 19:04 ms-ati

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

vitork avatar May 18 '11 22:05 vitork

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.

ms-ati avatar May 18 '11 22:05 ms-ati

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

vitork avatar May 18 '11 23:05 vitork

Eager loading of tags would be very useful.

avitus avatar Jun 28 '11 17:06 avitus

Did anyone find a way to do this?

twe4ked avatar Jul 16 '11 09:07 twe4ked

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 

bradphelan avatar Sep 15 '11 08:09 bradphelan

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

      )

bradphelan avatar Sep 15 '11 08:09 bradphelan

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.

bradphelan avatar Sep 15 '11 16:09 bradphelan

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.

weir avatar Jun 21 '12 15:06 weir

@bradphelan rocket_tag looks nice! Is it still maintained?

tilsammans avatar Apr 12 '13 21:04 tilsammans

+1

devilankur18 avatar Mar 01 '14 23:03 devilankur18

+1

bigardone avatar Jun 28 '14 10:06 bigardone

+1

joshuasiler avatar Jul 18 '14 00:07 joshuasiler

@mbleigh just wondered if there was any sign of eager loading tags being applied.

amnesia7 avatar Feb 04 '15 14:02 amnesia7

+1 Any progress being made with eager loading of tags? Anyway I can help?

hopkinschris avatar Feb 10 '15 16:02 hopkinschris

+1

mklb avatar Mar 19 '15 16:03 mklb

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 .

bradphelan avatar Mar 19 '15 16:03 bradphelan

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 .

bradphelan avatar Mar 19 '15 16:03 bradphelan

:smile_cat:

seuros avatar Mar 19 '15 16:03 seuros

+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' %>

neoice avatar Jun 19 '15 19:06 neoice

+1

gdomingu avatar Sep 22 '15 22:09 gdomingu

+1

mdesjardins avatar Oct 16 '15 01:10 mdesjardins

+1

Jared-Prime avatar Nov 06 '15 20:11 Jared-Prime

5 years later ... Any news on this ?

jerefrer avatar Nov 13 '15 13:11 jerefrer

+1 anyone ?

idan-senexx avatar Nov 30 '15 14:11 idan-senexx

found a solution !!! with PG , will update soon

idan-senexx avatar Nov 30 '15 21:11 idan-senexx