Dr Nic

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

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

Related posts:

  1. map_by_method – the final announcement I don’t really talk about my projects after I release...
  2. map_by_method now works with ActiveRecord associations I was always annoyed that map_by_method was broken for ActiveRecord...
  3. 5 things I’m in love with In no specific order, but enumerated for good measure: autotest...
  4. map_by_method now increasingly more niftier-er. Recap on map_by_method magic: >> p.account.transfers.map_amount # amount method mapped...
  5. My .irbrc for console/irb The relatively unspoken warhorse of Ruby/Rails programming is the irb/console...

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

  1. 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 says:

    Okay, this is badass.

  3. Dr Nic says:

    @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 says:

    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. 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 says:

    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 says:

    @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. [...] See the original and demo articles. [...]

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

  11. Mike McKay says:

    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]

  12. Mike McKay says:

    Solved it – I had to add:

    require “rubygems”

    just before the

    require “map_by_method”

    Strange that no one else has to do this…

  13. Dr Nic says:

    @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.