New magical version of Symbol.to_proc

Posted by Dr Nic on September 28, 2006

Before the magic, let’s go through a Beginner’s Guide to Mapping, then Advanced Guide to Symbol.to_proc, and THEN, the magical version. Its worth it. Its sexy, nifty AND magical all at once.

Beginner’s Guide to Mapping

Need to invoke a method on each object in an array and return the result?

# call 'to_i' on each item of the list to return the list of numbers
>> list = ['1',  '2', '3']
=> ["1", "2", "3"]
>> list.map {|item| item.to_i}
=> [1, 2, 3]

That is, we called the to_i method on each item of the list, thus converting the 3 strings into 3 integers. Of course, we could have called any method on any set of objects.

Advanced Guide to Symbol.to_proc

After doing that a few times, you start wishing there was simpler syntax. Enter: Symbol.to_proc

>> list.map &:to_i
=> [1, 2, 3]

It looks like magic. Well it certainly doesn’t make any sense. But it works. (The secret: the :to_id symbol is cast into a Proc object by the leading &. That is, it calls the to_proc method on the symbol. Rest of explanation here.)

Magical version of Symbol.to_proc

Quite frankly, that’s still a lot of syntax. Plus, I normally forget to added parentheses around the &:to_i, and then latter I want to invoke another method on the result, so I need to add the parentheses which is a pain… anyway. I thought of something niftier and dare I say, more magical.

How about this syntax:

>> list.to_is
=> [1, 2, 3]

This syntax makes sense – to_is is the plural of to_i – its obvious you want the to_i’s of your list.

What’s happening here?You pass the plural of the method name to the container and it invokes the singular of the method name upon the children using the map code above. Source is below.

More examples? Want to get the names of all the objects of a list? (assuming that each item in the list has a name method)

>> contacts.map {|contact| contact.name}
=> ['Dr Nic', 'Banjo', ...]
>> contacts.map &:name
=> ['Dr Nic', 'Banjo', ...]
>> contacts.names
=> ['Dr Nic', 'Banjo', ...]

You pick your favourite. I love the last one.

Bonus examples:

>> (1..10).to_a.to_ss
=> ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
>> (1..10).to_a.days
=> [86400, 172800, 259200, 345600, 432000, 518400, 604800, 691200, 777600, 864000]
>> [2,'two', :two].classes
=> [Fixnum, String, Symbol]
>> [2,'two', :two].classes.names
=> ["Fixnum", "String", "Symbol"]
>> [2,'two', :two].classes.names.lengths
=> [6, 6, 6]

So much happy syntax in one place!

UPDATE: After conversation on Ruby Forums, we’ve come up with some new syntax:

>> (1..5).to_a.map_days
=> [86400, 172800, 259200, 345600, 432000]
>> list = [nil, 1, 2, nil, 4]
=> [nil, 1, 2, nil, 4]
>> list.reject_nil?
=> [1, 2, 4]

Neat.

How do I do this at home?

Download and include this mini-library. Source below. Have fun.

module GenericMapToProc
  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"
  String.module_eval <<- EOS
    def singularize
      self.gsub(/e?s\Z/,'')
    end
  EOS
end

# Add this to the Array class
Array.send :include, GenericMapToProc

Your favourite _why projects on one page

Posted by Dr Nic on September 27, 2006

I have 3 links on my “blog roll” on the sidebar of my blog. One is for DHH, as a tribute to Rails. One is for the Err the Blog boys who write excellent Ruby articles week after week. And the other is for my own muse, Mr Why the Lucky Stiff.

I have a working assumption that if you subscribe to my blog then you’re subscribing to Why’s blog. Thus, I rarely write about his work.

His work is prolific. At Railsconf, I asked him “how do you find the time?”. And he told me. But its a secret.

Now all his work is available in one place: http://code.whytheluckystiff.net/

Dr Nic’s Favourite Projects of Why the Lucky Stiff

Of note (that is, I’ve never seen it before til now), is the cute library MetAid – which adds a few little helper methods for meta-programming.

The Sandbox is also a project I’m fascinated with (combined with the Try Ruby interface) – imagine being able to play with the Magic Models or the Composite Primary Keys code from a webpage before you download it? Try before you buy.

Hpricot is the first port of call whenever I need to scrape data from a website. In the times before RSS feeds, all you could do was scrape other people’s websites. This is a very nice, clean API for retrieving data from within a page.

Most newbies to Ruby are pointed to two tombs of education: the Pick Axe book, and Why’s (Poignant) Guide to Ruby. Chapter 6 includes the wonders of meta-programming.

And there is more – all of it good. Much of it still uninvestigated by myself (I’ve never played with Camping and haven’t a clue what to make of Hoodwink’d).

If there was a Rubists Hall of Fame, then _why would have the adjacent Ballroom named in his honour.

Remote Shell with Ruby

Posted by Dr Nic on September 22, 2006

I wrote Composite Primary Keys and Dr Nic’s Magic Models because we have a 50+ table legacy database and I am terrorised by explicit SQL statements on a daily basis. I have a dirty secret – I don’t use either of them at work. We’ve been unsuccessful installing Ruby on our HP-UX box, then time passed, life moved on for the people involved, and so no Ruby. Ergo, SQL hell for Nicholas.

Until today. No, we still don’t have Ruby installed on our HP box, but I do have it installed everywhere else in my life. So I guess I can still access the database remotely… that’s what the TCP/IP internet protocol is for… and for executing shell commands why not remote shell into the box?

Let’s skip over why I never thought of either of these until now. Its bad for my ego. The upside of this minor revelation is that the Composite Primary Keys project will have Oracle support soon.

Remote Shell

The point of this article though is to discuss the wonders of using remote ssh (aka remote shell) to invoke shell commands via ruby without having ruby installed locally.

I made it work, it rocks and I shall briefly sing the praises for Jamis Buck. Jamis, you rock. net-ssh is a lovely API for performing remote shell commands all from ruby. Here’s a demo of what I can do (excluding the setup code which is discussed later):

>> print shell.pwd.stdout
/home/senwilli
>> shell.cd 'demo'
=> #<struct Net::SSH::Service::Shell::SyncShell::CommandOutput stdout="", stderr=nil, status=0>
>> out = shell.ls '-lart'; print out.stdout
total 16
drwxr-xr-x   2 senwilli   sv              96 Sep 22 11:56 .
drwxr-xr-x   5 senwilli   sv            8192 Sep 22 11:56 ..
>> out = shell.send_command 'ls -lart'; print out.stdout
total 16
drwxr-xr-x   2 senwilli   sv              96 Sep 22 11:56 .
drwxr-xr-x   5 senwilli   sv            8192 Sep 22 11:56 ..

I’ve deliberately been inconsistent in the way I called the shell commands and how I handled the responses to show you some interesting things.

shell is our shell object (which we’ll cover in a bit) and it has delightfully overridden method_missing so I can send it any old shell command I like. In the example above, I call the commands: pwd, cd and ls using this method. The first parameter is the arguments you’d like to pass to the command. An optional second parameter is for any stdin you’d like to pass to the process during its execution.

The last command is a duplicate of the previous command, showing that you can explicitly pass a complete shell expression string via the send_command method.

The result of all shell commands is a struct containing stdout and stderr strings. So, to return the response of the command to the user or logger, you’ll need to store the response object (as per the out variable above) and use stdout? to test if stdout has anything in it.

In the example above, all these commands are executedly being executed synchronously, so I’m getting the stdout/stderr results back before the next command is executed. This is due to the way I created the Shell object. So, let’s look at that.

Creating your Shell object

First you’ll need to install the following gems (from rubyforge.org) in this order: needle, oepnssl, and net-ssh.

In your ruby script/console:

require 'net/ssh'

session = Net::SSH.start( 'host', 'user', 'passwd' )

To create our synchronous shell:

shell = session.shell.sync

And you’re off and racing.

Jamis has provided lots of useful syntatic sugar (for example, you can pass Net::SSH.start a block with session as its argument, and reading all the docco is a clever thing to do next.

Using sudo?

The only thing I couldn’t immediately figure out is how to invoke sudo su - envname and then continue to control my shell as per normal. All commands just blocked.

Luckily there is a mailing list for me to loiter in…

[ANN] Dr Nic’s Magic Models 0.8 – Validate Anything, Anytime, Anywhere

Posted by Dr Nic on September 21, 2006

Ladies and Gentlemen, welcome one and all to the greatest Magic Show in Programming. If you bought free tickets to the last show, then you’ll be prepared to pay double for this show because its twice as good. Magic Models provide virtual validations on all database constraints!

In the first run of the sell-out Magic Models – “I can’t believe its an ActiveRecord” – Super Show – you were amazed as we stripped away the necessary essentials of an ActiveRecord – right in front of your eyes – the necessary became unnecessary.

We removed the validates_presence_of validations… and the Magic Model still validated.

We removed the has_many, belongs_to, and has_many :through associations… and the Magic Model still associated.

We removed the actual class definition itself – the very thing that provides the ActiveRecord CRUD layer… and the Magic Model still CRUDded.

You applauded loudly, and then walked out the front door surruptitiously ignoring the donation jar.

Today. Ladies and Gentlemen, I have removed the donation jar entirely so as to not distract your singular focus on the All New Magic Show!

Drums rolling in the background…

Let us start with a humble, unassuming database schema. Today’s schema syntax of choice? SQL.

CREATE TABLE `groups` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(50) NOT NULL UNIQUE,
  `description` varchar(50) default NULL,
  `some_int` integer default NULL,
  `some_float` float default NULL,
  PRIMARY KEY  (`id`)
) TYPE=InnoDB;

The first change to note, is that all Magic Models and their associations are now being generated at startup time, and not dynamically.

>> ActiveRecord::Base.send :subclasses
=> [CGI::Session::ActiveRecordStore::Session, Group]

This is the current solution to supporting the :include option on find calls. Debate the pros/cons of this on the forum. It may be helpful for the various individuals trying to get Magic Models working with Streamlined.

Now… the magic!!! Watch carefully…

>> g = Group.new
>> g.name = "x" * 51
=> "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
>> g.valid?
=> false
>> g.errors.full_messages
=> ["Name is too long (maximum is 50 characters)"]

Crikey! That’s a beauty! That name is just too big for the database and boy does the validation automatically tell you that. Fantastic isn’t it!?

Ok, that’s enough Steve Irwin impressions.

>> g = Group.new
>> g.name = "x" * 50
=> "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
>> g.valid?
=> true

Good.

Ok, more tricks… more drumrolls…

>> g.some_int = 99.9
=> 99.9
>> g.some_float = "string"
=> "string"
>> g.valid?
=> false
>> g.errors.full_messages
=> ["Some float is not a number", "Some int is not a number"]

Tada!!!

And now for the final Virtual Validation magic trick… I draw your attention to the SQL: the name field is UNIQUE. Could the magic models… could they… no… maybe? Oh maybe they could support validation over unique fields?!?!?!

100 dancing girls and guys in silver spandex appear on the stage. Of course, more drums are rolling…

>> g = Group.new
=> #<Group:0x3884eb8 @attributes={"name"=>"", ... >
>> g.name = "test"
=> "test"
>> g.save
=> true
>> g2 = Group.new
=> #<Group:0x3884eb8 @attributes={"name"=>"", ... >
 @new_record=true>
>> g2.name = "test"
=> "test"
>> g2.save
=> false
>> g2.errors.full_messages
=> ["Name has already been taken"]

Dr Nic takes a bow and is escorted off by his 100 dancers and 3 large elephants that turned up late.

Installation

gem install dr_nic_magic_models

The at the top of your Ruby script or at the bottom of your Rails’ environment.rb file, place the line:

require 'dr_nic_magic_models'

Epilogue

Matthew Painter made this possible. He is the author, producer, and cheer leader for this release. Whilst I was fart-arsing around writing Dr Nic’s GrandCivs, and he was doing something more important – making the Magic Models the ultimate magic show in town. And he has a funky website with a draggable background.

The original ideas for the extended Virtual Validations came from Red Hill Consulting: though again it was Matthew who did the work to integrate it into the Magic Models.

The whole of Magic Models architecture has been refactored. Matthew did that.

My 2+ gig of Gmail account filled with diff files. Matthew did that. You get the picture. Thanks Matt!

Anything, Anytime, Anywhere

“Anything, Anytime, Anywhere” was the slogan of the Goodies – a British TV show that was rerun over and over again after school in Australia. Their fame amongst school kids in the 70s and 80s, gives them Rock God status amongst midaged Australians today. They toured Australia in 2005 with 13 sell-out shows. I didn’t get tickets. If you did get tickets, I don’t want to know about it because I won’t talk to you.

Prototype: “element-id”.$() instead of $(‘element-id’)

Posted by Dr Nic on September 11, 2006

The Prototype library gives us the $() operation for converting a DOM element id into the DOM element: $('element-id'). It also appends a bunch of functions to the resulting object; as shown previously, you can add your own methods.

Sometimes though, passing a string into the $() function doesn’t read well; and only makes Javascript code harder to read. For example: $(window.button_list()[3]).hide()

Instead, it’d be nice to have normal chainability. I like the following syntax: window.button_list()[3].$().hide().

That is, call the $() method on a string object, instead of passing the string into the $() method.

Add this code into your application:

String.prototype.$ = function() {
  return $(document.getElementById(this));
}

UPDATE: After discussion on Ruby on Rails: Spinoffs forum

Alternate version:

String.prototype.$ = function() {
  return $(String(this));
}

But what doesn’t work is:

String.prototype.$ = function() {
  return $(this);
}

Let us know if you know why $(this) returns an array of single character strings (e.g. ["e","l","e","m","e","n","t","-","i","d"] for "element-id".$()?!