Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #7735 (closed enhancement: wontfix)

Opened 2 years ago

Last modified 2 years ago

[PATCH] Add boolean finders and markers to ActiveRecord::Base

Reported by: jcoglan Assigned to: core
Priority: normal Milestone: 1.x
Component: ActiveRecord Version: edge
Severity: normal Keywords: boolean dynamic finders markers
Cc: tomafro

Description

I often find myself writing repetitive bunches of methods for setting boolean attributes and retrieving records based on them. For example:

class Order < ActiveRecord::Base
  def self.paid
    find_all_by_paid(1)
  end

  def self.unpaid
    find_all_by_paid(0)
  end

  def mark_as_paid
    update_attribute(:paid, 1)
  end

  def mark_as_unpaid
    update_attribute(:paid, 0)
  end
end

This patch lets ActiveRecord provide such methods out-of-the-box by extending ActiveRecord::Base#method_missing. It gives you dynamic attribute-based finders and markers that can be chained together, as in

Order.paid_and_delivered
country.mark_as_hot_and_sunny

or made negative using prefixes, like

Order.unpaid
user.mark_as_irresponsible

Ending mark_as calls with '!' saves the changes to the database, while omitting the '!' just sets the attributes to true/false without saving. If you specify after_mark_as_xxx callbacks on your models, these will be called as appropriate.

The patch contains full documentation of these new features and updated unit tests. I have only tested this with MySQL so far, and would greatly appreciate testing with other DBs. Also, there is one bug I'm not sure how to correct - this is commented in the unit tests, see the diff for more info.

Attachments

boolean_finders_and_markers.diff (13.9 kB) - added by jcoglan on 03/06/07 15:11:44.
Patch for ActiveRecord::Base to enable dynamic boolean finders and markers. With docs and unit tests

Change History

03/06/07 15:11:44 changed by jcoglan

  • attachment boolean_finders_and_markers.diff added.

Patch for ActiveRecord::Base to enable dynamic boolean finders and markers. With docs and unit tests

03/06/07 18:10:20 changed by tomafro

  • cc set to tomafro.

I don't find Order.mark_as_paid that much clearer than Order.update_attributes(:paid => true).

The finder methods are better, though it might be nice to precede them with find, similar to the current dynamic finders i.e find_unpaid, find_all_unpaid

Finally, it's going to be really hard to find a natural sounding inverse to every boolean parameter. Using your example above, what are the opposite of hot and sunny? unhot and unsunny?

So, while I can see how it can be useful in some circumstances, IMO I don't think it' can ever be comprehensive enough or adds enough value to deserve be part of rails core. If you released it as a plugin however, I'm sure you'd make many people happy.

(follow-up: ↓ 3 ) 03/06/07 18:43:28 changed by jcoglan

What if 'not_' were allowed as a prefix? I did consider that listing all the known opposite word pairs would be way more effort than it was worth, but I'd like to come up with a general ruleset that covers most plausible use cases. Besides, if you're really unhappy with the method names this generates, you can always roll your own. This is supposed to give you some generally useful functionality without writing any code.

My main motivation for this patch is increasing code legibility - Rails excels in that it allows you to write controller code that reads like a story. I find myself writing these methods again and again simply for that reason - they make my code more legible and keep column names all wrapped up in the model in case you need to change them. If you've use mark_as_paid all over your app, you can just define this method yourself in the model if you need to change the column name.

The other reason is that including this as a plugin or as library code means copy-pasting the existing method_missing hooks from ActiveRecord, which means it needs maintaining should these hooks change across Rails versions.

(in reply to: ↑ 2 ) 03/06/07 19:15:15 changed by tomafro

Replying to jcoglan:

My main motivation for this patch is increasing code legibility - Rails excels in that it allows you to write controller code that reads like a story. I find myself writing these methods again and again simply for that reason - they make my code more legible and keep column names all wrapped up in the model in case you need to change them. If you've use mark_as_paid all over your app, you can just define this method yourself in the model if you need to change the column name.

To clarify, I do think your patch makes some model methods clearer, just not enough to justify inclusion. I also fully support the goal of making code as easy to read as possible and reducing repetition in your app. Again, this is something I can see your patch does, but again, IMO not enough to justify inclusion. That said, I'm not a member of core so the decision isn't ultimately mine.

The other reason is that including this as a plugin or as library code means copy-pasting the existing method_missing hooks from ActiveRecord, which means it needs maintaining should these hooks change across Rails versions.

If this were the case it would be a compelling argument for inclusion, but it's not. Here's how you can implement a plugin to intercept method_missing, without having to copy/paste active record code. This is off the top of my head (as usual), so be lenient!

module MethodMissingInterceptor
  def self.included(base)
    base.class_eval do
      alias_method_chain :method_missing, :interception
    end
  end

  def method_missing_with_interception(method, *args, &block)
    if (method_should_be_intercepted?(method))
      handle_intercepted_method
    else
      # call to original method misssing
      method_missing_without_interception
    end
  end
end

ActiveRecord::Base.send :include, MethodMissingInterceptor

Using this, all calls to ActiveRecord::Base#method_missing will go to MethodMissingInterceptor#method_missing_with_interception, which can choose whether to pass them on to the original method or not.

03/06/07 20:24:37 changed by bitsweat

  • status changed from new to closed.
  • resolution set to wontfix.

Neat idea. This is perfect for a plugin rather than going straight into core.

03/07/07 19:25:13 changed by dcmanges

For the finders take a look at the scope_out plugin. It doesn't provide the mark_* methods, but you can easily create the finders.

  class Order < ActiveRecord::Base
    scope_out :paid
  end
  Order.find_paid(:all)