Redline Software Inc. - Winnipeg's Leader in Ruby on Rails Development

Will_paginate and Remote AJAX Links

Updated for will_paginate 2.3.x

So you’re using the will_paginate plugin and want to use remote AJAX links. You’re not using the will_paginate plugin? Simply install it with…

1
script/plugin install git://github.com/mislav/will_paginate.git

or install the gem and specify it in your config/environment.rb file

1
sudo gem install mislav-will_paginate

1
config.gem 'mislav-will_paginate', :lib => 'will_paginate', :source => 'http://gems.github.com'

will_paginate is an alternative to the classic_pagination plugin, which is the pagination plugin that basically took the pre Rails 2.0 pagination implementation and packaged it in plugin form. I won’t discuss the differences between the two plugins here, but there’s a good video explaining the two from Railscasts [here](http://railscasts.com/episodes/51).
Out of the box, will_paginate doesn’t allow remote AJAX links for page links, so I’ll show you an easy way to add such functionality…

I’ve read a number of blogs that seem to go a bit overboard in order to implement remote links, but will_paginate allows you to customize the links quite nicely already.

By default will_paginate uses the WillPaginate::LinkRender class to render links. So lets use this is a base and add to it. We can derive from this class and add our own functionality.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# app/helpers/remote_link_renderer.rb

class RemoteLinkRenderer < WillPaginate::LinkRenderer
  def initialize(collection, options, template)
    @remote = options.delete(:remote)
    super
  end

  def page_link_or_span(page, span_class = 'current', text = nil)
    text ||= page.to_s
    if page and page != current_page
      @template.link_to_remote(text, {:url => url_options(page), :method => :get}.merge(@remote))
    else
      @template.content_tag :span, text, :class => span_class
    end
  end
end

All I’m doing is creating a subclass to the existing renderer and overriding the page_link_or_span method that makes the link_to method call and replacing it with a call to link_to_remote (I also added the :method => :get option to work with my restful code).

So how do we use this? One of two ways.

  • Use the :renderer and :remote(optional) option on will_paginate. The :remote option takes any options that link_to_remote does, so refer to the rails docs for allowable options. The :with and :update options are simply being used to show example usage.

1
<%= will_paginate @collection, :renderer => 'RemoteLinkRenderer' , :remote => {:with => value, :update => some_div} %>

  • or set the default renderer in config/environment.rb

1
WillPaginate::ViewHelpers.pagination_options[:renderer] = 'RemoteLinkRenderer'

  • and use it without the :renderer option.

1
<%= will_paginate @collection, :remote => {:with => value, :update => some_div} %>

That’s all there is to it. You could get fancier when creating your LinkRenderer class to allow for additional customization, but this is a good start.

Update for will_paginate 2.3.x

This is even more concise in the new version.

1
2
3
4
5
6
7
8
9
10
11
12
13
# app/helpers/remote_link_renderer.rb

class RemoteLinkRenderer < WillPaginate::LinkRenderer
  def prepare(collection, options, template)
    @remote = options.delete(:remote) || {}
    super
  end

protected
  def page_link(page, text, attributes = {})
    @template.link_to_remote(text, {:url => url_for(page), :method => :get}.merge(@remote), attributes)
  end
end

The usage remains the same as above.

To use the new version, see the Installation section at http://github.com/mislav/will_paginate/tree/master

Custom Rails App Config Options

Do you use RAILS_ENV in your Rails app to execute different code depending on the environment you’re running?

1
2
3
4
5
if RAILS_ENV == 'development'
  log_some_data_or_do_something_different
else
  go_with_the_flow
end

No? Ok, great. Yes? Ok, not so great.

Those from both camps are likely to get something out of this post, so read on.

The problem with using RAILS_ENV in your code has 2 main issues.

  1. If you misspell the environment string (RAILS_ENV == ‘developent’) you won’t be notified of any errors or warnings and you may introduce bugs or other issues into your code (if your tests don’t catch it)
  2. You’re integrating details of a specific environment into the code when your app logic should have no knowledge of what environment it is currently running in.

Solution (Partial)

Introduce configuration details for your app in the environment files and use those in your app.

1
2
3
4
5
6
7
8
# config/environments/development.rb
do_extra_neato_stuff = true

# config/environments/production.rb
do_extra_neato_stuff = false

# in the app
send_email_and_log_some_data if do_extra_neato_stuff

This keeps the code logic clean from environment specifics and if you spell the variable incorrectly, you’ll get an error, which is nice.

Solution (The whole 9 yards)

The partial solution can work, but it’s far from ideal.

You can’t specify default values cleanly for your app and then override a configuration value for a specific environment; things get worse if you introduce additional environments. Specifying a default value in environment.rb and then providing an override in development.rb (for example) doesn’t really work without some some ugly hacks. The reason for this is that these new options don’t work in the same way the existing Rails configuration options do in the environment files.

So how about we use the Rails configuration functionality with our own options?

Not possible? Well, lets write a plugin to make it possible. Wait a second, lets first check if somebody else has solved this problem for us.

http://agilewebdevelopment.com/plugins/app_config BAM!

Before I found this plugin, I scoped out the basic details in my head for how I’d implement my own plugin to enable this new functionality, but no use reinventing the wheel, so I did a quick search first and I was extremely happy to see that this plugin implementation was pretty much identical to what I was thinking, so even better.

Using the plugin, I can now specify my options as follows.

1
2
3
4
5
6
7
8
# config/environment.rb (the default)
config.app_config.do_extra_neato_stuff = false

# config/environments/development.rb
config.app_config.do_extra_neato_stuff = true

# in the app
send_email_and_log_some_data if AppConfig.do_extra_neato_stuff

What the plugin introduces is an additional configuration value called app_config where you can specify any option you like and then in the code you access these options with the AppConfig value.

Now we also get all of the niceties of the Rails configuration functionality!

See the plugins README file and its code for additional details on using this handy plugin.

Migrating From Mac OSX Tiger to Leopard

Step 1: Backing up my Tiger Install

On Tiger I was using the great backup software called SuperDuper!, which enables you to make a complete bootable backup of your hard drive. I have an external USB drive which I backup to, so the plan was to boot off it in the event that Leopard has problems. If you don’t have an external drive, then go get one, as it will work great with Leopard’s new Time Machine feature.

Major Tip #1: To have your Intel Mac boot off of an external USB drive, you need to first partition that external drive correctly. Using the Disk Utility, partition your external drive but make sure in the options you set the Partition Map Scheme to GUID Mode. If you don’t do this, you can’t boot off of your backup. (If you’re not on an Intel machine, then this option will be different for you. Google is your friend.)

After partitioning the drive, have SuperDuper! do it’s magic and come back in a couple of hours when your backup is complete. You can take this time to lookup your important software on the web to see if they have Leopard compatible versions. I went and played video games instead, you do what you like.

Step 2: Making sure the backup boots

Once SuperDuper! is done, reboot your mac and hold down the option key when the system is booting back up. This will let you choose your boot drive on startup. Choose your USB drive and continue. You’ll find that the system boots a lot slower, but that is expected of course. With any luck, your backup is an exact replica of your system.

Step 3: Installing Leopard

Important!: Unplug your external drive from the computer BEFORE installing Leopard! I read some reports on some websites where people had problems with Leopard doing all sorts of things to the external drive. Maybe this is false, but regardless THIS IS YOUR BACKUP COPY. Don’t take any chances with it!

The rest of the install is dirt simple. Put in the Leopard DVD, restart your computer (hold option down on boot) and select the install disk. I installed with pretty much the default options, but the major option that I selected was to not transfer any information from an existing install. I figured I’d either use this Migration Assistant later or just manually move what I wanted.

Step 4: Software Update

After Leopard booted up, the first thing I did was run the software update utility and let it do its thing. I also choose to have it run daily.

Step 5: Installing Software

At this point you can go off and install whatever software you like. I installed the Xcode tools off the Leopard disk so I could get the development tools that I would need later on. I also configured all the other stuff like my time zone, dashboard widgets, turned on the firewall, etc.

Step 6: Migration

Here’s where hopefully this blog post can save you a bit of time (step 1 should too).
Now as you can see, I didn’t export any settings from my Tiger apps, I just took a backup and then wiped my drive. It’s shooting from the hip, but hey that’s how I roll.

At this point, it should be safe to plugin your backup drive to your machine again. We’re going to use it to restore some applications, some data, some music etc. One of the folders you are about to become familiar with is your ~/Library/Application Support folder. Note that the ~ means your home directory. There is also a /Library/Application Support folder for system wide stuff.

For the following, assume BACKUP is your backup drive and username is your username

Documents

Go grab your documents folder and drag it across to your new home folder. You can do this with other files like downloads, desktop items, music, pictures, whatever else you like.

Safari 3 Bookmarks

I mostly use del.icio.us for my bookmarks, but there was some things I have stored in my bookmarks bar that I wanted. Close Safari. Copy BACKUP/Users/username/Library/Safari/Bookmarks.plist from BACKUP to Leopard in the same folder.

Address Book

I followed the instructions on this site and it worked great. I’ll copy the instructions here so that we’ll have it all in one spot:

If you have addresses in your new AddressBook, make a backup of the AddressBook folder (Leopard home folder)/Library/Application .
Here are the steps:

  1. Quit all applications
  2. Open a Finder window and go to (home folder)/Library/Application Support/AddressBook/
  3. Drag AddressBook-v22.abcddb (the Leopard database) to the trash
  4. Make sure the AddressBook folder contains AddressBook.data file (the Tiger database)
  5. Rename the Metadata folder (you could call it Metadata.leopard for example)
  6. Launch Address Book.app (this will import the Tiger data, create a new Metadata folder, and fill it with the Tiger contact info)
  7. Wait several minutes for the Metadata to be written out, then quit Address Book
  8. Drag AddressBook-v22.abcddb to the trash again
  9. Go into the Metadata.leopard folder that you renamed in step 5
  10. Copy all of the files in that folder, and drag or paste them into the new Metadata folder that was created in the AddressBook folder
  11. Launch Address Book.app again

Thanks again to Adam for posting that.

iCal

This one is also a bit annoying. In Leopard they store the iCal data in ~/Library/Calendars, but on Tiger the data you want is in BACKUP/Users/username/Library/Application Support/iCal/Sources where you will have a folder for each calendar with a crazy name like 734B0524-3D68-4EC7-8215-2A06EBA30B29.calendar. Inside those folders are 2 files of interest: Info.plist which contains some general information about the calendar (most importantly the name), and another file named corestorage.ics, which is used to store the real calendar’s data.
What I did is went through each folder, opened the Info.plist and looked at the title. If it was a calendar I still wanted, I double clicked the corestoreage.ics file and renamed it in iCal after importing. I had less than 10 calendars so doing this was no big deal. Plus I purged a few calendars that I just don’t use anymore.

dot files

If you use the terminal a lot for things like subversion or ssh, you’ll have some directories in your home folder that you might want to copy over. I use ssh keys a lot so I wanted my private/public keys from my tiger install on Leopard. This part is easier to do in the terminal so launch that up.

To get to your external drive, it should be mounted in /Volumes/. So you can copy a few things like:
/Volumes/BACKUP/Users/username/.subversion to your home folder on Leopard.
You can do ls -a in a directory if you want to list the dot files/dirs as well. I did the .ssh directory too so that I’d have my keys.

iLife 06

I don’t really use the iLife apps that much so I’m not going to upgrade to ’08. Instead I just dragged the apps like iMovie, iDVD, GarageBand and iPhoto from my Tiger drive to my Leopard. The only one that gave a hassle was GarageBand, as it also needs the /Library/Application Support/GarageBand folder copied too. It will tell you what it needs on startup though, so copy over what it wants and restart it.

Email

I use Google Apps for my email so I can take advantage of IMAP, so I really didn’t migrate much here. IMAP seems to work nicer in the new Mail.app.

Other Applications

I had a few other applications that I just dragged over from the Applications folder on my Tiger drive and they all seemed to work just fine. Some of them thought they were starting up for the first time, but that was no big deal. For some other commercial apps, I just redownloaded them and reentered my license key. There isn’t any app that I wasn’t able to get working with one of these 2 methods. I opted to reinstall the apps when I could. Most of them also use the Libary/Application Support folder so you can also look there for additional files if something is acting weird.

Note: I take no responsibility if you’ve followed these steps and you can’t get application XYZ to work. Remember you can still boot off that backup if you need to export data to a format so that you can import it into your new setup. Do what you need to here. I might have gotten lucky for once.

Leopard Terminal

The terminal seems to be a bit better than in Tiger. One setting I had to turn on though so that I could get the backspace working properly when I ssh to remote hosts, was to go under Preferences…Settings and then on the Advanced tab, choose Delete sends Ctrl-H.

MacPorts

As an aside, I really hate MacPorts, and from what I can tell Fink might be worse since it’s way out of date. Coming from a Linux background where they have awesome things like apt-get and yum, port is really a hoof to the schnutz.

Having said that, I still installed MacPorts like a chump because it’s still better than hand compiling stuff (sometimes).

Other Stuff

Of course during the new setup, I installed a bunch of things, configured a bunch of things, and played with all the new stuff in Leopard. Here’s some additional apps that I installed that you may or may not know about, I’ll let their respective marketing pages do the talking for them:

  • Little Snitch – Still not sure why there really isn’t a good free alternative to this, but it’s a nicely done app none the less. Use your Mac OS firewall for incoming connections and Little Snitch for your outgoing. (This should be a part of Mac OSX dammit)
  • ClamXav AntiVirus – I have all my apps dump their downloads into the same folder, and then have ClamXav watch out for that directory.

Got any other great system related apps? Post in the comments please!

Issues

The only 1 glaring problem I’m having right now is that Quicksilver isn’t behaving like it used to. There seems to be an issue with Proxy Objects not working in Leopard anymore. If you know how to fix this one, please please let me know. Quicksilver is like crack.

Related information:

Conclusion

I never once had to go back and boot my backup drive, but being able to made the whole upgrade/migration process a lot less tense. I’m sure some of you used the Mac OS upgrade option and it probably worked just great, but I know some other people on the net who didn’t have much luck. I wanted to wipe my drive anyway to get rid of a lot of old stuff that I had installed when I was still learning about OSX. My fresh install is running a lot quicker and smoother.

I probably burnt up a day messing around with this install, so if you’re going this route try to schedule around that. To get a working Leopard up is pretty quick, but you’ll want some time to tinker with every little detail. I didn’t use the migration assistant, but maybe I should’ve looked into that. Either way, I got to learn a bit about how Mac OS apps store their data and migrating a system the slow way.

Human Attribute Override Plugin Tutorial

This is my second tutorial covering the plugins that we’ve developed.

This tutorial covers the human attribute override plugin.

The plugin allows humanized versions of attributes to be overridden with custom strings to provide a better conversion than human_name may provide.

Why?

The main reason for creating this plugin is that Rails doesn’t always provide an acceptable “humanized” version of an attribute name.

1
Column.new('num_employees', ...).human_name # => 'Num employees'

You could argue that perhaps the attribute should be named number_of_employees..

1
Column.new('number_of_employees', ...).human_name # => 'Number of employees'

But this is hardly an acceptable solution for various reasons

1. The attribute name is too wordy.
2. Changing an attribute name for the sake of it’s humanized version reading better results in unneeded refactoring.
3. You’re using a legacy database that uses a specific naming convention.

Rails Usage

Rails uses these humanized conversions in error reporting with the error_messages_for method via full_messages and in schema definitions for column names with the human_name method.

Using the above example, a table companies has a field called “num_employees”.

1
2
3
4
class Company < ActiveRecord::Base
end

Company.columns_hash['num_employees'].human_name # => 'Num employees'

Plugin Usage

So since the column name conversion in the previous example is basically unacceptable, let’s use the plugin to fix this up.

1
2
3
4
5
class Company < ActiveRecord::Base
   attr_human_name 'num_employees' => 'Number of employees'
end

Company.columns_hash['num_employees'].human_name # => 'Number of employees'

The plugin provides an additonal method called attr_human_name that accepts a hash of “attribute name” => “desired humanized conversion text” pairs. The hash keys (the attribute names) can be specified as a string or a symbol.

Specifying more than one conversion…

1
2
3
4
5
6
class Company < ActiveRecord::Base
   attr_human_name :num_employees => 'Number of employees', :unit_num => 'Unit #'
end

Company.columns_hash['num_employees'].human_name # => 'Number of employees'
Company.columns_hash['unit_num'].human_name # => 'Unit #'

If you’d like to retrieve a list of all of the overridden attributes, you can use human_name_attributes

1
Company.human_name_attributes # => {"num_employees"=>"Number of employees", "unit_num"=>"Unit #"}

Summary

So if you have some attributes that need better names for use in your rails views, use the plugin and make your life much easier.

Tip: DB Views as ActiveRecord Models

Here’s a quick tip for anybody using a database view as an ActiveRecord model…

If you want to use your view as a model and take advantage of the association methods properly (like belongs_to, etc.), you must make sure that your view specifies a unique column that can be used as the primary key. Without a primary key for the view, you’ll likely run into some nasty bugs down the road (mine occurred when I tried to use eager loading via :include with the “view model”).

First off, to use the view as a model, you don’t need to do anything different than what you’d do for a regular table.

1
2
class ViewModel < ActiveRecord::Base
end

Here are some ways to set your primary key for your view model:

1) If your view already has a unique “id” field, then you’re done. No need to do anything else.

2) If your view has a unique field, but it’s not called “id”, simply use the set_primary_key method.

1
2
3
class ViewModel < ActiveRecord::Base
  set_primary_key :unique_id # unique_id will be the name of a unique column used in the view
end

3) Create a new field in the view that can be used as the primary key.

1
create view view_model as select (field1_id::varchar + '-' + field2_id::varchar) as id, ...

The above SQL example uses two fields combined into one to form a unique field called “id” and this will be picked up automatically by ActiveRecord. The reason for adding the ‘-’ separator is so that 2 combinations of 2 separate values don’t combine to form the same value.

With no separator:
field1_id = 10, field2_id = 10, id = 1010 and field1_id = 101, field2_id = 0, id = 1010

versus

With a separator:
field1_id = 10, field2_id = 10, id = 10-10 and field1_id = 101, field2_id = 0, id = 101-0

To those who know their databases, you’ll recognize this as basically a composite key and since RoR doesn’t support composite keys out of the box, this can be used to mimic one.

4) Use a composite key plugin like http://compositekeys.rubyforge.org to add composite key support to RoR

Now you can use composite keys instead of the method of generating one as in item 3.

1
2
3
class ViewModel < ActiveRecord::Base
  set_primary_keys :field1_id, :field2_id
end

note
Pretty much everything here applies to a regular model based off a table, but when creating a view, it’s more common to not include a column that will be unique for each record like the “id” field used in almost all tables used with RoR.

Improved Player Signup With New Team Requests

In our constant effort to make the registration process easier, we’ve added an additional feature to Redzone Leagues to allow a player to request a spot on a teams roster.

The way the original process worked was that a team captain would create their team and then manually add each player to the roster themselves. This works, and still works, but issues can occur when a captain doesn’t know a players name, or a user hasn’t registered yet in order for them to be added to the roster. If a user hasn’t registered then a captain would have to wait for a user to signup and then that user would have to notify the captain via email, phone, in person, etc. that they’ve registered and can now be added to the team roster… and this is where the signup process can become a bit too much work.

To improve the signup process, a registered user would now find their team (which must already be registered by their captain) and simply click a link on the teams profile page to request to add themselves to the teams roster. Once a request is made, the team captain will receive an email notifying them of the request. The captain will then visit their team page to view the request (along with any other outstanding requests) and with a click of the mouse, can either approve or reject the player for the team.

Enjoy!

Conditional Cache Plugin Tutorial

Update

This plugin has now been DEPRECATED for Rails 2.2+ in favour of the built in :if and :cache_path options.

The following changes can be made to use :cache_path instead of the :tag option.

1. Change all instances of :tag to :cache_path
2. If the :tag referred to a sybmol such as :tag => :standard_tag, you simply need to change the method name that the symbol refers to as standard_tag_url (just add url to the end of the method name).
3. The host name no longer prefixes the cache fragment when :cache_path uses anything other than a hash, so make sure to begin your cache_path with the string generated from request.url (includes protocol) or request.host + request.request
uri (no protocol) if your cache fragment keys depend on the host name.

Updated for Rails 2.1

This is my first of many tutorials to come covering the plugins that we’ve developed.

This tutorial covers the conditional cache plugin, which is actually the first plugin released by Redline.

Why?

By default, the caches_action method allows you to specify one or more actions to apply caching to.

1
2
3
4
5
6
7
8
9
10
11
class TestController < ApplicationController
  caches_action :index, :new

  def index
    ...
  end

  def new
    ...
  end
end

In the above example, action caching is being applied to the index and new action. Beyond that we have no additional control over when these actions should be cached or how these actions should be cached. It’s either all or nothing.

So to fix this “all or nothing” problem, the conditional cache plugin adds additonal functionality to the caches_action method by adding 2 additional parameter options that can be passed to the method along with the existing list of actions.

:if option

Specifies a method, proc or string to call to determine if the caching should occur. The method, proc or string should return or evaluate to a true or false value.

This option behaves exactly like the :if option for validations, such as validates_format_of, validates_length_of, etc.

So an example using a method for the option would look like this…

1
2
3
4
5
6
7
8
9
10
11
12
class TestController < ApplicationController
  caches_action :index, :if => :allow_cache

  def index
    ...
  end

protected
  def allow_cache
    request.get?
  end
end

and using an inline Proc…

1
2
3
4
class TestController < ApplicationController
  caches_action :index, :if => Proc.new {|controller| controller.request.get?}
  ...
end

As you can see in the examples above, the “index” action will only cache during a GET request using the value specified for the :if option.

Generally I use the exact same test for my caching, so I create a method in the ApplicationController and then use it in my descendant controllers.

1
2
3
4
5
6
7
class ApplicationController < ActionController::Base
protected
  def standard_cache_test
    # only cache for a GET request and when the flash is empty
    request.get? && flash.empty?
  end
end

And then in descendant classes, I simply use the predefined method…

1
2
3
4
class TestController < ApplicationController
  caches_action :index, :if => :standard_cache_test
  ...
end

What the above test is saying is that caching should only occur for a GET request and that the flash is empty. I don’t want a page to be cached that contains flash data that may display on the page.

:tag option

Specifies a method, proc or string which adds an additional tag to the cache fragment path so that various states of caching can occur within the application. The method, proc or string should return or evaluate to a string. The tag (string) is appended to the end of the cache path on the server so the expire_fragment method should be used with a regex to safely expire actions that use the :tag option.

To better explain this, if you’re not aware, when an action is cached, a file is created in your apps tmp/cache directory by default. A path could look like tmp/cache/127.0.0.1:3000/test/index.cache

Using :tag, you can add your own text to this generated path for additional flexibility.

1
2
3
class TestController < ApplicationController
  caches_action :index, :tag => Proc.new { |controller| controller.user_type.to_s } # This will cache the page dependant on the type of user logged in. 
end

In this example, :tag returns a string version of a user_type. The user_type in this example would represent “admin”, or “moderator”, etc. If the generated index page differs for various user types, with say additional links, buttons, etc for an admin to manage various parts of the site, these different sections of the html should not be visible to a moderator or a normal user on the site.

So when an administrator loads the index page, the generated cache path would now look like tmp/cache/127.0.0.1:3000/test/indexadmin.cache

And when a moderator loads the index page, the generated cache path would look like tmp/cache/127.0.0.1:3000/test/indexmoderator.cache

If a normal user has no user_type associated with it and :tag returned an empty string, the cache path would look like it normally would if the :tag wasn’t used… tmp/cache/127.0.0.1:3000/test/index.cache

So as you can see from this example, the index action can be cached multiple ways depending on the type of user logged in.

Lets look at another example that uses a time zone tag associated with a user. This is helpful for pages that display time values where each user can set the time zone associated with their account, therefore seeing different times than a user with a different time zone setting.

1
2
3
4
5
6
7
8
9
10
11
class ApplicationController < ActionController::Base
protected
  def standard_cache_tag(options = {})
    logged_in? ? "/#{current_user.tz.utc_offset}" : ""
  end
end

class TestController < ApplicationController
  caches_action :index, :tag => :standard_cache_tag
  ...
end

As seen in the example for the :if option, I’ve created a predefined method to use in my descendant controller. What this method does is check if the user is logged in (using a method I’ve defined elsewhere called logged_in?) and adding the time zone associated with that user to the cache path, otherwise if the user is not logged in an empty string is returned.

So for a user not logged in, they would get the standard cache path of tmp/cache/127.0.0.1:3000/test/index.cache, but for a user logged in, the tag could generate a cache path of tmp/cache/127.0.0.1:3000/test/index/-21600.cache for one user and tmp/cache/127.0.0.1:3000/test/index/7200.cache for another. If you look at the tag created by the standard_cache_tag method, it adds a / delimiter to the tag, this just allows the cache path to create an additional directory.

So now the index action is cached with correct times for users viewing the page with different time zone settings.

Cleaning Up

For actions that are cached with an additional :tag, the usual method of using expire_action can still be used.

So using the example above…

1
2
3
class TestController < ApplicationController
  caches_action :index, :tag => Proc.new { |controller| controller.user_type.to_s } # This will cache the page dependant on the type of user logged in. 
end

We can expire the index action for an “admin” with expire_action…

1
expire_action :controller => 'test', :action => 'index', :tag => Proc.new {'admin'}

This method works, but it is somewhat static in that you can’t specify multiple strings for the :tag.

The preferred method of expiring cached data that uses a :tag is to use the expire_fragment method with a regular expression (regex).

We could then expire all index caches for the Test controller using…

1
expire_fragment %r{test/index.*}

This will match the path after the the main url, so both tmp/cache/127.0.0.1:3000/test/indexmoderator.cache and tmp/cache/127.0.0.1:3000/test/indexadmin.cache will match the above regex. Note that this will also match a path of tmp/cache/127.0.0.1:3000/test/index.cache which would result from caching the index action with no :tag.

If we want to expire the cache for the admin tag only, we could use…

1
expire_fragment %r{test/indexadmin.*}

This will only match the tmp/cache/127.0.0.1:3000/test/indexadmin.cache path. This example does the same thing as using expire_action in the example above, but for cases like these where you know what the :tag will be when expiring, using expire_action is preferred as it would perform faster over the expire_fragment approach.

Additional Usage

Of course the :if and :tag options can be used together…

1
2
3
4
class TestController < ApplicationController
  caches_action :index, :if => :standard_cache_test, :tag => :standard_cache_tag
  ...
end

And if you do use predefined methods, they can easily be combined with additional information…

1
2
3
4
5
6
7
8
9
10
11
12
class TestController < ApplicationController
  caches_action :index, :if =>:custom_cache_test, :tag => :custom_cache_tag

protected
  def custom_cache_test
    standard_cache_tag || test_some_other_condition
  end

  def custom_cache_tag
    standard_cache_tag + Date.now.day.to_s
  end
end

Summary

Hopefully this tutorial provides a thorough explanation to the additional benefits and flexibility that can be achieved using this plugin. If you use the plugin and find it handy, please leave comments here or visit the plugin page and leave a comment or a good rating. :)

Update for Rails 2.1

Removed the :if option since Rails now provides this functionality.

Game Card Feature

A game card feature has just been added to Redzone Leagues. The game card is used to help a referee, statistician, etc. to keep track of game stats for individual players during a game. Any game that has not been assigned a score will now display a link to a printable game card.

The game card contains the date, time, location, team names and rosters along with team colours and sections for game time-outs and scores.

This feature is aimed at providing something useful to as many leagues as possible, but may be missing some information for other leagues. If you feel something is missing or there are some problems that you are experiencing using the game cards, please leave a comment or post a message to the forum

User Signup Changes

We’ve recently made some changes to make it easier for users to signup for a league account.

  • Optional email address for administrators creating new users.
  • Optional password.
  • Optional address information and home phone number.

Optional email address for administrators creating new users

Previously an email address was mandatory for all users. Since we don’t exist in a perfect internet world, not all users have an email address. The way around this was for a user or an administrator to create a new user and simply provide an invalid email address in order to register a user. A user must be registered in order to be added to a team.

The latest change makes the email address optional when an administrator creates a new account. The user will now exist in the system, but they won’t be able to log in and use the website themselves since an email address is required to login to the site. If they wanted to login, then chances are they would have had an email address to sign up with anyway. An administrator can still add an email address to a user account at a later date to enable a user to confirm their account and login to use the site. An email address is still mandatory for a user registering with the league themselves.

Optional password

If a password is not specified, a random one will be generated now.

Optional address information and home phone number

Requiring address information and a home phone number is not really necessary and simply makes the signup process more complicated. Some users also don’t feel comfortable providing that much personal information if it is not necessary and in the end they could just enter invalid data, which is just an extra annoyance. So all of these fields are now optional.

Selection Javascript Lib

I’ve created a basic first draft for a javascript selection library. The library allows the user to select many elements on a web page with a simple mouse drag.

I call this version 0.1 as it has only been tested in Firefox and has some minor issues that need some cleaning up, but I figured I might as well make it public so people can take a look, try it, give feedback, or send patches.

You can grab the files and see a demo of it here

The library requires prototype and builder… both of which come with Rails.

Patches and feedback are welcome.