How to get a list of all tags while using the gem 'acts-as-taggable-on' in Rails (not the counts)
Asked Answered
H

4

22

I have set the acts-as-taggable-on gem in my model like this :

 acts_as_taggable_on :deshanatags

It use the context deshanatags. Now I need to get a list of all the tags (not only the ones assigned for one item. I need everything) in this context in the following format :

[
    {"id":"856","name":"House"},
    {"id":"1035","name":"Desperate Housewives"}
]

How can I do this?

I tried to follow many tutorials but hit dead ends because most of them are written for Rails 3. Rails for have some changes to the model like removal of attr_accessor which make it difficult for me to understand those tutorials. So please help.

Simply im trying to add Jquery Token input (http://loopj.com/jquery-tokeninput/) to my app

PS : Through Tagging table, is there a way to get a list of tags like the output of Tag.all by filtering the context?

Hindemith answered 28/3, 2014 at 10:30 Comment(0)
R
46

The tags are stored in the Tags table, which you access from your program with e.g.

ActsAsTaggableOn::Tag.all

If you need more info on tag usage, there is also the table

ActsAsTaggableOn::Tagging

which contains links to where each tag is being used, including its context

To further apply this to your example, you can use

ActsAsTaggableOn::Tagging.includes(:tag).where(context: 'deshanatags').map { |tagging| { 'id' => tagging.tag_id.to_s, 'name' => tagging.tag.name } }.uniq

Let's explain the various parts in this statement:

  • the 'includes' makes sure the different tags are "eager loaded", in other words, that instead of loading n+1 records, only 2 queries will be done on the database
  • the 'where' limits the records to the ones with the given context
  • the 'map' converts the resulting array of records into a new array, containing the hashes you asked for in your problem, with id mapped to the tag's id, and name mapped to the tag's name
  • finally the 'uniq' makes sure that you don't have doubles in your list

To also cope with your additional problem, being taggins without tag, you could extend the where clause to

where("(context = 'deshanatags') AND (tag_id IS NOT NULL)")
Rotten answered 28/3, 2014 at 10:45 Comment(6)
Is there a way to get only the ones with the context deshanatags ?Hindemith
Through Tagging table, is there a way to get a list of tags like the output of Tag.all by filtering the context?Hindemith
EAPubs - sorry for the late answer. Have been out for a whileRotten
Sorry it also have issues. The code says NoMethodError: undefined method name' for nil:NilClass`Hindemith
It looks like your database got "corrupted", with taggings having no tag... I suggest to clean them out, as it makes no sense to have them hanging around... Alternative is that you explicitly ignore them in your query, but that leads to "ugly code"... See last line added in solution!Rotten
Probably it more convenient to start from tags: ActsAsTaggableOn::Tag.includes(:taggings).where('taggings.context' => :popular)Rutharuthann
M
5

I personally think that both solutions will be slow if you have many tags to retrieve as there are many single select statements. I'd do it like this:

ActsAsTaggableOn::Tagging.includes(:tag).where(context: 'deshanatags').uniq.pluck(:id, :name)

If you're on ruby 1.9+

This way you'll just end up with:

SELECT DISTINCT "taggings"."id", name FROM "taggings" LEFT OUTER JOIN "tags" ON "tags"."id" = "taggings"."tag_id" WHERE "taggings"."context" = 'deshanatags'

Quick and easy imo

Megaera answered 12/8, 2014 at 9:46 Comment(1)
Note though this returns slightly different response than the original answer. The tags are not actually deduped by the names. You want to instead use: ActsAsTaggableOn::Tagging.includes(:tag).where(context: 'deshanatags').pluck(:id, :name).uniq {|id,name| name }Judiciary
B
3

Using the ActsAsTaggableOn::Tagging model, you can get a list of all tags and filter them down according to context, then format them as follows:

ActsAsTaggableOn::Tagging.all
.where(context: "deshanatags")
.map {|tagging| { "id" => "#{tagging.tag.id}", "name" => tagging.tag.name } }

The where will give us a result of all taggings for a given context, which we then iterate over using map, retrieving the related tag for each tagging in our result and getting the id and name attributes.

Make sure to chain the methods directly (not across several lines) if you're not using ruby 1.9+

You can then call .to_json on the result to get a properly formatted json object that you can use

edit:

I updated the post to clarify what each statement does and also updated the format of our final result to use strings as keys instead of symbols

Bamby answered 1/4, 2014 at 8:21 Comment(3)
Its not working... The records i get from ActsAsTaggableOn::Tagging.all.where(context: "deshanatags") are all like this ` #<ActsAsTaggableOn::Tagging id: 35, tag_id: 23, taggable_id: 1, taggable_type: "Deshana", tagger_id: nil, tagger_type: nil, context: "deshanatags", created_at: "2014-04-03 05:44:08">]`Hindemith
Yes, that's correct. You will need the .map part too to retrieve the info that you require.Bamby
Its giving me this error : NoMethodError: undefined method id' for nil:NilClass`Hindemith
D
3

In more recent versions, you can use:

ActsAsTaggableOn::Tag.for_context(:deshanatags)

I ended up here from Google, but then found the answer in the wiki.

From the wiki

From the code

Dinin answered 7/1, 2020 at 23:28 Comment(1)
This should be the top answer now!Bayly

© 2022 - 2024 — McMap. All rights reserved.