posts by tag

golftrac (9)
Golf Trac screen shots

Unfortunately, it has been a while since I’ve worked on Golf Trac. The end of grad school got a little crazy, and with the holidays and everything, it has been on the back-burner (I hope to have it finished by spring, though).

I’ve been keeping track of the progress by posting screen shots on Flickr, but always kept the images private. I for one enjoy looking at designs “in the making”, so I feel somewhat obligated to share. Prepare yourself… I’m unveiling to the world: Golf Trac screen shots! It’s pretty basic, but I think that’s a good thing. If nothing else, you can at least get a feel for the interface. Enjoy.

Refactored block helper

Sometimes when I post about something, I think more about what I’m doing, which in turn helps me to realize a potentially better solution. For instance, before I had i<2 ? true : false which is the same as i < 2—what was I thinking??? Anyway, the problem now is I don’t know if I’m taking this simple thing too far. The block helper I was using is:

def handicaps_and_pars(&block)
  titles = ["Men's Handicap", "Men's Par", "Ladies' Handicap", "Ladies' Par"]
  4.times do |i|
    yield titles[i], i % 2 == 0 ? 'handicap' : 'hole', i < 2 ? true : false
  end
end

I pulled out the parts that made my brain work a little harder into a separate method that returns the yielded data as an array:

def data_for_index(index)
  titles = ["Men's Handicap", "Men's Par", "Ladies' Handicap", "Ladies' Par"]
  [titles[index], index % 2 == 0 ? 'handicap' : 'hole', index < 2]
end

And from that I was between two options…

# option 1 - separate data into meaningful variables
def handicaps_and_pars(&block)
  4.times do |i|
    title, partial, is_mens = data_for_index(i)
    yield title, partial, is_mens
  end
end

# option 2 - yield the method itself
def handicaps_and_pars(&block)
  4.times { |i| yield data_for_index(i) }
end

I decided to go with option 2, as I think it’s the more concise of the two. But maybe I took this already short method too far? Sometimes I don’t know when to stop with this stuff, but it’s still fun to explore the options.

Nothing like a good block helper

I took a few months leave of absence from Golf Trac. Well, the other evening I decided to go through the entire application and clean out the garbage (I had a feeling there’d be some). I forgot how surprisingly complex it was to model a golf scorecard with a normalized database and a single web form. But that’s not the point. The point is I love blocks in Ruby, particularly block helpers.

One of the things I came across was this _handicaps_and_pars.rhtml partial:

<tbody id="holes-<%= side %>">    
  <tr>
    <td>Holes</td>
    <%= hole_count_for(side) -%>
  </tr>
  <tr>
    <td>Men's Handicap</td>
    <%= render :partial => 'handicap', :collection => @course.holes, :locals => { :mens => true, :side => side } -%>
  </tr>
  <tr>
    <td>Men's Par</td>
    <%= render :partial => 'hole', :collection => @course.holes, :locals => { :mens => true, :side => side } -%>
  </tr>
  <tr>
    <td>Ladie's Handicap</td>
    <%= render :partial => 'handicap', :collection => @course.holes, :locals => { :mens => false, :side => side } -%>
  </tr>
  <tr>
    <td>Ladie's Par</td>
    <%= render :partial => 'hole', :collection => @course.holes, :locals => { :mens => false, :side => side } -%>
  </tr>
</tbody>

It’s actually not that bad because it reveals what’s going on quite nicely. But there are just too many commonalities to leave it alone (the tr’s, td’s, :collection, :side, etc), so I added this little helper to assist with the problem:

def handicaps_and_pars(&block)
  titles = ["Men's Handicap", "Men's Par", "Ladies' Handicap", "Ladies' Par"]
  4.times do |i|
    yield titles[i], i % 2 == 0 ? 'handicap' : 'hole', i < 2 ? true : false
  end
end

And now the partial looks a little cleaner:

<tbody id="holes-<%= side %>">    
  <tr>
    <td>Holes</td>
      <%= hole_count_for(side) -%>
  </tr>
  <% handicaps_and_pars do |title, partial, is_mens| %>
    <tr>
      <td><%= title -%></td>
      <%= render :partial => partial, :collection => @course.holes, :locals => { :mens => is_mens, :side => side } -%>
    </tr>
  <% end -%>
</tbody>

I could probably make the helper a bit more readable…

# inside the 4.times block
title, partial, is_mens = titles[i], i % 2 == 0 ? 'handicap' : 'hole', i < 2 ? true : false
yield title, partial, is_mens

...but I doubt that I will, considering I’m the only one working on the code.

I suppose another thing that would make the view more appealing would be to use Markaby (or something similar)... those <%= -%> tags are beginning to annoy me.

Tabbed navigation tip for Rails

UPDATE see the refactored version here

A very common pattern in a lot of web applications is a tabbed menu with the current tab highlighted. Last night I spent some time refactoring the navigation in Golf Trac, and I thought I would post what I did, just in case someone is new to this and goes overkill on the implementation.

First of all, Rails in its entirety thrives off of conventions. It’s inspirational to say the least. I’ve embraced a conventional way of thinking in my tabbed menu implementation, so let’s get down to business.

Tabbed navigation for actions only

For this example, I’ll use a public_controller, which contains a few actions that aren’t restricted behind a login. Just to get you excited, here’s what the end result will be:

navigation ['login','register','about','tour']

You might immediately think that each link passed in is the title of an action and the route is generated by appending _path on the end. Actually, that’s pretty close. The problem I had with that quick-and-dirty implementation was the ambiguity of the named routes. For example, in Golf Trac a user can create a New course and a New round. Well, that means I have two “new” things. I couldn’t generate a named route such as new_path because it’s ambiguous.

To alleviate this problem, I’m using controller.controller_name to make the routes unique. I’ll just give the implementation (I made a pastie, as well) and mention a thing or two about it afterward.

def navigation(links)
  returning html = "<ul>" do
    links.each do |link|
      html << "<li class='#{css_for(link)}'>#{build_link_for(link)}</li>"
    end
    html << "</ul>"
  end
end

You could use content_tag instead of explicitly writing out the <ul> and <li> stuff, but sometimes I find this easier on the eyes. The css_for(link) method just pulls out a lengthy if condition (better readability) to determine if it’s the selected tab, but for completeness, here are the details:

def css_for(link)
  controller.action_name.downcase == link.downcase ? 'current' : 'plain'
end

The build_link_for(link) method simply generates the link with the appropriate named route dynamically, but again, here are the details:

def build_link_for(link)
  link_to link.capitalize, send("#{link.downcase}_#{controller.controller_name.downcase}_path")
end

Now, that’s how to select the current tab for a list of actions within a controller, but what if you wanted to select the current controller, too (i.e. nested tabs)? It’s simple. All you have to do is check if the controller.controller_name.downcase instead.

Modified for controllers and actions

In Golf Trac, I have a sidebar which has a menu that highlights the current controller, then in the corresponding content area, a set of tabs for each major action within that controller, which also get highlighted upon selection. So my navigation helper is more complex, but not by much:

def navigation(links, from_layout = false)
  returning html = "<ul>" do
    links.each do |link|
      html << "<li class='#{css_for(link, from_layout)}'>#{build_link_for(link, from_layout)}</li>"
    end
    html << "</ul>"
  end
end

Also, you’d have to modify the css_for(link) method to accept the from_layout parameter so it would know if it was supposed to check the controller or action name. Here’s how I’m currently doing that:

def css_for(link, from_layout)
  controller.send("#{from_layout ? 'controller_name' : 'action_name'}").downcase == link.downcase ? 'current' : 'plain'
end

It’s basically the same thing, only I’m determining if I need controller_name or action_name based on the from_layout parameter. And the build_link_for(link) method needs updated, too:

def build_link_for(link, from_layout)
  controller_path, action_path = "#{link.downcase}_path", "#{link.downcase}_#{controller.controller_name.downcase}_path"
  link_to link.capitalize, send("#{from_layout ? controller_path : action_path}")
end

It’s the same deal here. If it’s an action, then I need the route that has the controller name appended to it. The nice thing is the more frequent situation (view templates) would not require a true or false parameter (since it’s defaulted to false), so it keeps the API nice and clean. And you’d only have to add it in your layout once, like so:

# views/layouts/[whatever].rhtml
navigation ['login','register','about','tour'], true

Here are the routes that would work with the above example:

# config/routes.rb
map.with_options :controller => 'public' do |path|
  path.login_public '/login',       :action => 'login'
  path.register_public '/register', :action => 'register'
  path.about_public '/about',       :action => 'about'
  path.tour_public '/tour',         :action => 'tour'
end

TODO: determine a way to not require an extra parameter (from_layout), but have the helper know where it’s being called from and act accordingly.

Conclusion

Currently, I’m using this implementation to navigate around five controllers (in the sidebar) and 13 or so total actions (in the content areas). I personally like passing the text that I want to display as tabs, but that’s just one of the hundred ways to dynamically construct a tabbed menu in Rails. Whether or not you do something like I’ve shown above, I strongly recommend you setup some sort of convention to base it on. Be smart about the design and you can get so many things for free.

Golf Trac was flagged tonight

Literally. The past couple of nights I’ve had some time to work on Golf Trac. I spent most of the time coding, but I did stop to visit a few spots that were begging for attention. One of these spots was the dull header image. I shrunk the green a bit and added a flag. It doesn’t have anything to do with “tracking” a golf game, but I believe it was a needed adjustment. I did consider something corny, like making the flag stick the “L” in GOLF, but it just didn’t look right. Maybe that’s because I can’t make a nice flag stick in Photoshop. At any rate, here’s what I came up with…

How to use method_missing

There’s no doubt, Ruby can be tricky at times. But understanding it well (and not that I do) can greatly increase productivity, efficiency, and happiness. Seriously.

Would you believe me if I said I could randomly call Model.ryan_heath and dynamically teach Ruby that ryan_heath is a real method under the Model class? No, Ruby doesn’t come pre-packaged with a method that just-so-happens to be my name. There’d be no trick in that. Rather, this works because of method_missing. If you’ve never heard of method_missing, it’s simply a method that gets invoked after Ruby searches the entire class hierarchy for a method that doesn’t exist (Model.ryan_heath would result in Object.method_missing). So what, right? Wrong. Since methods can be easily overridden in Ruby, you can use this little hook to really help you write (or not write) code.

As an example (albeit, a shallow one) to show how flat out awesome this technique is, I’m going to use a snippet from Golf Trac (which is taking me forever to finish, by the way). So, in my golf application I have the obvious association that “a course has many holes”. Since I’ll be referencing those holes a good bit throughout the application, it would be nice to have a convenient way to access them. I’ll rarely (if at all) be asking for hole information outside the context of a course, and so it’d be best to define a method directly on the course-to-holes association.

While this can simply be any method I’d like, I chose to override method_missing (you’ll see my original intention in a minute). Here’s the top of my Course model:

class Course < ActiveRecord::Base
  has_many :rounds, :include => :scores
  has_many :holes do
    def method_missing(method)
      find(
        :first, 
        :conditions => { :hole_number => method.to_s.send(:gsub, /[a-z|A-Z|_]/, "").to_i }
      )
    end
  end
  ...
end

Here, find will always be scoped to a specific course, so I only need to worry about getting the appropriate hole. Now, to access the holes:

@course.holes.hole_10
# => tenth hole
@course.holes.hole_18
# => eighteenth hole

The best part is, the hole_10 and hole_18 methods don’t really exist! How cool is that?

Also, looping through all of the holes using the above implementation may seem to make sense, but it doesn’t. If I were to use the above jargon, I’d be doing a separate query for each hole in the loop. In that case, I’d probably rather eager load the holes instead. However, if I didn’t care to run 18 separate queries, it could be done like so:

(1..18).each do |i|
  puts "Par for the #{i.ordinalize} hole is #{@course.holes.send("hole_#{i}".to_sym).par}."
end

## results
 # => "Par for the 1st hole is 4."
 # => "Par for the 2nd hole is 3."
 # => ...

I’ve essentially told Ruby that there are 18 methods (hole_1, hole_2, ...hole_18) defined on the course-to-holes association, when really there’s nothing more than a single method_missing hook. That’s just awesome to me.

And now it’s time for my original intention. Originally, I had planned on using the hole number itself as the method, which would have been easier and much cleaner. But I can’t seem to get it to work. I’m currently using course.holes._10 instead of the more redundant, course.holes.hole_10, but what I really want is just course.holes.10.

def method_missing(method)
  find(:first, :conditions => { :hole_number => method.to_i })
end

@course.holes.18
# => eighteenth hole
@course.holes.11
# => eleventh hole

But, and correct me if I’m wrong, it’s apparent that Ruby doesn’t quite understand integers as method calls. Oh well, Ruby does so many things that amaze me, it was worth a shot. If anyone has a thought or two as to how I can get course.holes.18 to return the 18th hole, I’d love to hear them.

I still have a lot, a lot, to learn about metaprogramming, but it definitely feels good to have your code do extra work for you. If you have a clever method_missing example, feel free to leave it in the comments.

Golf Trac will support OpenID

The other night, I added OpenID support for Golf Trac. I’ve been using it (OpenID) more and more lately and I’m starting to really like it. It’s so easy. Since not everyone uses it (yet), I’ve implemented username/password registration, as well. Going from username/password to OpenID, or vice versa, is no problem. The accounts can be easily merged as long as the email that is registered is the same email associated with the OpenID account.

Technically speaking, managing both login types for the same account didn’t go quite as smoothly as planned. I didn’t want to create a second username/password registration process just because a user had an OpenID, so it was somewhat involved to come up with a work-around. I had a few issues bypassing certain validations, but all is well now. And besides, it’s only a little awkward in the rare case (going from OpenID to username/password). The typical case (going from username/password to OpenID) is completely seamless. If a user already has a username/password, signing in with OpenID will then tell Golf Trac to merge with the existing account.

If you think managing multiple logins is a pain, you really should look into OpenID.

Personal Projects

Golf Trac is something I can’t wait to use. Originally, using it was all my motivation. Now, I’m remembering how fun it is to work on personal stuff, and that boosts my motivation to finish it even more—it’s a win-win. I have to say, though, I wouldn’t enjoy it nearly as much without Ruby/Rails. I’m still figuring out more and more, which is awesome. It’s that amazed, “Ah ha!” moment that constantly adds to the experience (for me, anyway).

Personal projects in general are fun to work on (especially when it’s not rpheath.com again). Nobody can tell me what to do or when to do it. If I want to spend a week on nothing but adjusting pixels, I can. There’s no worrying about this decision or that decision, I only have to please myself. And, believe it or not, sometimes I’m not in the mood to program. It’s nice to be able to set it aside for when I am in the mood.

I think Google employees get 20% of their work-week to work on personal projects. That’s what I need… one day per week to work on my own stuff, and get paid for it. Of course, a full week would be much better (lucky bums)1.

1 Yes, I realize it took hard work to get there. And they’re not really bums.

Introducing Golf Trac (almost)

I enjoy monitoring things over time (Google Reader trends, my investments, spending habits, etc). For me, golf adds one more thing to the list. However, there’s no real easy way to monitor my golf game over time. Especially since it has potential to span across years and years, adding up to hundreds (if not thousands) of rounds. To make things easier for me, I’ve decided to build an application I’ll call Golf Trac (excuse the cheap name, but it wasn’t that important to me).

Currently, it’s only in a sign-up state. I had originally planned on doing it for myself, but I know of at least a couple of people who would like to use it, which was reason enough to build it for others, as well. I don’t plan on having a “social network” around it, as the original reasoning is for those who want to track his or her own game. Of course, if I see some sort of need or reason to add social context, I may, but it’s better for me to start off with the basics. However, I have considered a mobile interface (to post scores as you play), but since I’m not really into mobile technology (yet?), that’s not a priority of mine.

So if you’re a golfer and think you might be interested, sign up to find out when it’s ready (curious minds are fine, too).

2008 by Ryan Heath | Get In Touch

flickr

DesolateInfinityLooking upDazedBlurred