Ruby Trick Of the Day

November 24th, 2009 by Casey Dreier No comments »

I was browsing through the Rails source code, and came across this nice little snippet. To paraphrase:

["val1","val2","val3"] * '&'
# returns => "val1&val2&val3"

Yes, multiplying an array by a string does the equivalent of an array join() method. So the above is equivalent to:

["val1","val2","val3"].join('&')
# returns => "val1&val2&val3"

Using Google To Host Your Javascript Assets

November 18th, 2009 by Casey Dreier No comments »

Well, not all of them, but some big ones.

I noticed the other day, via Google’s AJAX Libraries page, that they host a variety of popular JS frameworks that you can load directly via the standard <script/> tag. So instead of hosting, say, JQuery, on my own server, I can just reference Google’s copy like this:

<script language="javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.1/jquery.min.js" type="text/javascript"></script>

It’s like having your own basic asset host. Here’s the full list, (hint: includes JQuery, Prototype, the Yahoo UI library, and a few others).

So why would you want to do this? Two reasons:

  1. HTTP can only have two concurrent requests from a single server. That means everything from your site will is downloaded two-at-a-time. If you’re loading a lot of different resources (images, css files, js files, etc), they all have to wait in line to be downloaded. But if you link to your JS library via Google, that’s a new server, and the browser can call that independently from the other resources on your site. This means your page will load faster.
  2. You save bandwidth. Those JS libraries can be reasonably large, but with this method, Google pays for that bandwidth, not you.

Google claims that it will host these files “indefinitely”, but even if they don’t, it’s a reasonably quick action to re-set the reference back to your own server.

The Morning Read

November 16th, 2009 by Casey Dreier No comments »

Metaprogramming in Ruby seems to be today’s topic:

CU Web Forum Talk Up on SlideShare

November 15th, 2009 by Casey Dreier No comments »

For any interested parties, I posted my recent talk where I presented about Ruby on Rails to the Cornell Web Development community. The talk is two parts, the first being a background to Ruby on Rails, and the second covering the challenges and payoffs that came from integrating Rails into our PHP/Oracle environment.

You can see the full thing at:
http://www.slideshare.net/daphonz/moving-to-ruby-on-rails-a-love-story

Oracle ENV Vars in Phusion Passenger

November 11th, 2009 by Casey Dreier 1 comment »

Rails’ oracle adapter needs to see your Oracle environment variables in order to hook up to the database. In an old post, I described how to do that before Phusion Passenger could handle it naturally. As of 2.2.3, Passenger can now pass server ENV variables to the Rails environment directly, via the “PassEnv” command.

So now, in your httpd.conf, you can add the following:

PassEnv LD_LIBRARY_PATH
PassEnv ORACLE_HOME
PassEnv NLS_LANG
PassEnv TNS_ADMIN

That’s it! You no longer need to create a wrapper for the ruby interpreter.

The Morning Read

September 30th, 2009 by Casey Dreier No comments »

Samples of today’s self-education:

Sharing A Workspace With Perforce

September 29th, 2009 by Casey Dreier No comments »

We here at the SHA webteam use Perforce for our version control system. It’s nice in many ways, but it can be a little unwieldy at times. Recently, I wanted to use a second computer at work, but wanted to use the same workspace I’ve been using for the past year. My workspace is stored on a networked drive, which is linked to a development server and so forth.

By default a Perforce workspace is tied to one “host” and one “root”. The host is the hostname of your computer on the network, and the root is the location in the file system where the workspace resides. If you try to access the workspace via another computer, Perforce will prevent you from doing so, declaring that the workspace is inaccessible from any computer that’s not the default host.

Luckily, we can change this. In your client spec in perforce, we need to do two things:

  • Remove the host name from the host section
  • Add an additional file system path to the “AltRoots” section

Removing the original hostname for your workspace effectively removes that requirement from Perforce, and adding the additional file system path tells it how to access the workspace from your other computer.

So say I have a default setup on a PC, and the clientspec contains:

host: SHA-casey
root: J:\perforce\depots\cjd47

In order to make this work with a Mac, I need to know the location of the depot in the Mac’s filesystem. If we’re mounting a SAMBA drive or something, it should be located in the /Volumes directory like so:

/Volumes/FILESTORE/perforce/depots/cjd47

So my modifed clientspec would look something like:

host: 
root: J:\perforce\depots\cjd47
AltRoots: /Volumes/FILESTORE/perforce/depots/cjd47

Everything else in the client spec on the new computer should be the same as the original client name, workspace name, views, etc. When you connect with Perforce, everything should work just fine.

Why You Don’t Need an AfterFind Callback in Rails

June 4th, 2009 by Casey Dreier No comments »

I came to Rails from a background in CakePHP. In the course of my experience with that framework, I really started to embrace the concept of Model callbacks: methods that are run automatically at specifics points during common actions, like beforeSave, beforeRender, afterSave, and afterFind, to name a few.

I used to use the afterFind callback quite a bit in Cake, mainly to standardize the data coming out of the model, or to run occasional queries that I didn’t want to associate directly through the find() method.

So when I moved over to Rails, I started to poke around a bit looking for callback methods. And while I found them, there seemed to be a lack of focus on the after_find callback, as well as a general lack of details or examples online. Plus, it looked like there’s a huge performance hit when you use it.

So why the lack of focus? Surely most people want ways to play with returned data from a find() method call?

And then I realized what is probably obvious to most people: Rails returns model objects, not arrays.

Objects have that nice feature of being able to define arbitrary methods that you can execute at will. This ability limits the need for an after_find method to go through and add or change some data in most cases. This is why it seems to be rarely used.

To really see the difference, let’s look at a slightly contrived example.

Say I have a Product model that includes a field for weight (in pounds). When I display this data in my view, I want to display the English units and the metric units for its weight, but I don’t want to write out the conversion code in the view every time.

Using the afterFind() callback in CakePHP, I’d have to do something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
class Product extends AppModel {
 
  private function add_weight_types($data) {
    if (!empty($data) && is_array($data)) {
      if (isset($data[$this->alias]) && isset($data[$this->alias]['weight']) {
        $data[$this->alias]['weight_in_lbs'] = $data[$this->alias]['weight']. ' lbs';
        $data[$this->alias]['weight_in_kg'] = ($data[$this->alias]['weight'] / 2.2) . ' kg';
      }
   }
   return $data;
  }
 
  function afterFind($data) {
    if (!empty($data)) {
      if (isset($data[$this->alias])) {
        return $this->add_weight_types($data);
      } elseif {
        for ($i=0;$i<count($data);$i++) {
          $data[$i] = $this->add_weight_types($data[$i]);
        }
      }
    }
    return $data;
  }
 
}
?>

Notice I had to take into account the two differing data structures that CakePHP returns: the find(’all’) method that returns an array of model arrays, or the find(’first’) method that returns a single model array. Every find call now adds two additional array items for the weight that I can use in my views:

echo 'Product Weight: '.$data['Product']['weight_in_lbs']. ' ('. $data['Product']['weight_in_kg'] . ')';

Rails, because it returns arrays of model objects, allows us to have a much simpler implementation. Start in the Product Model again:

1
2
3
4
5
6
7
8
9
10
11
class Product < ActiveRecord::Base
 
  def weight_in_kg
    "$.2f kg" % (weight.to_f / 2.2)
  end
 
  def weight_in_lbs
    "$.2f lbs" % weight
  end
 
end

Notice there’s no callback needed for this. The code only executes when it’s called in a view, like this:

Product Weight: <%= @product.weight_in_lbs %> (<%= @product.weight_in_kg %>)

Man, that’s nice. It just feels good, you know?

CakePHP is great in many, many ways, but for someone who learned a lot about programming by using that framework, I’m realizing now how many unconscious thought patterns it instilled in me. Rails is a good extension of that framework concept at a higher level of complexity, and after working with Rails for a while, I began to break out of the Cake thought-pattern (and most likely moved well in the Rails thought-pattern instead).

So I Gave This Talk….

May 8th, 2009 by Casey Dreier No comments »

I was fortunate enough to have the opportunity to give a talk about our recent work with the SHA LaunchPad redesign and Rails project for the regional conference of the Higher Education Web Professionals Organization, which was hosted here at Cornell by the tireless Jason Woodward.

It was a great conference, with some very interesting talks. The slides for mine, which mostly deal with the concept and philosophy behind our LaunchPad site (which is still not live, sorry), are on SlideShare, so you can see (not hear, unfortunately) Building a New LaunchPad From the Ground Up.

Did you know that POST variables are always passed as strings?

May 8th, 2009 by Casey Dreier No comments »

Because I didn’t.  Or at least I didn’t think much about it. And dammit, I should’ve.

As you can probably tell, I spent a long time trying to figure out a very simple problem.  This is one of those posts that is inspired by the (vain) hope that maybe, just maybe, I can spare someone else a few hours of confusion so they can do better things, like go outside and eat ice cream or something.

Here’s the (simplified) situation.  The user can select a variety of widgets via checkboxes in a form.  The information gets passed as widget ids to the controller, which triggers a method that updates the assocation of a user’s widgets:

### WidgetsController ###
 
def updatewidgets
  @user.update_active_widgets(params[:widget_ids]) if request.post?
  redirect "/"
end
 
### User Model ###
has_many :widgets
 
def update_active_widgets(new_widget_ids)
  current_widget_ids = self.widget_ids
 
  # return an array of widget ids that the user no longer wants #
  unbound_widget_ids = current_widget_ids - new_widget_ids
 
  # Delete all unbound widgets... save new widgets....etc...etc...#
 
end

Ok, who can see why this deletes all of the user’s widgets every time we run this script? Hint: I already told you in the title of this post.

Since the params[:widget_ids] is coming from a POST request, every item in the array is going to be a string, even if it was listed as a number. So, if the user selected widget_ids of 1, 2, and 3, the incoming params[:widget_ids] array would look like this:

params[:widget_ids] = ["1","2","3"]

In my method, update_active_widgets, I take a set difference between two arrays to find which of the current widget_ids associated with the user are no longer selected by the user. But the when I pull out the current widget ids using self.widget_ids, I get an array of integers:

self.widget_ids = [1,2,4,6]

So when I take the set difference between an array of strings and an array of integers, Ruby understands them to be totally different, “1″ != 1. So what I was doing was this:

unbound_widget_ids = [1,2,4,6] - ["1","2","3"]
# returns the original set [1,2,4,6]

Thus, I’ve just told Rails that every current widget_id is unbound, and should be deleted. Since Rails is smart enough to automatically convert a number-string to a number in ActiveRecord, there was never a problem adding widgets. My unit tests were even passing fine, because I kept entering my widget_ids as integer arrays.

Obviously, the solution is to convert everything to an integer before doing a set difference, something like:

new_widget_ids = new_widget_ids.collect {|w_id| w_id.to_i}

Now, when I do the set difference, what happens is this:

unbound_widget_ids = [1,2,4,6] - [1,2,3]
# returns the difference set [4,6]

And that fixes it. Widgets with ids 4 and 6 get removed from this user, and we go ahead and add the widget with id 3. Finally.

Regardless, POST values get passed as strings. I should really know things like this by now.