- loading...
map_by_method – the final announcement
I don’t really talk about my projects after I release them except to show off fancy new things, like newgem sporting the new RubiGen generator.
So I don’t know why I give updates here about one of the smallest projects – map_by_method.
Probably, its because it doesn’t deserve a Google Group or even a webpage really. It should probably be integrated into activesupport gem or something.
Except, it just never seemed to 100% work.
That is, it go more blog coverage than anything else, but wasn’t always useful. Yeah, a like Miss South Carolina.
BUT Version 0.8.2 is now out. It fixes – I believe – all known problems – I think.
gem install map_by_method
In your code (or config/environment.rb for rails):
gem 'map_by_method', '>=0.8.2' require 'map_by_method'
I added a few more iterator methods too: sort_by, group_by, and index_by.
>> Conference.find(1).conference_sessions.group_by_from => returns all MyConfPlan conference sessions grouped by start time, for RailsConf2007
MyConfPlan…
Oh yes it sold. Announcement coming soon.
Plus, a link to the code – as it is being open sourced by its new owner. Sweet!
Well… sweet for you. I already had access to the code.
Next on the todo list
Add something similar to Ambition by Err “we-write-so-many-cool-projects-I-don’t-know-how-we-find-time-to-maintain-them-all” the Blog, Chris and PJ.
I’d kind link something like:
Conference.select { |c| c =~ "Rails").sort_by_name
Which would behave like:
>> Conference.select {|c| c.name =~ /Rails/}.sort_by(&:name).to_sql
=> "SELECT * FROM conferences WHERE conferences.\"name\" ~ 'Rails' ORDER BY conferences.name"
Oh well, perhaps we can talk about it during RailsConf Europe, when I get the microphone to talk about such tom-foolery and hi-jinx: Meta-Magic in Rails: Become a Master Magician
map_by_method now works with ActiveRecord associations
I was always annoyed that map_by_method was broken for ActiveRecord has_many associations. 6 mths later I finally fixed it.
That’s the magic of Open Source Software. [/end sarcasm]
So now, the following example works like it should:
$ gem install map_by_method $ console > require 'map_by_method' # stick this in your environment.rb for Rails > user = User.find_by_name "Dr Nic" > user.companies.map_by_name => ['Dr Nic Academy', 'Dr Nic Institute of Being Silly'] > user.companies.map_by_id_and_name => [[1, 'Dr Nic Academy'], [9, 'Dr Nic Institute of Being Silly']]
Recap: why use map_by_method?
Try the following example:
> user.companies.map_by_employees.flatten => list of all employees of user
Versus:
> user.companies.map { |company| company.employees}.flatten
or
> user.companies.map(&:employees).flatten
Or compare:
> user.companies.map_by_id_and_name => [[1, 'Dr Nic Academy'], [9, 'Dr Nic Institute of Being Silly']]
Versus:
> user.companies.map { |company| [company.id, company.name]}
That is, it looks and feels just like ActiveRecord’s #find method, with its find_by_first_name_and_last_name magic.
Summary
No {, }, |, &, or : required. Just clean method names.
Bonus other gem
In the spirit of ActiveRecord hacks, there is to_activerecord:
$ gem install to_activerecord $ console > require 'to_activerecord' # stick this in your environment.rb for Rails > [1,2,3].to_user => [list of User with id's 1,2,3]
To me, this suffix operator reads cleaner than the traditional:
> User.find([1,2,3])
For example, if you want to perform an operation on the list of Users:
> ids = [1,2,3] > ids.to_user.map_by_name => ['Dr Nic', 'Banjo', 'Nancy']
Versus:
> User.find(ids).map_by_name
map_by_method now increasingly more niftier-er.
Recap on map_by_method magic:
>> p.account.transfers.map_amount # amount method mapped to all Transfers => [1878.35, 433.0, 433.0, 433.46, 433.46, 433.46, 433.46, 433.46, 300.0, 300.0] >> p.account.transfers.map_date.map_to_s # date method mapped to all Transfers, and the Dates mapped into strings => ["2003-05-27", "2003-06-23", "2003-06-27", "2003-07-04", "2003-07-11", "2003-07-18", "2003-07-25", "2003-08-08", "2003-09-25", "2003-10-03"]
But if you want the amount and date methods mapped at the same you’ve been plain out of luck, and forced to use the uglier block syntax:
>> p.account.transfers.map {|t| [t.amount, t.date]}
Due to demand-by-inspiration (that is, I just thought of it), you can now use _and_ to join method names together, thusly:
>> p.account.transfers.map_date_and_amount [[#<Date: 4905573/2,0,2299161>, 1878.35], [#<Date: 4905627/2,0,2299161>, 433.0], [#<Date: 4905635/2,0,2299161>, 433.0], [#<Date: 4905649/2,0,2299161>, 433.46], [#<Date: 4905663/2,0,2299161>, 433.46], [#<Date: 4905677/2,0,2299161>, 433.46], [#<Date: 4905691/2,0,2299161>, 433.46], [#<Date: 4905719/2,0,2299161>, 433.46], [#<Date: 4905815/2,0,2299161>, 300.0], [#<Date: 4905831/2,0,2299161>, 300.0]] >> p.account.transfers.map_date_and_amount.map_map_to_s [["2003-05-27", "1878.35"], ["2003-06-23", "433.0"], ["2003-06-27", "433.0"], ["2003-07-04", "433.46"], ["2003-07-11", "433.46"], ["2003-07-18", "433.46"], ["2003-07-25", "433.46"], ["2003-08-08", "433.46"], ["2003-09-25", "300.0"], ["2003-10-03", "300.0"]]
Note the dubious/amusing use of map_map_to_s to apply to_s to the inner arrays. That is, map_to_s is mapped to each inner array.
Want this extended map_by_method magic? Version 0.5 of map_by_method is ready via gem install map_by_method now (or direct download)
Bonus use
Make simple arrays for drop-down lists based on id and a string name field of a list of objects.
>> User.find(:all).map_id_and_firstname => [[1, "Glenda"], [2, "Brenda"], [3, "Yas"], [4, "Osborne"], [5, "Gary"], [6, "Anthony"], [7, "Jackie"]]
Debugging use
Some of my Transfer rows had amounts of zero. This was just stupid, but as the initial data was being migrated from a variety of sources I needed to figure out where it was coming from.
“I wonder which account owner(s) this erroneous data comes from?”
> zero_transfers = Transfer.find(:all,:conditions => ['amount < 10 and amount > -10'])
> zero_transfers.map_from_account.map_owner.map_name
["Brenda",
"Brenda",
"Brenda",
"Brenda",
"Brenda"]
And I now knew which files to start cleaning up before re-importing all the data again.
Data investigation in the console/irb becomes almost fun.
My .irbrc for console/irb
The relatively unspoken warhorse of Ruby/Rails programming is the irb/console [1]. Ruby is such a joy to work with in part from this one development tool.
I got tired of typing require 'pp' and the like each time I loaded up my console, that I was joyed to discover I could configure it: using the .irbrc file (win32 explanation below).
Here is mine:
require 'irb/completion' require 'map_by_method' require 'what_methods' require 'pp' IRB.conf[:AUTO_INDENT]=true
Here is what each line does:
Line 1 – Auto-completion in IRB
You can discover what methods can be called on an object. Double-TAB is your “exploration” combo.
irb(main):001:0> "hello".to_<double TAB here> .to_a .to_f .to_i .to_s .to_str .to_sym irb(main):003:0> a = "hello" => "test" irb(main):004:0> a.t<double TAB here> a.taint a.to_f a.to_str a.tr! a.type a.tainted? a.to_i a.to_sym a.tr_s a.to_a a.to_s a.tr a.tr_s!
Not understanding this? Watch the video
Line 2 – Map by Method
When in the console its great to be able to explore and manipulate data as quickly as possible. The Map by Method (previously called Map by Pluralisation) is wizardly for quick-to-type array manipulations.
See the original and demo articles.
To install:
gem install map_by_method
Line 3 – MethodFinder/Object.what?
Ever asked: “if I have an object, what method can I call on it to get that result?”
See if this suits your console cravings:
> 3.45.what? 3 3.45.truncate == 3 3.45.to_i == 3 3.45.prec_i == 3 3.45.floor == 3 3.45.to_int == 3 3.45.round == 3 => ["truncate", "to_i", "prec_i", "floor", "to_int", "round"] > 3.45.what? 4 3.45.ceil == 4 => ["ceil"] > 3.55.what? 4 3.55.ceil == 4 3.55.round == 4 => ["ceil", "round"]
Just what you need in the console.
I’ve gemified a library found on _why’s blog by Nikolas Coukouma.
To install:
gem install what_methods
Line 4 – pretty print
pp = pretty print – wondeful in the console for exploring objects that are visually large.
Thusly demonstrated:
> me = {:name => "Nic Williams", :country => "Netherlands", :city => "Amsterdam", :tags => %w(ruby rails javascript)}
=> {:country=>"Netherlands", :name=>"Nic Williams", :city=>"Amsterdam", :tags=>["ruby", "rails", "javascript"]}
irb(main):017:0> pp me
{:country=>"Netherlands",
:name=>"Nic Williams",
:city=>"Amsterdam",
:tags=>["ruby", "rails", "javascript"]}
=> nil
It prints, and its pretty.
Line 5 – auto-tabbing turned on
Auto-tabbing is the console’s way of trying to help your code look pretty by indenting your blocks, etc.
It’s only primitive, but better than a kick in the teeth.
> [1,2,3].each do |a| * puts a > end 1 2 3 => [1, 2, 3]
.irbrc for Win32
Create a file called anything you like (e.g. “_irbrc” or “irb.rc”) and place it anywhere you like (say C:\Documents and Settings\IRBRC, e.g. C:\Documents and Settings\
More ideas at the bottom of this.
Epilogue
Any other useful libraries to include in irb/console?
[1] You can run irb from anywhere, but to use console for a specific rails app, you need to point to the console script for that app, e.g ruby script/console from the rails app root folder.
[ANN] Generating new gems for graceful goodliness

I don’t like you [1]. You don’t share code. I know, I know, you’ve had good reasons – you don’t know how to create a Ruby gem, how to upload it to a gem server like RubyForge, and you’re a chicken. Today we’ll remove the first of these minor roadblocks, with a New Gem Generator!
Now you can take any library or Rails plugin or command line application, gemify it, and easily share it with the Ruby world. With gems you get in-built version support (you can specify which version of a gem you want when you use it via the require_gem method), an encapsulated, consistent folder structure for your bin/lib/test folders, and you get cross-platform support for bin apps. Too much niftiness to ignore, really.
The New Gem Generator is like the rails command for rails applications, but it creates the folders and starting files for a new gem. It’s called newgem.
Tutorial
Aim
To convert the Map by Method (previously called Map by Pluralisation) library into a gem.
See original and demo articles. Sex on a stick – soon to be gemified before your very eyes. (Download instructions for the prebuilt gem)
Installation
> gem install newgem
Download from rubyforge if you have firewall problems (as I do at work) and need to get the gem explicitly first. THEN run the above command in the folder you saved the gem.
Create new gem
> newgem map_by_method
creating: map_by_method
creating: map_by_method/CHANGELOG
creating: map_by_method/README
creating: map_by_method/lib
creating: map_by_method/lib/map_by_method
creating: map_by_method/lib/map_by_method.rb
creating: map_by_method/lib/map_by_method/version.rb
creating: map_by_method/Rakefile
creating: map_by_method/test
creating: map_by_method/test/all_tests.rb
creating: map_by_method/test/test_helper.rb
creating: map_by_method/test/map_by_method_test.rb
creating: map_by_method/examples
creating: map_by_method/bin
Copy in the library
The generated lib/map_by_method.rb file looks like:
Dir['map_by_method/**/*.rb'].sort.each { |lib| require lib }
This will automatically include (require), in alphabetical order, the files in the
lib/map_by_method folder. If you need the files required in a specific order, then do it explicitly here, for example:
require 'foo' require 'bar' require 'tar'
or
%w(foo bar tar).each {|lib| require lib}
For this gem there won’t be any additional library files, so we’ll just copy in the following code into the lib/map_by_method.rb file and remove the library loading code.
module MapByMethod def self.included(base) super base.module_eval <<-EOS def method_missing(method, *args, &block) super rescue NoMethodError error = $! begin re = /(map|collect|select|each|reject)_([\\w\\_]+\\??)/ if (match = method.to_s.match(re)) iterator, callmethod = match[1..2] return self.send(iterator) {|item| item.send callmethod} end return self.map {|item| item.send method.to_s.singularize.to_sym} rescue NoMethodError nil end raise error end EOS end end unless String.instance_methods.include? "singularize" class String def singularize self.gsub(/e?s\Z/,'') end end end Array.send :include, MapByMethod
Package your gem into a .gem file
From the root folder of your gem run rake package:
> rake package
(in C:/InstantRails/ruby_apps/map_by_method)
rm -r .config
Successfully built RubyGem
Name: map_by_method
Version: 0.0.1
File: map_by_method-0.0.1.gem
mv map_by_method-0.0.1.gem pkg/map_by_method-0.0.1.gem
Tada! You are the owner of a gem.
Install your gem onto your machine
Your Ruby (and Rails) applications can only use the gemified libraries once you have installed the packaged gem. This is the same for other people who will use your gem.
Typically you install a gem from a remote gem server such as rubyforge. Today, you will install the gem locally:
> gem install pkg/map_by_method-0.0.1.gem
Attempting local installation of 'pkg/map_by_method-0.0.1.gem'
Successfully installed map_by_method, version 0.0.1
Installing RDoc documentation for map_by_method-0.0.1...
Note that it created and installed RDoc documentation for the library too. Each user automatically has a copy of the generated documentation for your libraries (if you actually added documentation to your code).
Unit testing
Look in the test folder and see that it has created a map_by_method_test.rb unit test file to get you started. Put tests in there. Add more test files. Run rake test from the project’s root folder and watch all your tests succeed or fail. Be good.
Version numbers
Note that the generated gem is map_by_method-0.0.1.gem. The 0.0.1 is the version number of the gem, and you can easily change this as you wish as you gem takes on new features and fixes.
There are two common version number formats:
X.Y.Z – X = major release number, Y = minor release number, Z = patch/bug fix number
or
X.Y.Z.svn = svn is the subversion number at the time the gem was released.
The latter is the default implementation generated by newgem. If you want the simpler version number format (the 1st one), then remove the following line from your Rakefile (around line 13):
REV = File.read(".svn/entries")[/committed-rev="(\d+)"/, 1] rescue nil
Your gem has a prebuilt mechanism for specifying the X.Y.Z portion of the version number.
Go to lib/map_by_method/version.rb
module MapByMethod #:nodoc: module VERSION #:nodoc: MAJOR = 0 MINOR = 0 TINY = 1 STRING = [MAJOR, MINOR, TINY].join('.') end end
Change MINOR and TINY to:
MINOR = 1
TINY = 0
And repackage and reinstall your gem:
> rake package (in C:/InstantRails/ruby_apps/map_by_method) rm -r .config Successfully built RubyGem Name: map_by_method Version: 0.1.0 File: map_by_method-0.1.0.gem mv map_by_method-0.1.0.gem pkg/map_by_method-0.1.0.gem > gem install pkg/map_by_method-0.1.0.gem Attempting local installation of 'pkg/map_by_method-0.1.0.gem' Successfully installed map_by_method, version 0.1.0 Installing RDoc documentation for map_by_method-0.1.0...
And you’re done. Next you would upload your gem to a gem server such as RubyForge, or one your company runs internally to share gems via the “gem install” mechanism.
map_by_method already on RubyForge
If you want the map_by_method gem, its already on RubyForge, so you can remotely install it:
gem install map_by_method
Inspiration
Jay Fields created a great “ruby application setup” script.
[1] That’s not true. I do like you.




