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.

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.

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

Things I commonly do when starting a Rails project

Here’s another post from me for me. When starting a new Ruby on Rails project, I tend to do several things the same each time.

1. Setup SVN repository. Most of my projects are completely unrelated, so I usually have to setup a separate repository. The best walkthrough I’ve found is here.

2. Create development/test databases in mysql:

create database project_development;
create database project_test;

3. Setup ActiveRecord sessions:

rake db:sessions:create
rake db:migrate

(uncomment line in config/environment.rb)

config.action_controller.session_store = :active_record_store

4. Setup initial controller…something like:

script/generate controller Main index

And make it the default route (edit the file config/routes.rb)

# Install the default route as the lowest priority.
map.connect ':controller/:action/:id', :controller => 'main'

(Don’t forget to delete or rename public/index.html)

5. I almost always need user accounts. The ActsAsAuthenticated plugin works well:

script/plugin install http://svn.techno-weenie.net/projects/plugins/acts_as_authenticated
script/generate authenticated user account
rake db:migrate

Look in the newly created app/controller/account_controller.rb for a few things to move to app/controller/application.rb

6. While editing app/controller/application.rb, here are a bunch of date/time formatting shortcuts I use a lot:
ActiveSupport::CoreExtensions::Time::Conversions::DATE_FORMATS.merge!(
:date_time12 => "%m/%d/%Y %I:%M%p",
:regular => "%b %d %I:%M %p",
:date_time24 => "%m/%d/%Y %H:%M",
:pretty_date => "%B %d, %Y",
:month_year => "%B %Y",
:short_month_year => "%b %Y",
:month_day => "%b %d",
:pretty_date_time => "%B %d, %Y %I:%M %p",
:short_date => "%b %d, %Y",
:short_12 => "%b %d %I:%M %p",
:standard => "%m/%d/%Y"
)
ActiveSupport::CoreExtensions::Date::Conversions::DATE_FORMATS.merge!(
:pretty_date => "%B %d, %Y",
:month_year => "%B %Y",
:short_month_year => "%b %Y",
:year_only => "%Y",
:month_day => "%b %d",
:short_date => "%b %d, %Y",
:standard => "%m/%d/%Y"
)

Just add all of that to the bottom of application.rb, then use like this: object.created_at.to_s(:standard)

7. Other essential plugins include:

CalendarDateSelect

script/plugin install http://calendardateselect.googlecode.com/svn/tags/calendar_date_select

FileColumn

script/plugin install http://opensvn.csie.org/rails_file_column/plugins/file_column/trunk
mv vendor/plugins/trunk vendor/plugins/filecolumn

ValidatesEmailVeracityOf

script/plugin install http://svn.savvica.com/public/plugins/validates_email_veracity_of

DynamicSessionExp

script/plugin install http://svn.codahale.com/dynamic_session_exp/trunk
mv vendor/plugins/trunk vendor/plugins/dynamic_session_exp

8. Create a default layout for all controllers: app/views/layouts/application.rhtml (If you have a controller needing its own layout, just create one called, “controller.rhtml”) Here is a basic application.rhtml:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title><%= @page_title || 'Site Name' %></title>
<%= stylesheet_link_tag 'all', :media=>'all' %>
<%= stylesheet_link_tag 'print', :media=>'print' %>
<%= javascript_include_tag :defaults %>
<%= calendar_date_select_includes %>
<!--[if lt IE 7]>
<script defer src="/javascripts/pngfix.js" type="text/javascript"></script>
<![endif]-->
</head>
<body>
<% if flash[:notice] -%>
<div class="flash_notice" id="Flash"><%= flash[:notice] %></div>
<% end -%>
<% if flash[:alert] -%>
<div class="flash_alert" id="Flash"><%= flash[:alert] %></div>
<% end -%>
<%= yield %>
</body>
</html>

9. Default public stuff (pngfix.js, latest scriptaculous, stylesheets, images, etc.)

Hostmonster on Rails

A client of mine recently moved their web hosting to Hostmonster, and I was tasked with migrating a Ruby on Rails application to the new server. I ran into a few snags.

First of all, I followed their tutorial.

The default rails welcome page worked fine, but I kept getting “Application Error” on everything else. The logs showed nothing. I found several message board posts pointing to the permissions on the “public” folder.  Dr. Chuck had the best article on the subject.

Dr. Chuck’s instructions fixed the problem for a month or two.  Then it stopped working again. I figured some server-side process had changed the permissions on my files. So, I set it up again. It still didn’t work.

I actually had to contact tech support. A couple of days later they responded with the solution (thankfully, this isn’t a mission-critical app). Hostmonster had recently updated to a newer version of Rails. So, I just commented out the version in the rails environment file:

#config/environment.rb
#...
#RAILS_GEM_VERSION = '1.2.5' unless defined? RAILS_GEM_VERSION

I believe that tells the application to use whatever version of rails it can find (the most recent version on the server). Since version 1.2.5 had apparently been replaced with 1.2.6, removing the declaration for 1.2.5 fixed the problem.

The part that still confuses me is, I had tried generating the application from scratch while logged into the Hostmonster server (via ssh). Doing so still generated an environment.rb file for version 1.2.5.

Of course, the other part that confuses me is why did 1.2.5 stop working?  When I update my own servers with ‘gem update’, I keep old versions available for backwards compatibility of existing rails apps. As far as I know, there is no problem doing it this way. Am I wrong?

Using RubyODBC in Rails on Linux to connect to MS SQL Server

(I really need to work on shortening my titles.)

I spent the day trying to figure out one little problem. I setup a brand new server for the Intranet at work. I set it up with Fedora 7 and started configuring it for my rails apps. Since our corporation has decided to use Microsoft SQL for all databases, I turned to the instructions on the Rails wiki to setup ODBC.

Most of it went pretty smoothly. When I was done, I launched mongrel and checked the site using http://ip_address:3000. It all worked great. So, then I setup mongrel_cluster and fired it up. No workey. I kept getting database errors saying “No data found.”

The very misleading error was hard to solve. I finally found a post which explains that there is a bug in RubyODBC which does not play well with “text” columns that are blank. So, the answer is to set all your blank fields to NULL. Well, I couldn’t figure out how to do that. (MSSQL is not my favorite.) I finally figured out to use a whole bunch of these queries in SQL query analyzer:

UPDATE tablename SET description=" " WHERE description LIKE ""

(There’s a space between the first set of quotes, but not the second.)

So, that fixed everything for now, but I also had to make sure it doesn’t happen again. I couldn’t get the default value in SQL to do what I needed, so I added a bunch of these in the model code for each problematic model:

def before_save
if self.description.blank?
self.description = ' ' #(two spaces)
end
end

Hopefully this will help somebody else.

Using the “dry_scaffold” and “dhtml_calendar” plugins with Rails 1.2.3

I spent the morning trying to get a lame little site up and running. I didn’t know it would be an issue until I couldn’t turn back. Information was sketchy, so here’s my addition to the sketchy info I had found.

Keep in mind, these instructions are only designed to help if you already had these plugins working with an older version of Rails.

The dry_scaffold plugin doesn’t seem to be supported anymore. The author seems to prefer “active_scaffold”. Nice–unless you don’t want to rewrite all your code that had previously been designed for “dry_scaffold”. Also, the older version of “Engines” I had doesn’t work with Rails 1.2.3. So, here’s the skinny:

1. upgrade Engines:

ruby script/plugin install http://svn.rails-engines.org/plugins/engines --force

(if that fails, export the svn and copy it to your vendor/plugins directory)

2. read the “UPGRADING” file included in the Engines plugin. A lot has changed. To get the two plugins I needed, I had to modify several files.

environment.rb

just before the section with your configuration for dry_scaffold, add this:

require File.join(RAILS_ROOT, "vendor", "plugins", "engines",
                    "lib", "engines", "deprecated_config_support")

also, you’ll want to get rid of your “Engines.start” statement(s).

Do the following for both plugins…Copy the init_engine.rb file to init.rb within the individual plugin’s folder.

Then, edit init.rb:

Comment out the line about “Engines.current.version = …” I’m not sure why, but that breaks stuff and everything works fine without it.

Finally, in your layouts in app/views/layouts, you’ll need to edit the stylesheet and javascript include tags. Here’s the new format:

<%= stylesheet_link_tag "dry_scaffold", :plugin=>"dry_scaffold" %>
<%= javascript_include_tag "dry_scaffold", "rico_corner", :plugin=>"dry_scaffold" %>

<%= stylesheet_link_tag "dhtml_calendar","calendar-blue", :plugin=>"dhtml_calendar" %>
<%= stylesheet_include_tag "dhtml_calendar","datebocks_engine","calendar","lang/calendar-en","calendar-setup", :plugin=>"dhtml_calendar" %>

I think that will do it. Hopefully this will help someone…and not be more confusing than the info out there now.