Alternative to method_missing in Rails

Yes. it has been a long time since I’ve posted anything, but I’m sure I’ll want to refer to this in the future.

While developing some new functionality for TheBigFork, I found an opportunity to take advantage of the mysterious “method_missing” method in my ActiveRecord models. I have two models: Website and Setting. A website has many settings. Nothing earth-shattering there. To simplify the explanation, let’s just say I want to be able to load a value from the settings table with something like this:

website.site_name
=> "TheBigFork"

Where the “site_name” comes from the related settings. The longer, built-in way to do this is:

website.settings.where(:name => 'site_name').first.value
=> "TheBigFork"

Okay, so the “method_missing” approach would look something like this:

class Website < ActiveRecord::Base
  has_many :settings

  def method_missing(method)
    self.settings.where(:name => method).first.value
  end
end

(I really need to figure out a better way to post code in WordPress. Maybe it is time to move this blog to my own server where I can manage plugins. Anyway…)

That approach sort-of works. It seems to work better in some versions of rails than others. Some problems show up because “website.respond_to?” returns false for these pseudo methods. That could be fixed by adding this to the Website class:

def respond_to?(method)
  if self.method.to_s != 'settings' && self.settings.where(:name => method)
    true
  else
    super
  end
end

But that still gave me all sorts of “stack level too deep” errors. I stumbled upon Jay Field’s post, Replace method_missing with dynamic method definitions. Just what I needed. Except it wasn’t a Rails-specific solution. Just ruby in general. Apparently, classes that inherit from ActiveRecord::Base may or may not load with your own initialize method. So, I used the after_initialize hook like so:

class Website < ActiveRecord::Base
  has_many :settings
  after_initialize :dynamic_methods

  def dynamic_methods
    self.settings.each do |setting|
      (class << self; self; end).class_eval do
        define_method setting.name.to_sym do |*args|
          self.__send__("get_value", setting.name, *args)
        end
      end
    end
  end

  def get_value(name)
    self.settings.where(:name => name).first.value
  end

end

Works like a charm. It even answers correctly to the “respond_to?” for each setting.

Now, it is a bit expensive to dynamically create lots of methods every time you initialize a new instance, so I probably wouldn’t recommend this approach if you have tons of settings for each website. However, in my application, I actually have a third model involved (Brand) which shares settings among websites. The website actually caches the values, and this solution works nicely.

Bountiful Standard Optical

What a sad excuse for customer service…

Standard Optical Poor Customer Service

Based on this experience, you should avoid the Bountiful, Utah, Standard Optical, or Standard Optical in general. Or at least check to make sure Jill is not planning any vacations before you make your appointment.

Setting up Ubuntu for Ruby on Rails development

NOTE: This is a work-in-progress. I’m publishing it early so that Sam can use it. If it doesn’t work for you exactly as expected, well, that’s to be expected.

Every time I setup a new Ubuntu machine, I have to search around to come up with a procedure for getting everything I need to develop Ruby on Rails apps. Here it is pre-packaged.

(The configuration below is based on my current preferences: Ubuntu 9.04, ruby 1.8, rails 2.3.2, mysql 5, subversion plugin for Eclipse (subclipse), Eclipse with Aptana and RadRails. Ruby 1.9 is starting to gain momentum, but I haven’t used it yet. Oh, and most of the instructions below are for command line users. Try it. You’ll like it.)

After installing Ubuntu 9.04 (which is not covered here), install Eclipse, subversion, ruby and mysql like so:

sudo apt-get install eclipse ruby libmysql-ruby mysql-client mysql-server irb
mongrel rdoc ri ruby-dev rubygems subversion

The first thing to note is that while Eclipse works with the out-of-the box java installed with Ubuntu 9.04, Aptana does not. So install the Sun Java JRE like so:

sudo apt-get install sun-java6-jre

Then, configure the system to use the new java by default:

sudo update-alternatives --config java

With Eclipse, you can’t rely on the command above to make it use the Sun Java. So, edit the Eclipse configuration file:

sudo nano /etc/eclipse/java_home

Comment out the java-gcj line and add a line for sun just after it:

#/usr/lib/jvm/java-gcj
/usr/lib/jvm/java-6-sun

Fire up Eclipse from the Applications menu. (Applications -> Programming -> Eclipse). Then, install the Aptana plugin. Within Eclipse, click on Help -> Software Updates -> Find and Install -> Search for New Features to Install. Add a new Remote Site for updates with the following URL:

http://update.aptana.com/update/studio/3.2/

Make sure it is checked, then click Finish. Eclipse should then walk through the Aptana installation. After restarting, you’ll see the Aptana welcome screen. Look for the link which will install RadRails. Once you’ve done that, it is time to install subclipse–the subversion plugin for Eclipse. Go to Help -> Software Updates -> Find and Install -> Search for New Features and create a new remote site with the following URL:

http://subclipse.tigris.org/update

Go through the same steps from the previous step to add the SVN client.

That’s it for the installation. You’ll probably want to customize some settings in Eclipse. Make sure the settings for the ruby interpreter, rake, mongrel, etc.  are accurate. I like to turn line numbers on in the Text Editor preferences.

Your project may require one or more “gems” for ruby. Most can be installed with a command like this:

sudo gem install [gem-name]

Of course, you haven’t even installed rails yet. So do that:

sudo gem install rails

One gem you’ll need is rake. The rake package from the Ubuntu repository is old. Get the new one and then symlink it to make it more accessible:

sudo gem install rake
sudo ln -s /var/lib/gems/1.8/gems/rake-0.8.4/bin/rake /usr/bin/rake

And, it isn’t a bad idea to put the gems’ bin folder in the system path so things like autospec work correctly. To do so, create this file:

sudo nano /etc/profile.d/gems.sh
# Here's the one-line for the file:
export PATH=$PATH:/var/lib/gems/1.8/bin

Reboot or just logout to trigger it. This wordpress editor is super annoying, so I’m done now.

Rain8net Ruby Library v1.0.0

I previously wrote about a product called Rain8net. I’m happy to announce the initial release of my Rain8net ruby library. The library is now available from rubyforge.org.

Or, just install it like this:

gem install rain8net

Now you won’t have to worry about sending the specific codes I detailed in my last post. Controlling your sprinklers is as easy as:

r8 = Rain8net.new
r8.turn_on_zone(1)
r8.turn_off_zone(1)

See the online documentation at the project homepage for more information.

Irrigation 2.0 with Rain8Net

The problem: Automatic sprinklers are great for convenience but waste a ton of water. You see businesses and neighbors running sprinklers during a rainstorm all the time–right? Rain conscious individuals can turn their sprinklers off when not needed, but you lose convenience and sometimes it is a pain to get back on track.

The solution: Rain8Net from WGL Designs, a PC with a serial port, and a little bit of Ruby programming.

Disclaimer: this post is a bit premature. I just got my Rain8Net last weekend and spent some time trying to program it. I plan to script the whole system so that it reads weather reports from the Internet to determine irrigation needs. That part isn’t ready yet. This is just an introduction to what I’ve discovered so far.

That being said, here is a sample of how to communicate with the Rain8Net via Ruby. (First, be sure to download and install the ruby-serialport library.)

require 'serialport.so'
tty = 0
rain8 = SerialPort.new(tty, 4800, 8, 1, SerialPort::NONE)
# Turn on Zone 1
rain8.write(["400131"].pack("H*"))
sleep(60)
# Turn off Zone 1
rain8.write(["400141"].pack("H*"))

WLG Designs has great documentation explaining what the various codes do. I am providing the code above as an example of how to implement the provided codes. (It took me many tries to get this far. Hopefully it will save someone else a bit of work.)

I plan to develop a Ruby library for use with the Rain8Net which will make it much easier to use. Watch for it…

Build a Development Infrastructure…or buy a Mac

Disclaimer: Let me start out by saying I am not anti-Mac. That being said, I am very pro-Linux. I am not a Dell lover either. I just picked them for comparison.

As a web developer, I have found it extremely beneficial to have access to my own servers for development purposes. I have my own infrastructure for version control. I maintain my own DNS servers and can quickly provide access to a temporary site for clients.  While developing TheBigFork, I developed several long-running data cleanup scripts. These would not have been possible without an always-on server to do the work.

Great. So, what does that have to do with buying a mac? Nothing, really. I recently have become aware that more and more web developers (especially Ruby developers) are developing on a Mac. Frankly, I don’t get it. I’ve tried it, and it doesn’t come close to developing on Linux. But, that’s really a discussion for another day. This article is about price.

I compared the price of a MacBook Pro 17″ 2.5 gHz notebook with a collection of Dell machines. The MacBook is listed at $2799. For that much money, you could buy:

Seven Dell Vostro 200 desktops with a 20″ widescreen display and a 250 gig hard drive. These come with Windows XP, but you can easily reformat it and install Ubuntu or Fedora. Doing so opens up a whole world of freely available open-source software. (And, don’t argue that Linux isn’t ready for the desktop. If you can switch from Windows to Mac, you can easily make the switch to Linux.)

With 7 computers, you can hire a receptionist, bookkeeper and project manager and still have a few extra machines for some junior developers. Granted, these aren’t the most powerful machines out there and they probably aren’t worthy of acting as a server–unless you’re desperate.

Here’s another scenario. Buy a Dell PowerEdge 840 server plus a Dell Precision T4300 workstation (which alone rivals the macbook’s specs). The only problem is, you’ll have almost $1400 extra. I guess you’ll have to buy a Dell Latitude D830 laptop for working on-the-go, and a second 21″ monitor for your workstation. Wait–you still have extra money. So, grab a Vostro 200 from the previous scenario. Now you have four machines: a server, a high-powered 64-bit workstation, a nice notebook and an extra desktop for your assistant.

If you really need a laptop with more horsepower than the MacBook, just get the Precision M6300 mobile workstation instead of the workstation and notebook above. You’ll still have enough for a server and the spare desktop.

Finally, if computers aren’t your thing, put a down-payment on a nice new car!

Bottom line: Macs are very nice. To me, they are simply not worth the extra cash–especially when you have work to get done.

In case you’re wondering, the best Ruby IDE that I’ve found is Aptana Radrails which runs poorly on Mac and excellent on Linux. Textmate is the Ruby IDE of choice on Mac. I found it lacking in features and difficult to use.

Pinewood Derby 2008

Sometimes a picture is worth…

RFC_READ_TABLE with Ruby and SAP::Rfc

Warning: another code sample is included in this post.

I spent a few hours this morning trying to use Piers Harding’s SAP/Rfc library for Ruby to read a table from SAP. I found several examples using other languages (Perl, VBscript, PHP, etc.), but the only Ruby example I could find reads the entire table. Figuring out how to load the “options” took some trial and error.

The idea of this example is to read the “LQUA” table in SAP which stores information about where to find a particular material in the warehouse. The whole thing is wrapped up in its own model so it can be easily called elsewhere in my Rails app:

class SapMaterial < SAP4Rails::Base
  function_module :RFC_READ_TABLE
  class << self

    def find_stock(options={})
      material = options[:material]
      return nil if material.blank?
      rfc = self.RFC_READ_TABLE
      rfc.query_table.value = "LQUA"
      rfc.delimiter.value = "|"
      rfc.options.value = ["MATNR EQ '#{material}'"]
      # optional set of fields to return from the table
      #rfc.fields.value = ['MATNR', ...]
      rfc.call()
      rfc
    end

  end
end

This is called with something like this:

stock_locations = SapMaterial.find_stock(:material=>'VOC300V')

Which yields a handy data set containing all the locations and available quantity for the DigiTech Vocalist 300 in the warehouse. This will be used as part of my new scan gun application which directs the shipping department to the various storage bins for picking large orders.

UPDATE: It seems the preferred approach is to use the new SAP Netweaver RFC library (sapnwrfc.rb). This changes the code sample a bit…

class SapMaterial < SAP4Rails::NW::Base
  function_module :RFC_READ_TABLE
  class << self

    def find_stock(options={})
      material = options[:material]
      return nil if material.blank?
      rfc = self.RFC_READ_TABLE.new_function_call
      rfc.QUERY_TABLE = "LQUA"
      rfc.DELIMITER = "|"
      rfc.OPTIONS = [{'TEXT' => "MATNR EQ '#{material}'"}]
      # optional set of fields to return from the table
      #rfc.FIELDS = [{'FIELDNAME' => 'MATNR'}, {'FIELDNAME' => ...}]
      rfc.invoke
      rfc.DATA
    end

  end
end

The trickiest part was figuring out the correct syntax for the OPTIONS and FIELDS. Pier’s documentation hints at the correct format (look under the heading ‘A Closer Look At Handling Parameters’), but I had to dig in to the table structures within the function in SAP to figure out that it was expecting ‘TEXT’ and ‘FIELDNAME’. So, if you’re using some other function, then explore the table structures to learn what the function expects to see.

Ruby: Howto convert numbers to letters

Let’s say you have a series of numbers (1,2,3,4,5…500+) and you need to convert them to letters like A,B,C,D…AA…ZZ (think Excel column headers.)

I searched and searched to find a built-in way of doing this with Ruby, but I couldn’t find it. So, I tried to write my own:

def number_to_letter(n=1)
  n.to_i.to_s(27).tr("0-9a-q", "A-Z")
end

That works great except that 1=B instead of A, so all of AA,AB,AC…AZ doesn’t work. I tried all sorts of different ideas based on the one above. Then, I stumbled on: succ.

For an Integer, it makes a lot sense: 1.succ = 2, and so on (returns the next integer). But, it also does exactly what I need for strings: “A”.succ = “B” and “Z”.succ = “AA”. So, rather than converting numbers to letters, I ended up with a block like this:

column = "A"
(1..500).to_a.each do |i|
   puts "#{i} : #{column}"
   column = column.succ
end

Teague dancing…NO!

A few years ago, we used to listen to They Might Be Giant’s album called, “NO!” all the time. I captured Teague dancing to it one day. Be sure to watch all the way through.