Archive for the ‘Ruby on Rails’ Category

For the front-end system over at Digital Collective I wanted to implement a simple tagging system in Ruby on Rails with multiple tagging schema's - ie separate types of tags for locations, interests and skills (in much the same way that Facebook uses tags for pretty much everything). You see, I have three main models - Members, Projects and Jobs (with Events coming along shortly) and I wanted them to share the tags.

I'm using the acts_as_taggable plugin (if you want to find out more, check out these pages), which caters for the sharing of tags across models, but I could see no way to create subsets or different instances of tags. Since I didn't want to have to develop my own tagging system, I hit Google hard for the answer. With no results (except for a few more people that were trying to do the same thing). So I figured I would have to mess with the plugin code directly.

My first thought was to create three separate instances of the acts_as_taggable plugin (ie acts_as_taggable_locations, acts_as_taggable_interests etc) but that is clearly a lot of hard work! In contrast, my solution was incredibly simple and only required a couple of changes to the acts_as_taggable plugin.

The general idea is to categorise each tag, and then use that category to filter out tags at the Model level. So here goes:

First, make sure you install the acts_as_taggable plugin:

script/plugin install http://dev.rubyonrails.com/svn/rails/plugins/legacy/acts_as_taggable/

Then generate the necessary migration:

script/generate migration add_tag_support

Insert the following:

RUBY:
  1. class AddTagSupport <ActiveRecord::Migration
  2.   def self.up
  3.     create_table :tags do |t|
  4.       t.column :name,     :string
  5.       t.column :tag_type, :string # Additional row to categorise tags
  6.     end
  7.  
  8.     create_table :taggings do |t|
  9.       t.column :tag_id,         :integer
  10.       t.column :taggable_id,    :integer
  11.       t.column :taggable_type,  :string
  12.     end
  13.  
  14.     add_index :tags, :name
  15.     add_index :taggings, [:tag_id, :taggable_id, :taggable_type]
  16.   end
  17.  
  18.   def self.down
  19.     drop_table :taggings
  20.     drop_table :tags
  21.   end
  22. end

And then run Rake to add the table to the database:

rake db:migrate

Now to make a couple of changes to the plugin:

in acts_as_taggable.rb (found in vendor/plugins/acts_as_taggable/lib):

RUBY:
  1. def tag_with(list, tag_type='')
  2.   Tag.transaction do
  3.     taggings.destroy_all
  4.            
  5.     Tag.parse(list).each do |name|
  6.       if acts_as_taggable_options[:from]
  7.         send(acts_as_taggable_options[:from]).tags.find_or_create_by_name_and_tag_type(name, tag_type).on(self)
  8.       else
  9.         Tag.find_or_create_by_name_and_tag_type(name, tag_type).on(self)
  10.       end
  11.     end
  12.   end
  13. end

In your model:

RUBY:
  1. has_many :my_tag_alias, :through => :taggings, :source => :tag, :conditions => "tags.tag_type = 'my_tag_category'"

In your controller, to add tags (remember to use the relevant id's on the form that feeds into this action!):

RUBY:
  1. @mymodel.tag_with(params[:my_tag_category], 'my_tag_category')

And in your views, instead of using @mymodel.tags, just use @mymodel.my_tag_alias:

RUBY:
  1. <%= @mymodel.my_tag_alias.collect{|t| t.name}.join(', ') %>

And that's basically it! I hope it helps, and if anyone knows of a better way to do it (or perhaps the official way to do it!) let me know!

I'm working on a project which uses the ThickBox JavaScript widget, and it works an absolute treat. Only problem is, it uses the jQuery JavaScript library which clashes with the Prototype library that ships with RoR, causing some of the built-in effects to fail.

The solution is remarkably simple, and can be used wherever you need to use both jQuery and Prototype (for example, if you wanted to use both ThickBox AND Lightbox on the same page, though one wonders why you would want to!)...

AFTER you have loaded up all of the necessary libraries (example is in RoR syntax):

RUBY:
  1. <%= javascript_include_tag :defaults %>
  2. <%= javascript_include_tag ‘jquery’ %>
  3. <%= javascript_include_tag ‘thickbox’ %>
  4.  
  5. <script> jQuery.noConflict(); </script>

Then, using the uncompressed version of ThickBox.js, replace all of the $ with jQuery. Sorted.

I'm probably stating the bloody obvious here, but it took me nearly an hour to figure this out today, so hopefully I can save somebody else the effort...

I'm working on a Rails-based project at the moment, and wanted to utilise some sort of CAPTCHA security (y'know, where you have to type in the totally deformed letters in the image). I came across a nice little option courtesy of the 'Turing' Ruby Gem.

After installing the gem and creating the necessary code, I came across an error relating to the 'GD' library (a facility for outputting images). 'Not a problem' I thought to myself, somebody else has already solved the issue by installing the GD library using DarwinPorts, an 'infrastructure that allows easy installation and management of freely available software on Mac OS X 10.4 systems'.

So I installed DarwinPorts as per the instructions, and tried to selfupdate the version, only to be presented with all manner of errors. Nearly an hour oftrawling Google and various obscure message boards led me to a potential solution. 'Reinstall Xcode' was the suggestion.

Re-install? Who said anything about needing Xcode installed in the first place??

Luckily, I already had a download of the latest version that I had used on a different machine, so didn't have to sit through nearly 1GB of downloading. I installed the developer tools, restarted my machine and attempted to selfupdate DarwinPorts. Result? I've just finished installing the latest GD2 library plus all dependencies.

In conclusion, if you need to install DarwinPorts (or MacPorts for that matter) and are coming up against a brick wall, download and install Xcode from Apple.

Also, if you're trying to selfupdate DarwinPorts and are getting port: command not found, try typing source .profile in the Terminal.

UPDATE: It seems as though the GD library integration with the Turing Ruby gem is a little spurious on a Mac, so after all the hassle of getting DarwinPorts working, I ended up sacking-off the Turing gem and going for the SimpleCaptcha plugin for Rails. It's not as pretty in its base form, but it bloody well works straight out of the box (with a lot less code to boot!).