Flagger
- Current version: 0.9.5
- Release date: 31st May 2007
Installation
To get the latest release, open a command prompt in your Rails project directory and type (line wraps marked »):
script/plugin install »
http://svn.jcoglan.com/flagger/tags/0.9.5/flagger
Or, to get the latest development version:
script/plugin install »
http://svn.jcoglan.com/flagger/trunk/flagger
Introduction
Do you often find yourself writing repetitive collections of methods on your models that look like:
class Order < ActiveRecord::Base
def self.paid
find_all_by_paid(true)
end
def self.unpaid
find_all_by_paid(false)
end
def mark_as_paid
update_attribute(:paid, true)
# related tasks
end
def mark_as_unpaid
update_attribute(:paid, false)
# related tasks
end
end
This is good practise, as it keeps your column names wrapped up in the model in case they need changing, rather than littering your controllers (or other models) with them. And it increases code legibility, which is also a Good Thing.
The Flagger plugin allows ActiveRecord::Base to give you these methods without you having to write any code. It uses method_missing hooks to respond to methods not caught by ActiveRecord::Base‘s built-in method_missing code. It is designed to be used with boolean attributes, that is, attributes that contain true/false, 1/0 or "1"/"0" depending on column type. (Although, it is generally not a good idea to use string/text columns for this type of data. You may encounter problems if you attempt to do so, depending on which Rails version you are using.)
Boolean finders
Flagger allows you to use dynamic finders on boolean attributes without using find_all_by_. So, using the above example, you can happily call Order.paid to get all your paid orders, without defining the method yourself. In addition, you can call Order.unpaid, and you will get back all the orders for which paid == false. The full list of allowed prefixes is:
- in, im, un, non, dis, de, ir, and not_
If your model has an attribute whose name already begins with one of these, Flagger will take this into account. For example, TallStory.implausible returns all the TallStory records for which implausible == true. You can chop off the prefix as well: TallStory.plausible does what you‘d expect.
Only one prefix will be added to or removed from the attribute name you use to try and find matching attribute names. Fact.undeniable works if the Fact model has an attribute called undeniable, deniable, inundeniable, imundeniable … or not_undeniable. Attribute names are always checked in this order, and Flagger will use the first one that exists. If no attribute match can be found, the exception raised by ActiveRecord::Base‘s built-in method_missing code is reraised.
Attribute names can be chained together in a similar fashion to that allowed by find_by_ and friends. You can use and and but to separate names, so you could write:
orders = Order.paid_and_delivered
orders.first.paid?
#=> true
orders.first.delivered?
#=> true
martinis = Cocktail.shaken_but_not_stirred
martinis.first.shaken?
#=> true
martinis.first.stirred?
#=> false
Dynamic markers
Attribute-based markers perform the converse task to boolean finders - they set boolean attributes rather than retrieving based on them. They mark (flag) records as having certain properties. Their syntax is very similar to that for finders:
order = Order.find(:all).first
order.mark_as_paid
order.paid?
#=> true
order.mark_as_delivered_but_not_signed_for
order.delivered?
#=> true
order.signed_for?
#=> false
These methods do not save the attributes to the database. If you want to save the attributes, end the method call with a !.
mark_as_ methods detect the column type for each attribute and set values accordingly. true/false are used for :boolean fields, 1/0 for numeric fields, and "1"/"0" for :string/:text fields.
To perform tasks related to flagging, you can define after_mark_as_ methods on your models. Each callback can include one attribute name (with or without prefixes as appropriate) and can end with a ! to denote whether it should be called on database saves or not. For example, Order#after_mark_as_unpaid! will be called after mark_as_unpaid!, mark_as_not_paid! and mark_as_accepted_but_not_paid!, but not after mark_as_unpaid (no exclamation mark). All the existing callbacks that apply to a mark_as_ call will be called, but only if a change has been made to the relevant attribute.
Compatibility
Flagger has been tested successfully using MySQL 5 with Rails 1.1.6 and 1.2.0 through 1.2.3. Bug reports and test reports for other databases are welcome - my email address is at the end of this file.
Notes
Flagger is an adjusted version of a patch I submitted to Rails that didn‘t make it into the core. The source has been modified from that version in a few places. Thanks to tomafro for pointing me in the right direction and plugging holes in my Ruby/Rails knowledge.
Clearly this plugin will not cover all possible use cases for flaggable models. It is meant to try and provide some generally useful functionality out of the box, and to eliminate the need to write code for the simpler cases. If it doesn‘t quite do what you need, you can always roll your own methods to avoid hitting method_missing.
One potentially useful addition would be support for OR-based finder conditions, as in Lorry.red_or_yellow, although I suspect that including that would be excessively complicating things - how would one handle Lorry.red_and_yellow_or_green_but_not_blue? Does that mean (red && yellow) || (green && !blue), or red && (yellow || green) && !blue?
Flagger is (c) 2007 James Coglan and is released under the terms of the MIT license.
Contact: james at jcoglan dot com