12.07.07

What is it about Ruby that makes everyone so happy?

Posted in Ruby at 1:07 pm by Robert Horvick

As I become more familiar with the Ruby language I have also become more familiar with the global Ruby community (though it will be quite some time before I am familiar to them).

As in all language spaces there are the founders, leaders, thinkers, personalities, corporate players, and random people you’d drink with (if the opportunity presented itself).

Happiness

What’s interesting about this community, though, is the theme of happiness that everyone keeps talking about.

As in other communities people discuss language features, benefits, failures, frameworks, productivity, metrics, performance, blah blah blah. But this whole notion of happiness, as a language feature, is new to me.

I worked on a commercial compiler for several years early in my career and not once did the word “happiness” come up in design discussions or feature lists. “Tolerable”. “Correctness”. “Performant” (one of my favorite made up words). Absolutely.

But not happiness.

I’m trying to let it soak in slowly. People seem to be happy for basically the same reasons. It boils down to the “feel” of the language. The expressiveness of it. The ease at which things happen. Least surprise. Agile principles. BDD/TDD. RSpec. Lots of good stuff.

And, of course, Rails (and all the MVC, ORM, pluggable goodness it brings to the table).

For me, though, it’s none of those things. Those same things exist in other languages (and will in future languages).

Those things are brick construction, crown modeling and custom paint. Solid. Attractive. Welcoming. Stylish. Fun. But there are other well-made and attractive homes. In the future there will be new well made attractive homes.

Why this one? Why Ruby?

Just like when we picked the neighborhood for our home - it’s the local community. The Raleigh-area Ruby Brigade (Raleigh.rb). Sitting in on the meetings and learning from others. Hearing their stories about past, present and future projects. Listening to their excitement around Ruby and Rails. Learning from them and talking about development topics over laptops and burritos.

Associating with people who are smart, passionate, excited, and who get things done is infectious.

Even just for a few hours a month.

It gets me going. Pushes me to work on my own projects. Revitalizes my spirit. Inspires me to try out new things.

Makes me feel like a winner

Seeing talented developers with a flare for design pushes me to improve my design skills. I’ve begun reading about graphic design, typography, print layouts, usability and photo processing. I’ve started to draw again. I’m sleeping less (in a good way), thinking more, and generally more excited about my day-to-day work (C#, not Ruby). I’m reconnecting with that person I used to be before I got trapped in the clutches of a monolithic corporate machine. He is much cooler than I am. At least he had better taste in socks.

None of this is really about Ruby as a language, though. It could just as well be Python or a Linux fetish. I’m sure there are some great local groups for those technologies.

But I’m not in those groups. I’m in Raleigh.rb. Maybe I’ll check them out some other time. Maybe not. I’m still having fun here.

So go now. Seek out a local Ruby Brigade in your community. If you can’t find one then start your own.

Sit in the back. Don’t say a word. Or sit up front and chat the place up. Doesn’t matter. Just find one and go. If you’re in the Raleigh area come by to the local meetup. I’ve found it to be a friendly, open group who enjoy seeing new faces.

Make it a New Year’s resolution.

You’ll be glad you did.

I promise.

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

11.29.07

Use escape_javascript when rendering model data into a js string

Posted in Ruby, rails at 5:00 am by Robert Horvick

To give users a better view into some data I want to render local points of interest with current deals onto a Google map.  In the marker popup I’d like the user to be able to drill down further into the details.  To do this I want a hyperlink to the deal details.

 The link_to I want is:

link_to "Details", { :controller => "deal", :action => "details", :id => deal.id }

link_to rendering link in javascript

 The problem is that the rendered <a> should go into a blob of javascript such as:

       GEvent.addListener(        marker,"click",        function() {    

          var myHtml = "<b><%= h deal.Restaurant.name %></b><br/><%= h deal.description %></br><%= link_to "Details", { :controller => "deal", :action => "details", :id => deal.id } %>";    

          map.openInfoWindowHtml(marker.getLatLng(), myHtml);    

        }    

      );

That fails because the string literal has embedded double quotes.

Since ‘h’ (alias for html_escape) exists I figured there had to be a javascript version as well - and action view did not disappoint.

The updated code is:

      GEvent.addListener(        marker,"click",        function() {    

          var myHtml = "<b><%= escape_javascript h(deal.Restaurant.name) %></b><br/><%= escape_javascript h(deal.description) %></br><%= escape_javascript link_to("Details", { :controller => "deal", :action => "details", :id => deal.id }) %>";    

          map.openInfoWindowHtml(marker.getLatLng(), myHtml);    

        }    

      );

The javascript now renders correctly and the link to the details page functions as expected.

I’m still shooting in the dark - but I think I’m facing the target.

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

11.28.07

Getting RSpec working on Windows with InstantRails

Posted in Ruby, rails, rspec, testing at 6:00 am by Robert Horvick

Since bringing a copy of my current project down from heroku to test some features I decided to give RSpec a try at the same time.

My gut feeling was to run “gem install rspec”.  So, for the record, that is NOT the way to properly install rspec on a rails site.

To use rspec on a rails site you need to install it as a plugin.

The documentation page on installing RSpec indicates that I should do this be running the following commands from my ruby shell:

ruby script/plugin install svn://rubyforge.org/var/svn/rspec/tags/CURRENT/rspecruby script/plugin install svn://rubyforge.org/var/svn/rspec/tags/CURRENT/rspec_on_rails

When I did this nothing happened.  No output whatsoever. 

I tried running the next command on the install guide (output provided):

>ruby script/generate rspec 

Couldn't find 'rspec' generator 

I figured the problem was not having svn installed (let me take a moment here to say that the plugin script could do a better job of indicating this - silent failure smells a lot like success).

I installed downloaded the win32 client binaries from the subversion site http://subversion.tigris.org/downloads/1.4.5-win32/apache-2.2/svn-win32-1.4.5.zip and extracted them to c:\svn  (so c:\svn\bin now contains svn.exe).

In my ruby command shell I added c:\svn\bin to the path (set PATH=%PATH%;c:\svn\bin) and now re-running the plugin installation succeeded (thousands of lines of status scroll by).

Next I ran the following (with output):


C:\InstantRails\rails_apps\kidseatfree>ruby script\generate rspec 

      exists  spec 

  create spec/spec_helper.rb 

      create  spec/spec.opts 

  create previous_failures.txt 

      create  script/spec_server 

      create  script/spec

Finally I ran “rake spec” - which showed I had a missing gem dependency:


C:\InstantRails\rails_apps\kidseatfree>rake spec 

(in C:/InstantRails/rails_apps/kidseatfree) 

C:/InstantRails/rails_apps/kidseatfree/vendor/plugins/rspec/lib/spec/runner/formatter/base_text_formatter.rb:37:in `colour=': You must gem install win32console to use colour on Windows (RuntimeError)

So I ran “gem install win32console” and now rake spec gave the following (I already had an rspec test written so there was something to do):


Errno::ENOENT in 'Deal with fixtures loaded should have one record' 

No such file or directory - C:/InstantRails/rails_apps/kidseatfree/config/../spec/fixtures/deal

So I copied my fixtures from test\fixtures to spec\fixtures and re-ran:


C:\InstantRails\rails_apps\kidseatfree>rake spec 

(in C:/InstantRails/rails_apps/kidseatfree) 

..Finished in 0.166 seconds 

2 examples, 0 failures
[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

11.27.07

Getting an exported heroku project working

Posted in Programming, Ruby, heroku, rails at 6:00 am by Robert Horvick

After exporting a project from heroku to do some local work there were two things I needed to do:

  1.  Create config/database.yml - it is good that this file is not exported.  It contains no details that are useful outside of the heroku environment.  But don’t forget you need it (if you don’t know what to put there - create an empty rails project and grab that one).
  2. create the databases referenced in config/database.yml
  3. Execute “rake db:sessions:create” - this will create a migration that is necessary to enable SQL session storage.
  4. Execute “rake db:migrate” - this will execute all of your migrations as well as the SQL session migration.

After this my project was working.

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

11.26.07

heroku.com + script.aculo.us = broken ajax

Posted in Programming, Ruby, ajax, heroku, rails at 4:55 pm by Robert Horvick

Update: As I expected the guys are heroku turned around a fix very quickly.  I tested autocompletion tonight and it worked perfect.  heroku + script.aculo.us = working like a champ :) 

They say that constructive criticism (e.g. complaints) should occur in a sandwich of positive thoughts.  Generally I think that’s a bunch of PC granola-farming nonsense.  People need to hear what people need to hear.  If I suck - tell me.  If I’m lucky you’ll tell me why.  If I’m really lucky you may offer advice on how to improve.

But knowing that something sucks is the baseline.

But I’m going to break my rule and go with the PC warm-fuzzy approach.

Positive: Heroku kicks ass

Rails site online fast, syntax highlighting editor, backup/restore, nginx, postgres … love it.  I’ve been able to make a lot of progress in this environment (and being able to work on the site from multiple computers is very nice).

Negative: Heroku injects some code into every response

This breaks ajax behaviors by altering partials (and includes prototype.js which breaks script.aculo.us) … in short ajaxy rails is broken.

When I make an ajax request I expect to get back something like:

<ul class="restaurants">

  <li class="restaurant">    <div class="name">rest name</div>

  </li>

  <li class="restaurant">

    <div class="name">O'Charley's</div>

  </li>

  <li class="restaurant">

    <div class="name">Sunday Deal</div>

  </li>

  <li class="restaurant">

    <div class="name">Monday Deal</div>

  </li>

  <li class="restaurant">

    <div class="name">Sunday Cheap</div>

  </li>

</ul>

But what I actually get back is:

<!-- heroku toolbar -->

<script src="http://heroku.com/javascripts/prototype.js” type=”text/javascript”></script>

<script src=”http://heroku.com/javascripts/effects.js” type=”text/javascript”></script>

<script src=”http://heroku.com/toolbar/heroku_toolbar.js” type=”text/javascript”></script>

<link href=”http://heroku.com/toolbar/heroku_toolbar.css” media=”screen” rel=”Stylesheet” type=”text/css” />

<script type=”text/javascript”>HerokuToolbar.html = ‘<div id=”heroku_toolbar”><a href=”http://heroku.com” id=”heroku_logo”></a><a href=”http://edit.kidseatfree.heroku.com/?uri=%2Fdeal%2Fauto_complete_for_restaurant_name” id=”heroku_back_button”></a><a href=”javascript:HerokuToolbar.toggle()” mce_href=”javascript:HerokuToolbar.toggle()” id=”heroku_inspect_button”></a><img id=”heroku_spinner” src=”http://heroku.com/toolbar/images/spinner.gif” style=”display: none”><span id=”heroku_login_info”><a href=”http://heroku.com/myapps”>robert.horvick@gmail.com</a> | <a href=”http://heroku.com/logout”>logout</a></span></div>’</script>

<span class=”heroku_marker heroku_start” style=”display: none”>/mnt/home/userapps/347/app/views/deal/_restaurants.rhtml</span>

<ul class=”restaurants”>

  <li class=”restaurant”>    <div class=”name”>rest name</div>

  </li>

  <li class=”restaurant”>

    <div class=”name”>O’Charley’s</div>

  </li>

  <li class=”restaurant”>

    <div class=”name”>Sunday Deal</div>

  </li>

  <li class=”restaurant”>

    <div class=”name”>Monday Deal</div>

  </li>

  <li class=”restaurant”>

    <div class=”name”>Sunday Cheap</div>

  </li>

</ul>

<span class=”heroku_marker heroku_finish” style=”display: none”></span>

Ah crap.  Now my autocomplete doesn’t render … oh but it gets worse.

Reincluding prototype.js causes the scriptaculous extending of Ajax.Autocompleter to be lost and now autocomplete events don’t fire (in fact FireBug shows an error because Ajax.Autocompleter is not a constructor).

Bug reported.

Positive: The folks at heroku kick ass

I have reported quite a few issues - some bugs, some feature ideas, some random rants about usability or business models.  But in every case these guys have turned around fixes or offered workarounds in a matter of hours. 

The issue I just described - I’m confident that they will be able to work past it quickly or provide some sort of workaround for the specific case of partials.

So there you go.

A feedback sandwich.

Now I’m hungry.

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

11.25.07

A rails form that creates multiple models at once

Posted in MySQL, Programming, Ruby, rails at 6:10 pm by Robert Horvick

I want to create a simple site that allows users to add event, restaurant and location details through an ajax-ish interface.  Ultimately I have a design in mind but to get there I need to be able to submit details of multiple models through a single form (there are about 10 fields over 4 models that will need to be defined).

I wanted to use the action view “form_for”, a single submit and have the submission automatically map the fields to their proper object so that my controller method could look like this (sans error handling):

def create
    if request.post?
@deal = Deal.new(params[:deal])
@deal.Restaurant = Restaurant.new(params[:restaurant])
@deal.Location = Location.new(params[:location])
      @deal.save
    end
  end

Long story short - what you need to do is use the “fields_for” call within the form to get the model information. The form in the view looks like:

<% form_for :deal, :url => { :action => :create } do |form| %>
  <% fields_for :restaurant do |r| %>
Restaurant: <%= r.text_field :name %><br/>
  <% end %>    

  <% fields_for :location do |l| %>
Address 1: <%= l.text_field :address1 %><br/>
Address 2: <%= l.text_field :address2 %><br/>
City: <%= l.text_field :city %><br/>
State: <%= l.text_field :state %><br/>
Zip: <%= l.text_field :zip %><br/>
  <% end %>    

  Details: <%= form.text_area :description, :rows => 3 %>
  <%= submit_tag %>
<% end %>

In this example three models are used - Deal, Restaurant and Location.

The form is based on Deal (since I want the submit to go to Deal::create and I defined two fields_for blocks - one for Restaurant and Location.

The params.inspect output of the form data is:

Parameters: {”restaurant”=>{”name”=>”rest name”}, “deal”=>{”description”=>”Kids can eat whatever they want in under 3 minutes.”}, “commit”=>”Save changes”, “location”=>{”city”=>”some place”, “address1″=>”address one”, “zip”=>”12345″, “address2″=>”", “state”=>”NC”}}

Notice that restaurant, deal and location are all their own hash which can be passed to the new method on the respective models.

Worked like a champ.

Details on the schema is:

    create_table :locations do |t|
      t.column :address1, :string, :limit => 60
      t.column :address2, :string, :limit => 60
      t.column :city, :string, :limit => 40
      t.column :state, :string, :limit => 2
      t.column :zip, :string, :limit => 10
      t.column :phone, :string, :limit => 16
t.column :lng, :string, :limit => 30
      t.column :lat, :string, :limit => 30
      t.column :restaurant_id, :int
    end    

    create_table :restaurants do |t|
      t.column :name, :string, :limit => 60
    end    

    create_table :deals do |t|
      t.column :restaurant_id, :int
      t.column :location_id, :int
      t.column :description, :text
    end

And the models relationships:

class Deal < ActiveRecord::Base
  belongs_to :Restaurant
  belongs_to :Location
...
end    

class Location < ActiveRecord::Base
  belongs_to  :Restaurant
end    

class Restaurant < ActiveRecord::Base
  has_many  :Location
  has_many  :Deal
...
end
[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

11.21.07

Setting data on hidden fields during form submit using AJAX (Google maps geocode example)

Posted in Programming, Ruby, rails at 7:30 pm by Robert Horvick

Using heroku I’m working on a small rails app that needs to integrate with google maps

The Plan 

I want to do is allow a user to provide a location which I will store and later be able to render on a google map. 

Now, to use Google Maps to store location data you want to be using geocodes (i.e. longitude/latitude values).  There are a few reasons for this:

  1. Ultimately adding a marker requires a geocode.
  2. Google limits how many address-to-geocode conversions a site can perform (I’ve seen daily and weekly limits - they were 15,000 and 50,000 respectively [which doesn't add up - but whatever]).
  3. Converting from address to geocode takes a second or longer.  Why make the viewer wait when the information only needs to be gathered once?

So the plan was to do the following:

  1. Accept the data in a standard address form.
  2. Contact the google web service for geocode creation
  3. Cache the geocode with the location record in SQL
  4. Subsequent requests would use the cached record

 Reality Bites 

The problem is that heroku does not allow outgoing connections so I can’t perform #2.  So I need to get the information from the caller without actually making them enter it.  What I came up with was:

  1. Get the data from the user
  2. Perform the geocode lookup in javascript on form submit
  3. Note the geocodes on a pair of hidden form fields
  4. Submit using javascript

Get the data from the user

Let’s get right to it.  This is the form code in the rails view (index.rhtml):

    <%= form_tag( {:controller => “location”, :action => “create”},

                  {’id’ => ‘frmAddress’, ‘method’ => ‘post’, ‘onSubmit’ => ’showAddress(this); return false;’} ) %>

     <p>

        <%= text_field_tag( :address ) %>

        <%= hidden_field_tag( :lat ) %>

        <%= hidden_field_tag( :lng ) %>

        <%= submit_tag(”Go”) %>

      </p>

      <div id=”map_canvas” style=”width: 500px; height: 300px”></div>

    <%= end_form_tag %>

Time for a little breakdown:

    <%= form_tag( {:controller => “location”, :action => “create”},
                  {’id’ => ‘frmAddress’, ‘method’ => ‘post’, ‘onSubmit’ => ’showAddress(this); return false;’} ) %>
 

We are submitting the form to the location controller’s “create” action.  We will submit using the post method.  The ‘id’ isn’t needed in this example - ignore it.  When the submit button is clicked we will call the showAddress javascript function and pass the form as the parameter.  So what’s with return false?

OMG - this is so important.  I can NOT stress this enough.

When submitting a form via javascript you must (MUST) have this return false.  If you don’t the form will submit twice - once via the javascript submit and once via the normal form post.

Say it with me - if javascript is submitting the form I will return false.  I will return false!!!

By the way - Agile Web Development With Rails had this information on 537 (form_remote_tag and remote_form_for documentation).       

        <%= hidden_field_tag( :lat ) %>
        <%= hidden_field_tag( :lng ) %>

These are the hidden fields for the longitude and latitude.

Everything else is plain-jane form stuff.

Lookup, record, submit

function showAddress(frm) {   
      if (geocoder) {
        geocoder.getLatLng(
          frm.address.value,
          function(point) {
            if (!point) {
              alert(address + ” not found”);
            } else {
              frm.lat.value = point.lat();
              frm.lng.value = point.lng();
              frm.submit();
            }
          }
        );
      }
 

This is mostly the stock google sample code except that I am storing away the latitude and longitude in the hidden form fields and I changed the function to take the form instead of the address value.

Also I submitted the form.

Now in the controller’s create method I can access the parameters like:

  def create
    @lat = params[:lat]
    @lng = params[:lng]
    @address = params[:address]
  end
 

And display them in the view like:

  Long: <%= @lng %><br/>
  Lat: <%= @lat %><br/>
  Address: <%= @address %><br/>
 

So it’s not the server-side solution I had hoped for but it gave me a chance to try something AJAX-ish and to make use of a more complex form submission approach.

Alright … now what have I done wrong?

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

11.17.07

Merb on Windows: It works!!!

Posted in Programming, Ruby, merb at 12:09 am by Robert Horvick

UPDATE: Installing Merb on Windows is now a lot easier!  Check it out here

This post outlines the exact steps I took to get merb working on Windows (to be specific - Vista Home Premium)

  1. Install InstantRails (I have no reason to believe this won’t work with the one-click installer).
  2. Start InstantRails and open a Ruby Console window (the steps following take place in that window)
  3. Execute “gem install hoe”  (RubyInline depends on hoe)
  4. Download http://web.mit.edu/~agp/www/parsetree-win32/ParseTree-2.0.2-mswin32.gem
  5. Download http://web.mit.edu/~agp/www/parsetree-win32/RubyInline-3.6.4.gem
  6. Execute “gem install RubyInline-3.6.4.gem”
  7. Execute “gem install ParseTree-2.0.2.mswin32.gem”
  8. Execute “gem install ruby2ruby”
  9. Execute “gem install json”  (choose json 1.1.1 (mswin32))
  10. Execute “gem install merb -y”
  11. Execute “merb myapp”
  12. Execute “cd myapp”
  13. Execute “set INLINEDIR=c:/temp”  (the slash direction matters!)
  14. Execute “merb”
  15. Browse to http://localhost:4000 and …

Merb Splash Screen

So a big thanks to Ezra for providing the pointers I needed.

Hopefully these steps will prove useful for others.

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

11.16.07

Merb on Windows: PEBKAC! (dependency and user error)

Posted in Programming, Ruby, merb at 11:56 pm by Robert Horvick

After my last merb post Ezra left a comment with some concrete suggestions to move forward with getting merb working on Windows.  My next post will cover that exact topic but I wanted to create a post to captute the stack traces of the errors I hit when trying to get merb running.

The first was a dependency issue.  Merb requires json but I did not have it installed.  I think the merb gem should have noted this dependency but honestly I don’t understand gems enough to know for sure.  The output of the merb command and call stack is:

C:\InstantRails\ruby\merb>merb
Merb started with these options:

:query_string_whitelist: []

:reloader: true
:environment: development
:merb_root: C:/InstantRails/ruby/merb
:exception_details: true
:cache_templates: false
:reloader_time: 0.5
:host: 0.0.0.0
:use_mutex: true
:port: “4000″
:session_id_cookie_only: true

Using pure ruby JSON lib
Using pure ruby JSON lib
C:/InstantRails/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require’: no such file to load — json/pure (LoadError)
        from C:/InstantRails/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require’
        from C:/InstantRails/ruby/lib/ruby/gems/1.8/gems/merb-0.4.1/lib/merb.rb:28
        from C:/InstantRails/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:32:in `gem_original_require’
        from C:/InstantRails/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:32:in `require’
        from C:/InstantRails/ruby/lib/ruby/gems/1.8/gems/merb-0.4.1/lib/merb/server.rb:236:in `initialize_merb’
        from C:/InstantRails/ruby/lib/ruby/gems/1.8/gems/merb-0.4.1/lib/merb/server.rb:573:in `mongrel_start’
        from C:/InstantRails/ruby/lib/ruby/gems/1.8/gems/merb-0.4.1/lib/merb/server.rb:509:in `run’
        from C:/InstantRails/ruby/lib/ruby/gems/1.8/gems/merb-0.4.1/bin/merb:6
        from C:/InstantRails/ruby/bin/merb:16:in `load’
        from C:/InstantRails/ruby/bin/merb:16

I fixed this by installing json 1.1.1 (mswin32).

C:\InstantRails\ruby\merb>gem install json
Select which gem to install for your platform (i386-mswin32)
 1. json 1.1.1 (mswin32)
 2. json 1.1.1 (ruby)
 3. json 1.1.0 (mswin32)
 4. json 1.1.0 (ruby)
 5. Skip this gem
 6. Cancel installation
> 1
Successfully installed json-1.1.1-mswin32
Installing ri documentation for json-1.1.1-mswin32…

No definition for cState_configure

No definition for cState_configure
Installing RDoc documentation for json-1.1.1-mswin32…

No definition for cState_configure

No definition for cState_configure

With the dependencies resolved I re-ran merb but hit another error message.  I’ll give you the output and call stack first and see if you see the problem:

C:\InstantRails\ruby\merb>merb
Merb started with these options:

:query_string_whitelist: []

:reloader: true
:environment: development
:merb_root: C:/InstantRails/ruby/merb
:exception_details: true
:cache_templates: false
:reloader_time: 0.5
:host: 0.0.0.0
:use_mutex: true
:port: “4000″
:session_id_cookie_only: true

C:/InstantRails/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require’: no such file to load — C:/InstantRails/ruby/merb/config/merb_init.rb (LoadError)
        from C:/InstantRails/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require’
        from C:/InstantRails/ruby/lib/ruby/gems/1.8/gems/merb-0.4.1/lib/merb/server.rb:250:in `initialize_merb’
        from C:/InstantRails/ruby/lib/ruby/gems/1.8/gems/merb-0.4.1/lib/merb/server.rb:573:in `mongrel_start’
        from C:/InstantRails/ruby/lib/ruby/gems/1.8/gems/merb-0.4.1/lib/merb/server.rb:509:in `run’
        from C:/InstantRails/ruby/lib/ruby/gems/1.8/gems/merb-0.4.1/bin/merb:6
        from C:/InstantRails/ruby/bin/merb:16:in `load’
        from C:/InstantRails/ruby/bin/merb:16

See it yet?

Require failed to find the file C:/InstantRails/ruby/merb/config/merb_init.rb.  But why?

Because I’m an idiot :).  I ran the merb command from the wrong directory.  I needed to move to the application directory at which point I re-ran merb and …

Wait for the next post!

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

11.14.07

ActiveRecord - How do those dynamic finders and attributes work?

Posted in Programming, Ruby at 8:32 pm by Robert Horvick

In my effort to understand more about ActiveRecord, and Ruby in general, I have been digging through AR line by line.

One of my first goals was to figure out how the dynamic attributes and find_* methods worked. I mean … you call a method and it’s not there and then somehow it gets created.

In the .NET world I would expect something like:

try {
    object.Invoke("Method", args);
} catch(MissingMethodException) {
    // Use the CodeDOM or Emit to generate some code and defer to the created object
}

Was that happening here? Turns out it’s not far off.

I started by creating a case I knew would fail:

class MissingMethod
end

mm = MissingMethod.new
mm.does_not_exist("argument")

# Exception: undefined method `does_not_exist' for #<MissingMethod:0x32171c4>

So how to capture that? Well - I figured ActiveRecord::Base needed to do that so I started there and found “method_missing”. Awesome :)

So I add a handler and now I’m looking like:

class MissingMethod
  def method_missing(method_id, *arguments)
    puts method_id
  end
end

mm = MissingMethod.new
mm.does_not_exist("argument")

# does_not_exist

Sweet.

Now I want to get that whole dynamic goodness thing to happen. So I set out to create a class that would take any missing method that starts with “call_*” and extract the “*” portion, see if that method exists and call it with the original arguments if it does.

So obj.call_real_method(arg) would in turn call obj.real_method(arg).

So now I’m left with a few tasks:

  1. split off the real method name
  2. figure out if it exists
  3. call it

The splitting was easy to figure out:

if match = /^call_([_a-zA-Z]\w*)$/.match(method_id.to_s)
  method = match.captures[0]

Figuring out if it existed took a little more research. I dug into the method_missing handler in ActiveRecord and fairly quickly ran into respond_to? which, after checking some docs, looked perfect.

if(respond_to?(method))

Finally I needed to pass along the call. I did not see an obvious invoke method so I checked out places in ActiveRecord that called respond_to? and noticed the pattern of calling __send__ shortly after. This looked promising. A few doc checks later and my sample was done.

class MissingMethod
  def method_missing(method_id, *arguments)
     if match = /^call_([_a-zA-Z]\w*)$/.match(method_id.to_s)
       method = match.captures[0]
       if(respond_to?(method))
         __send__ method, arguments
       else
         super
       end
     else
       super
     end
  end

  def display(arg)
    puts arg
  end
end

mm = MissingMethod.new
mm.call_display(”Hello World”)

When run prints “Hello World”

As for ActiveRecord - it is doing more than that, obviously. It is not just deferring to existing methods but rather also generating them, adding them to a hash and then calling the generated method from the hash. But this is that point of diminishing interest. I can see the goal line from here so it’s time to move on to the next topic before I get bogged down in the details instead of getting my head around the big picture.

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

« Previous entries · Next entries »