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
http://www.extate.com/search.php?q=kensington . 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
http://www.juixe.com/techknow/index.php/2006/07/15/acts-as-taggable-tag-cloud/

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