Dynamic tag cloud

Hello everyone :slight_smile:

This is my first post to this list so let me briefly introduce myself. I am a student at Imperial College London and am currently learning to ride the rails. Together with two other students I am developing a revolutionary job platform for students.

At the moment I am implementing tagging into our application using DHH's acts_as_taggable plugin. In my database I have a table with job listings. Every job listing has various tags. As intended by the acts_as_taggable plugin these tags are stored in a table called tags and linked to the job listings through a table called taggings. I also have a tag cloud showing the most popular tags. So far everything is working fine.

Now I would like to modify my tag cloud in the following way: I have a page where users can narrow the job listings down using tags. They can click on a tag and then a list of all jobs carrying that tag is displayed. Now I would like to adjust my tag cloud accordingly. The tag cloud should now only show tags associated to the remaining jobs. In other words: By showing only those listings with a particular tag I am defining a subset of my database. Now I would like to show only tags out of that subset in my tag cloud. An example can be seen on Zoopla > Search Property to Buy, Rent, House Prices, Estate Agents . When you click on a tag the tags list gets updated and you can narrow your search down again using the new list of tags.

Any ideas about how I could accomplish such a dynamic tag cloud? Please be as specific as possible as I am relatively new to Ruby and Rails.

So far, my tag cloud is generated as described on

Many thanks for your help, Aeneas

Anyone? Could you at least point me in the right direction?

Aeneas

Sure, that’s pretty easy. You’ll need to extend the acts_as_taggable plugin though.

Lets say we add something like this to tab.rb:

def self.get_all_tagged_with_for_type(type, tags)

query = "SELECT taggable_id, name FROM"

query << " taggings, tags WHERE"
query << " [tags.id](http://tags.id) = taggings.tag_id"
query << " AND name IN ('#{sanitize(tags.join('\',\''))}') AND taggable_type = '#{

type.name}';

end

That will return all object IDs of the given type for the given array of tags.

so you might call it like this:

Tag.get_all_tagged_with_for_type(Job, Tag.parse(params[:tags].tr(‘,’, ’ ')))

This assumes your URL looks something like: www.site.com/?tags=tag1,tag2,tag3,tag4

You need to perform some better parsing if you want tags with spaces in them. Tag.parse() will handle such tags if they’re quoted.

I’ve not tested any of this code, so no doubt it doesn’t work without a little work. But please note the use of sanitize() in the model method! We’re taking user input and using it a query so we need to protected against SQL injection attacks

The annoying part comes when extending the existing plugin tab.rb. Currently, Rails will reload the plugin in development for each request and you’ll loose any methods you dynamically extended it with during startup. So for now stick the method inside the plugin and keep and eye out for Rails 1.2: http://rails.techno-weenie.net/tip/2006/9/8/reloading-dynamically-extended-classes

Hope that helps Ian

Oops, and of course we need to Tag.find_by_sql(query) in that model method :wink:

Ian, many thanks for your reply! It is still not doing what I want. I simplified (for testing) the relevant part of the SQL query slightly by adding the common tag manually:

query << " AND name IN ('hard')"

Here is what I expected:

I wanted to see all tags assigned to those jobs which have also been tagged with "hard".

However, that's not what I am getting with this query. Instead I just get the tag hard itself and nothing else. I am pretty sure everything else is set up correctly. When I leave the line quoted above out then I get all tags in the tags table so this is working.

Any suggestions?

You need to iterate over the results and load each job using the taggable_id value:

tagged_with = Tag.get_all_tagged_with_for_type(Job, [‘hard’])

for tagged in tagged_with

job = Job.find(tagged[:taggable_id])

job.tag_list

end

You could move the query into a method inside your Job model and join on the taggable_id and job id to save you having to iterate the results in this way.

Many thanks for your help!

I have now managed to output the correct tags to the browser as a comma separated list.

Controller: @jobs_tagged_with = Tag.find_by_name(params[:common_tag], :limit => 10 ).tagged

View: <% for job in @jobs_tagged_with %>   <%= job.tags.collect{|tag| tag.name}.join(", ") + "," %> <% end %>

However, that's not quite what I want. How can I get the tags currently displayed on screen in an array? This array could then be used to feed my tag cloud.

I am sure this is nothing very difficult for someone a bit more experienced with Ruby than I am. I am still trying to get my head around all this...

Aeneas

Displayed on the screen as an array object? or you want to convert a string list of tags into an array? Not quite sure what you want.

If you meaning parsing the tags:

Tag.parse(“tag1 tag2 "tag3 with spaces"”) # Parse space seperate list of tags

=> [“tag3 with space”, “tag1”, “tag2”]

“tag1, tag2, tag3 with spaces”.split(', ') => [“tag1”, “tag2”, “tag3 with spaces”]

If you mean an array as in something you can pass as a parameter in the URL, then just use a comma seperated list and parse it in the receiving controller.

Finally, I have been able to figure out an SQL query which does all the work for me. Here it is:

SELECT tags.name, count(*) AS count FROM jobs JOIN taggings ON jobs.id = taggings.taggable_id JOIN tags ON taggings.tag_id = tags.id WHERE jobs.id IN ( SELECT jobs.id FROM jobs   JOIN taggings ON jobs.id = taggings.taggable_id   JOIN tags ON taggings.tag_id = tags.id   WHERE tags.name IN ("tag2", "tag3")   ) AND tags.name NOT IN ("tag2", "tag3") GROUP BY tags.name ORDER BY count DESC , tags.name

Aeneas