I love “map by pluralisation” [now: map_by_method]

Posted by Dr Nic on October 04, 2006

Update: this is a gem called map_by_method.

The other day I introduced a new syntax idea that I call “map by pluralisation”. Everyday I use it in code and in the console/irb I fall more in love with its simplicity - both to type and to read.

The following are all equivalent:

>> BankTransaction.columns.names  # map by pluralisation
=> ["id", "amount", "date", "description", "balance"]
>> BankTransaction.columns.name   # singular works too
=> ["id", "amount", "date", "description", "balance"]
>> BankTransaction.columns.map_name  # merge collector (map) + operation (name is method of BankTransaction object)
=> ["id", "amount", "date", "description", "balance"]
>> BankTransaction.columns.collect_name  # merge collector (collect) + operation
=> ["id", "amount", "date", "description", "balance"]

All of which are easier to read and quicker to type than the current equivalents:

>> BankTransaction.columns.map {|p| p.name}  # standard map
=> ["id", "amount", "date", "description", "balance"]
>> BankTransaction.columns.map &:name  # Symbol.to_proc
=> ["id", "amount", "date", "description", "balance"]

You can now type:

>> BankTransaction.columns.select_primary  # merge collector (select) and operator (primary returns true/false on a Column object)
=> [#<...MysqlColumn:0x3c8a4f0 @limit=11, @sql_type="int(11)", @primary=true, @type=:integer, @number=true, @name="id">]

Instead of:

>> BankTransaction.columns.select {|c| c.primary}
>> BankTransaction.columns.select &:primary

Use with ActiveRecords

“Map by pluralisation” is truly wonderful in the console for exploring and collecting data models.

Without knowing a thing about the data model, I bet you can understand either of the following:

@transactions = BankTransaction.find :all, :conditions => ['date = ?', Date.today], :include => [:accounts => [:owner]]
return @transactions.collect_accounts.select_overdrawn?.collect_owner.full_names

Answer: the full name of each owner of an overdrawn bank accounts for all today’s bank transactions.

It reads well as there is a minimum of {, }, |, &, : characters.

Download

Code available here

Tip for use with ActiveRecord Associations

You may need to cast associations to Arrays.

@account.transactions.to_a.map_dates

The result of the transactions assocation on the Account class is not an Array. My initial attempts to provide the “map by pluralisation” code (a method_missing) didn’t work, and casting it to an explicit Array using to_a seemed simple and harmless.

Discussion of Syntax Ideas

Ruby Forum

Trackbacks

Use this link to trackback from your own site.

Comments

Leave a response

  1. Greg Spurrier Wed, 04 Oct 2006 19:02:34 UTC

    I like it! I read your previous post, but the coolness didn’t really sink in until your overdrawn bank account example.

    One minor suggestion on the syntax: I think it reads a lot better if you leave off the question mark for the select_xxx methods and then add it back in under the covers before invoking the predicate. E.g., instead of select_overdrawn? you’d have select_overdrawn.

    My reasoning is that a question mark indicates that the method is a predicate, but select_overdrawn[?] is not one. It’s “select all that are overdrawn” not “did I select one that is overdrawn?”

    Anyway, that’s my two cents.

  2. Chris Thu, 05 Oct 2006 00:34:59 UTC

    Okay, this is badass.

  3. Dr Nic Thu, 05 Oct 2006 08:06:03 UTC

    @greg - implementing select_overdrawn? assumed that there was a method overdrawn? on the Account objects in the list. So it cuts away the select (collector) and the rest (overdrawn?) and collects the results of applying the rest (overdrawn?) upon the items.

    But, I certainly agree that the resulting method is confusing in itself: select_overdrawn? doesn’t return a boolean nor return a single result.

    What to do… hmm. Not sure yet either.

    @chris - well spotted my good chap.

  4. SonOfLilit Thu, 05 Oct 2006 09:31:34 UTC

    Simple solution:

    Add syntax to make it possible to do select_by_overdrawn?

    Reads much much better.

    Amazing work! The reason I like ruby is at not only it reads great and is written great, but it has a culture devoted to magically make it even more readable!

    SonOfLilit

  5. Greg Spurrier Thu, 05 Oct 2006 18:50:46 UTC

    Dr. Nic,

    What I was thinking was that you’d take into account which iterators take blocks that are expected to be predicates and then append a ‘?’ to the end of the method name in your method_missing implementation before sending it to the object.

    By convention, all predicates are supposed to have question marks at the end of their name. Just embrace that convention and require it. :)

    So, you’d still have overdrawn? the predicate, but it’d be select_overdrawn the magical method.

    BTW, yesterday I came across a something similar that you might enjoy reading: http://nat.truemesh.com/archives/000535.html

    Greg

  6. lninyo Sat, 07 Oct 2006 00:17:10 UTC

    That is awesome! (and a bit reminiscent of Django) am I correct? I remember seeing something like this when I was checking out django some time ago.

  7. Dr Nic Sat, 07 Oct 2006 09:55:03 UTC

    @SonOfLilit - I like that idea as a fix for readability. I’ll still keep select_overdrawn? as something that works because in the console I can be just that lazy, but for maintainability/re-readability I like select_by_xxx, map_by_yyy, etc.

    @greg - would select_by_xxx? solve the readability issues for you?

    @lninyo - django/python could certainly implement this as python has a method_missing mechanism as well, but I haven’t looked at Django beyond intro pages and the Snakes vs Rubies video.

  8. [...] See original and demo articles. Sex on a stick - soon to be gemified before your very eyes. (Download instructions for the prebuilt gem) [...]

  9. Dr Nic » My .irbrc for console/irb Thu, 12 Oct 2006 13:19:38 UTC

    [...] See the original and demo articles. [...]

  10. [...] Recap on map_by_method magic: [...]

  11. KDr2 » 简单漂亮的打包GEM Fri, 05 Jan 2007 10:03:50 UTC

    [...] 1.目标: 把 Map by Method(曾经叫做 Map by Pluralisation) library 打包成gem。 查看我们将要打包的代码的相关链接:original and demo。 [...]

  12. Mike McKay Thu, 28 Jun 2007 16:13:44 UTC

    I have a strange situation. If I do require “map_by_method” within my .irbrc file it doesn’t work. But I run require “map_by_method” within irb or script console it works great. Any ideas why this would happen?


    # cat /home/crazy/.irbrc
    ARGV.concat [ "--readline", "--prompt-mode", "simple" ]

    require ‘irb/completion’
    require ‘irb/ext/save-history’
    require ‘pp’

    IRB.conf[:AUTO_INDENT]=true
    IRB.conf[:SAVE_HISTORY] = 500
    IRB.conf[:HISTORY_FILE] = “#{ENV['HOME']}/.irb-save-history”

    require ‘map_by_method’ # Can’t get this to work

    # script/console
    Loading development environment.
    >> ["1","2"].collect_to_i
    NoMethodError: undefined method `collect_to_i’ for ["1", "2"]:Array
    from (irb):1
    >> require ‘map_by_method’
    => true
    >> ["1","2"].collect_to_i
    => [1, 2]

  13. Mike McKay Fri, 29 Jun 2007 08:55:07 UTC

    Solved it - I had to add:

    require “rubygems”

    just before the

    require “map_by_method”

    Strange that no one else has to do this…

  14. Dr Nic Fri, 29 Jun 2007 08:58:53 UTC

    @Mike - ahh, yes you will need to add require 'rubygems' in your .irbrc file (and wherever else you want to access rubygems). Sorry I didn’t get around to looking at this before you solved it. Thanks.

Comments