Archive for the '/dev/rails' Category

The Power of rake

rake is one of those little things about Ruby on Rails that I suspect most of take completely for granted, running the odd rake tast without really ever thinking much about it. Turns out rake is pretty damned cool.

Gregg over on Rails Envy has written a very nice intro to rake in Ruby on Rails Rake Tutorial (aka. How rake turned me into an alchoholic) :

In this article we’re going to discover why Rake was created, and how it can help our Rails applications. By the end you should be able to write your own tasks, and learn how to get piss drunk using rake in no less then three steps.

He’s written well enough that by about midway down the article I was already thinking of ways to use rake to do some of the unpleasant maintenance tasks every site needs that I was consequently putting off thinking about. rake: not bad at all.

No Comments »

chris on June 12th 2007 in /dev/rails, /dev/ruby

options_for_select and <<

It's fairly common to want to grab a bunch of unique database entries and display them as a set of form option tags. Rails has which does quite a nice job of it.

But what to do when I also want to include items not in the database, such as "all" or "none"?

RUBY:
  1. <%= select_tag :activity, options_for_select( ( Activity.find( :all ).collect{ |x| [ x.name, x.id ] } ).uniq <<[ "all", 0 ] ).sort, { :onchange => "submit();" } %>

In this case I'm appending "all" onto the hash that constitutes the options.

(Note that it's probably better to have the uniq be in the SQL as DISTINCT but that's for another day.)

3 Comments »

chris on May 23rd 2007 in /dev/rails

‘rake db:migrate’ on Production Server

When the time comes to deploy on a production server, the following sets up the database:

rake db:migrate RAILS_ENV=production

3 Comments »

chris on May 3rd 2007 in /dev/rails

in_place_editor_field as a textarea

You want to use in-place editing but you've got a large amount of text and the default textfield created by in_place_editor_field just isn't large enough? Set the rows property:

RUBY:
  1. <%= @activity_item_note = note; in_place_editor_field :activity_item_note, :note, {}, :url => { :controller => 'activity_item_note', :action => 'set_note_text', :id => note.id }, :rows => 4 %>

When the rows property is set to any value greater than 1, in_place_editor_field renders a textarea instead of a textfield.

No Comments »

chris on April 13th 2007 in /dev/rails

in_place_edit_for, in_place_editor_field and Complex Cases

In-place editing of content is a very cool feature and for the most part dead-simple to do in Rails. Unfortunately all the examples around, including the ones in Rails Recipes, assume you'll be implementing this functionality in only the simplest of cases, usually when displaying just that editable data on the page.

In my case I have much more complex requirements - an instance of an event can have many notes attached to the event. If the author of the note is viewing the event they should be able to edit any notes they've written, and edit them in the page using in_place_editor_field.

In this instance the controller for the notes is different than the controller for the page.

Here's how I finally managed to get it to work, using the editing of a note title as an example.

First, add the following line to the note controller:

RUBY:
  1. class ActivityItemNoteController <ApplicationController
  2.     in_place_edit_for :activity_item_note, :title

This automagically sets up a function in the controller called set_activity_item_note_title for us.

Second, in the view, which is app/views/activity/show.rhtml and thus controlled by the ActivityController I display all notes via:

RUBY:
  1. <% activity_item.activity_item_notes.each do |note| %>
  2. ...
  3. <% end %>

At this point every other example of in_place_editor_field would tell you to add the following to your page to create an in-place editor:

RUBY:
  1. <%= in_place_editor_for :note, :title %>

or some variant of that (or something even worse if the author is building their example off of Rail's scaffolding). But that won't work in this case for many, many reasons which you have probably already discovered for yourself if you're reading this.

So, third, I customized the in_place_editor_for like so:

RUBY:
  1. <%= @activity_item_note = note; in_place_editor_field :activity_item_note, :title, {}, :url => { :controller => 'activity_item_note', :action => 'set_activity_item_note_title', :id => note.id } %>

@activity_item_note = note; - set our iteration as an instance variable instead, since in_place_editor_for requires one of these to work (sigh).

:title - define the property to be edited.

:url => { :controller => 'activity_item_note', :action => 'set_activity_item_note_title', :id => note.id } - take back control of the controller and point it to the one that should handle the edit. Add the name of the method and make sure the id of the note being edited is in there as well.

And that does it.

Warning: Because we're passing the id in the URL there's pretty much nothing in this to stop someone from hijacking the URL, injecting their own id's and running rampant through the database, mucking with titles as they see fit. The default set_activity_item_note_title created when I defined in_place_edit_for :activity_item_note, :title has no provision to manage this sort of authentication. As such it is probably a good idea to wrap the auto-generated functions with your own code to manage authentication, something like:

RUBY:
  1. note = ActivityItemNote.find( params[:id] )
  2.   if @user.id == note.user_id
  3.     # pass along to built-in 'set_activity_item_note_title' here
  4.   else
  5.     # scold the user for trying to edit someone else's note
  6.   end

In my case a simple version of this is:

RUBY:
  1. def set_note_title()
  2.     begin
  3.       note = ActivityItemNote.find( params[:id] )
  4.       if nil != note
  5.         note.user_id == @user.id ? set_activity_item_note_title : render( :text => note.title )
  6.       end
  7.     rescue
  8.       render :text => ' --- '
  9.     end
  10.   end

1 Comment »

chris on April 13th 2007 in /dev/rails

Polymorphic Associations

It's been at least a week since something in Rails made me stop and think "whoa - cool!" but today it was polymorphic associations. I am impressed indeed.

Good examples of how to implement are a bit hard to come by though. Polymorphic Association in Rails by Manik was the clearest for me, particularly his simple demonstration in the console.

Intuitively this feels like a feature that it might be easy to abuse. Has anyone done any performance benchmarking on how linking relationships like this might impact an application?

No Comments »

chris on April 10th 2007 in /dev/rails

Ruby Jobs

Looking for a job working with Ruby and/or Rails?

http://jobs.rubynow.com/

Even better, there's an RSS feed: http://jobs.rubynow.com/rss/feed.xml

(Note to my current co-workers: no worries, I'm not looking, honest)

(Thanks Sam!)

No Comments »

chris on April 9th 2007 in /dev/rails, /dev/ruby

English-friendly timespan in Rails

Awhile ago I was looking for a function that would convert a number of seconds into a human-readable timespan string. For instance: convert 86400 into 1 day and 172800 into 2 days, accomodating intervals ranging from weeks to minutes.

I didn't find one. Instead, this is what I came up with this, which at its fanciest will output something like 2 days, 14 hours and 15 mins.

RUBY:
  1. # Takes a period of time in seconds and returns it in human-readable form (down to minutes)
  2. def time_period_to_s time_period       
  3.   out_str = ''
  4.      
  5.   interval_array = [ [:weeks, 604800], [:days, 86400], [:hours, 3600], [:mins, 60] ]
  6.   interval_array.each do |sub|
  7.     if time_period>= sub[1] then
  8.       time_val, time_period = time_period.divmod( sub[1] )
  9.          
  10.       time_val == 1 ? name = sub[0].to_s.singularize : name = sub[0].to_s
  11.            
  12.       ( sub[0] != :mins ? out_str += ", " : out_str += " and " ) if out_str != ''
  13.       out_str += time_val.to_s + " #{name}"
  14.     end
  15.   end
  16.  
  17.   return out_str 
  18. end

Obviously it's hard-coded only to support english and ignores seconds (too fine-grained for me) but over-coming either of those limitations should be fairly straight-forward.

Update: I terminated the upper value at weeks because things get complicated when we get to months. We can all agree that a day is 24 hours, a week is 7 days, but how long is a month? Not four weeks, not five weeks.... Predictability dies at months, where the actual timespan becomes relevant, and that's more work than is worthwhile at this point. Feel free to embrace and extend :)

2 Comments »

chris on April 4th 2007 in /dev/rails, /dev/ruby

Deprecated @flash Warnings

I keep getting this deprecation warning:

DEPRECATION WARNING: @flash is deprecated! Call flash.[] instead of @flash.[].

which would be reasonable except that the code triggering it is this:

RUBY:
  1. <% for name in [:message, :error] %>
  2.     <% if flash[name] %>
  3.         <div id="flash" class="alert <%= name.to_s %>"><%= h( flash[name] ) %></div>
  4.     <% end %>
  5. <% end %>

Something's not sitting right with the @flash deprecation warning.

5 Comments »

chris on March 28th 2007 in /dev/rails

Class Objects from Strings

Or "Let the code write the code".

One of the beauties of Rails is that the "convention over configuration" mandate means that not just program execution but program implementation are extremely predictable. This is very handy when you'd prefer to have your code write the code, as I needed to this afternoon when I wanted to call the same function on a bunch of different classes who's names were available for the operation as an array of strings.

I first started out by spending far too much time mucking around with eval() but that felt kludgy and I've never liked using eval() in any language - I'm naturally gun-shy when it comes to arbitrary code execution. I may know all the code paths into that point today but what about six months from now? Will I always remember never to pass user data through there accidentally?

A little more reading of the Kernel module and the Module class revealed a much cooler and ultimately much simpler way to implement the same functionality: const_get().

In a nutshell:

RUBY:
  1. begin
  2.   the_obj = Kernel.const_get( ( activity.model_name ).camelcase )
  3.   activity_events = the_obj.find( :all )
  4. rescue
  5.   the_obj = nil
  6. end

Much nicer but still not as nice as I'd like since I've had to wrap it in the begin / rescue block. For reasons unexplained const_defined? always returns false when used in here.

Note: Some googling afterwards showed up this very nice example by Matt Biddulph in which he used const_get() to walk the ActiveRecord models and create OmniGraffle diagrams of their relationships. That's a nice mash-up.

No Comments »

chris on March 15th 2007 in /dev/rails, /dev/ruby