Showing posts with label Ruby on Rails. Show all posts
Showing posts with label Ruby on Rails. Show all posts

Saturday, January 16, 2016

Setup of Ruby Development Environment VM

I recently needed to provide some development environment setup instructions to a friend, so I figured it would be a good idea to document them here in case I need them again or if it helps anyone else.

Let me start off with a few premises:

  • I like doing Ruby on Rails development.
  • Ruby development on Windows is not pretty.
  • The computer I'm using most often is Windows-based (provided for my "real" job which is not Ruby development).
  • My personal computers are Macs.
  • I want to be able to work from any computer but don't want to hassle with keeping the environments in sync.
  • I've used multiple flavors of Linux over the years back to the SLS days (Linux version 0.9x), and am very comfortable with it.
With those premises in mind, I've adopted the use of a virtual machine (VM) to be my development environment.  It allows me to separate my personal development environment from other clutter (or even probing by workplace compliance scans), and allows me to readily move it from machine to machine as necessary.  I also usually host my applications on Linux-based servers, so it provides me more confidence in an environment that matches the deployment environment.

I personally don't like living in a VM window to work.  I also feel that I'm already running a windows manager (Windows or OSX) on my host operating system, so why incur the overhead of running another one in the VM.  Therefore, I run a "headless" Linux in the VM, and open X windows on the host by running a simple X server on the host OS.

Step 1 - Setup an X server on the host OS

For Windows, I recommend VcXsrv (see http://sourceforge.net/projects/vcxsrv/), but if you're already using Cygwin for other UNIX applications, the Cygwin Xserver (see http://x.cygwin.com/docs/ug/setup-cygwin-x-installing.html) works well, or alternatively you could try Xming (http://sourceforge.net/projects/xming/files/latest/download).

For OSX, I use XQuartz (see http://www.xquartz.org/)

Step 2 - Install VMWare (or VirtualBox, Parallels, etc.)

I'm not going to provide instructions for every virtualization software out there.  I use VMWare so that's what I'm describing.  Other solutions work just as well.

For Windows, VMWare Player does everything you need, and it's free!

For OSX, there's no free player, so you have to purchase VMWare Fusion.  (but, hey, it's the only thing you need to buy!).

Step 3 - Download a Linux distribution ISO

Again, there are plenty of options (Ubuntu, Red Hat, SuSE, etc), but I'm using Ubuntu here as it is frequently used for hosting.  You want the Server version (not Desktop) as it will be more light-weight without all the X11 + window manager overhead.  Also, I recommend the LTS (longer term service) version so you don't have to deal with upgrading your OS all the time (besides security fixes of course).

Step 4 - Create a New Virtual Machine

Select the VMWare option to create a new virtual machine, point it to the ISO and it should be pretty straight forward.

You don't have to be excessive with the resources you allocate for the VM.  The default recommendations by VMWare will probably suffice, but if you have a little more to give, then bump it up a little.  For example, the VMWare recommendation for the memory is 1GB, my system has 16GB so I gave the VM 2GB.  I allowed it 4 out of the 8 processor cores, and give it up to 40GB of disk space.

Step 5 - Forward local ports to VM

While this is not strictly necessary, it is useful.

For Windows, in earlier versions of VMWare Player, the Virtual Network Editor was included, but you needed to know the right command to access it.  In case the current omission is unintentional and they add the network editor back in future releases, the method to access the network editor for VMPlayer was:

  1. Open command prompt as administrator
  2. cd "C:\Program Files (x86)\VMware\VMware Player"
  3. rundll32.exe vmnetui.dll VMNetUI_ShowStandalone

As it stands currently, that will give you an error that the vmnetui.dll is not found.  So instead, you need to extract the Virtual Network Editor from the VMWare Workstation package.  This is done by:

  1. Download the latest VMWare Workstation package.
  2. unpack it locally with this command :
    "
    VMWare-worksation-full-10.0.1-xxxxxx.exe /e .\ext"
    note the xxxxxx is the release id you got at download
  3. Go to the newly created "ext" directory and open the "core.cab" (use your favorite zip util)
  4. Get the "vmnetcfg.exe" from there, and copy it to the vmwareplayer install directoryGet the "_vmnetcfglib.dll" file and rename it "vmnetcfglib.dll", and then copy it to the same directory than for vmnetcfg.exe
OR  

Download vmnetcfg.exe and vmnetcfglib.dll and put them in your VMWare Player install directory.

Once you have the Virtual Network Editor, select the NAT adapter (should be VMnet8), click on the "NAT Settings..." button, and add whatever ports you'll be using as shown below.  In this example, my VM IP is 192.168.216.131 and I'm forwarding ports 22 for SSH and 3000 as the default Rails server port.  Add port 80 if you'll be running your apps through a normal HTTP server like Apache or nginx (ex. via Passenger).




For Macs using VMWare Fusion, edit the vmnet8 NAT config file using the terminal and your favorite editor

sudo emacs /Library/Preferences/VMware\ Fusion/vmnet8/nat.conf

or
 
sudo vim /Library/Preferences/VMware\ Fusion/vmnet8/nat.conf
 
 
Near the bottom you’ll see something like this
 
[incomingtcp]
 
# Use these with care - anyone can enter into your VM through these...
# The format and example are as follows:
# = :
#8080 = 172.16.3.128:80
 
This is where we’ll be putting the port forwarding.  We'll assuming the same VM IP and that we still want to forward to the Rails server port 3000 and SSH port 22.   Since OSX is likely already running an SSH server on port 22, we'll forward from port 2222 instead.  So, add the lines:

3000 = 192.168.216.131:3000
2222 = 192.168.216.131:22 


Restart VMWare Fusion and it should be forwarding to these ports for you.  Alternatively, you could restart the VMWare networking using the command line:


sudo /Applications/VMware\ Fusion.app/Contents/Library/vmnet-cli --stop
sudo /Applications/VMware\ Fusion.app/Contents/Library/vmnet-cli --start


Optionally, if you want to ensure your VM always gets the same IP address, you can edit /Library/Preferences/VMware\ Fusion/vmnet8/dhcpd.conf and add a clause to the bottom with the format of:


host <some-name> {
    hardware ethernet <MAC-ADDRESS>;
    fixed-address <IP-address>;
}

You can determine the VMWare assigned MAC address for your VM by looking in your VM's *.vmx file and look for a line like:

ethernet0.generatedAddress = "00:0c:29:42:f0:54"

Then to assign a static IP address of 192.168.216.131 to this VM, you could add the clause:

host ubuntu {
    hardware ethernet 00:0c:29:42:f0:54;
    fixed-address
192.168.216.131;
}


Remember to restart the Fusion Networking services either by restarting Fusion or using the command line option.
Most of the OSX instructions came from http://encyclopediaofdaniel.com/blog/fusion-dhcp-port-forwarding/ where you can also find information on a Ruby gem to make the changes for you.

Step 6 - Connect using SSH with X forwarding

X11 forwarding needs to be enabled on both the client side and the server side.

On the client side, the -X (capital X) option to ssh enables X11 forwarding, and you can make this the default (for all connections or for a specific conection) with ForwardX11 yes in ~/.ssh/config.

On the server side, X11Forwarding yes must specified in /etc/ssh/sshd_config. Note that the default is no forwarding (some distributions turn it on in their default /etc/ssh/sshd_config), and that the user cannot override this setting.

The xauth program must be installed on the server side. If there are any X11 programs there, it's very likely that xauth will be there. In the unlikely case xauth was installed in a nonstandard location, it can be called through ~/.ssh/rc (on the server!).

Note that you do not need to set any environment variables on the server. DISPLAY and XAUTHORITY will automatically be set to their proper values. If you run ssh and DISPLAY is not set, it means ssh is not forwarding the X11 connection.

To confirm that ssh is forwarding X11, check for a line containing Requesting X11 forwarding in the ssh -v -X output. Note that the server won't reply either way.

(credit to http://unix.stackexchange.com/questions/12755/how-to-forward-x-over-ssh-from-ubuntu-machine)

Monday, October 29, 2012

Ruby 1.8.7 vs 1.9.3 performance

There are plenty of Ruby 1.8 to 1.9 benchmark results out there, and this is by no means as thorough as most.  I thought I'd share the results for one of my websites when I upgraded it from Ruby 1.8.7 to Ruby 1.9.3 as demonstrated in my New Relic report for the site. Conveniently, I upgraded the site on a Monday morning, and since that's also when New Relic reports switch over, it provided a fairly clean week-to-week comparison (perhaps 1/3 of Monday 10/22 was still Ruby 1.8.7).

High Level Summary

With no other changes, other than fixes required to allow the application to work with Ruby 1.9.3, simply upgrading Ruby resulted in the overall response time average for the week dropped from 304ms to 72ms (a 76.32% drop in response time).

Unfortunately, the week immediately before the upgrade was a bit of an anomaly.  The week before that had a 153ms average response time, which is more in line with typical weekly results.  However, that's still a 53% reduction in response time from 1.8.7 to 1.9.3.

Here's a daily comparison for the 3 weeks:

Ruby 1.8.7 


Ruby 1.9.3



Keep up the good work, Ruby development team!

attachment_fu as a gem for Rails 3.2

As I've mentioned before, I develop the web site for St. Francis Society Animal Rescue.  As I've also described in that earlier article, it started as a Rails 1.2 app, then Rails 2.1, then 2.3.  Right now it's in Rails 3.1, but I'm about to switch it to Rails 3.2.  Having some history, it was developed to use attachment_fu to upload all the images for the cats and dogs.  While I know many people have abandoned attachment_fu for paperclip, or carrierwave, or dragonfly, etc. attachment_fu has continued to work just fine for me so if it ain't broke, don't fix it.  Plus, there are currently over 13,000 animal images that have been uploaded, and I don't really want to hassle with converting them over to a new attachment system.

Enter Ruby 1.9.x

The last official update to the attachment_fu github repository was on April 25, 2009.  While that update is for Ruby 1.9 compatibility fixes, if you try to use it as is for Ruby 1.9.3, you'll find it won't work.  To that end, I've forked off a new repository that will fix additional Ruby 1.9.3 incompatibilities.  If you want to continue to use attachment_fu as a plugin with Ruby 1.9.x and Rails prior to 3.2, you can use my github repository in your project simply by issuing this command in your project:

git submodule add https://github.com/pothoven/attachment_fu.git vendor/plugins/attachment_fu

Enter Rails 3.2.x

attachment_fu has always functioned as a plugin (vendor/plugins), the problem is that Rails 3.2 will give you this error if you continue to use it as a plugin:

DEPRECATION WARNING: You have Rails 2.3-style plugins in vendor/plugins! Support for these plugins will be removed in Rails 4.0. 
Move them out and bundle them in your Gemfile, or fold them in to your app as lib/myplugin/* and config/initializers/myplugin.rb. 
See the release notes for more on this: http://weblog.rubyonrails.org/2012/1/4/rails-3-2-0-rc2-has-been-released.

To that end, I've also updated my fork of attachment_fu to function as a gem! Simply add this line to your Gemfile
 
gem 'pothoven-attachment_fu'

I need to acknowledge Christophe Porteneuve for doing most of the gem conversion work. I just pulled in his updates and fixed a few problems with it that I encountered. Please feel free to let me know if you have any problems using the gem.

Thursday, October 04, 2012

Self-marking required fields in Rails

David Sulc wrote a nice article entitled, Self-marking required fields in Rails 3 that utilized a key element of Ryan Bates' Railscast on Validations in Rails 3.  In Ryan's original solution, he creates a new helper method named mark_required that uses the Rails validation reflection to determine if the model requires that the field to be present (via validates :presence => true or validates_presence_of).  If the field is required, this helper method adds a '*', otherwise it does nothing. Using this helper method in your code, your code would look like:

<%= f.label :name %><%= mark_required(@user, :name) %>
<%= f.text_field :name %>  

David improves upon this idea by enhancing the ActionView::Helpers::FormBuilder label helper instead of adding a new helper.  This eliminates the need to add the extra call to the mark_required helper for every label, which can be easily forgotten.  If the field is required, the enhanced label helper appends a "*" to the label text to indicate that the field is required.  So now your code is back to the normal form of:
<%= f.label :name %>
<%= f.text_field :name %>  

That's a nice improvement, but I thought it would be an even better design practice to simply give the label a required CSS class, and then allow the designer to be able to easily modify how required fields should be indicated.   My version of config/initializers/form_builder.rb looks like:

class ActionView::Helpers::FormBuilder
  alias :orig_label :label

  # add a 'required' CSS class to the field label if the field is required
  def label(method, content_or_options = nil, options = nil, &block)
    if content_or_options && content_or_options.class == Hash
      options = content_or_options
    else
      content = content_or_options
    end

    if object.class.validators_on(method).map(&:class).include? ActiveModel::Validations::PresenceValidator
      if options.class != Hash
        options = {:class => "required"}
      else
        options[:class] = ((options[:class] || "") + " required").split(" ").uniq.join(" ")
      end
    end

    self.orig_label(method, content, options || {}, &block)
  end
end
With this revised version in place, all labels of required fields are given a required CSS class.  If you want to indicate required fields with an asterisk after the label, you can do that by adding this CSS rule to your stylesheet,

/* add required field asterisk */
label.required:after {
    content: " *";
}
However, by extracting the method of indicating that the field is required from being hard-coded in the helper to instead using a CSS class, anything you can do with CSS is open to you and can be quickly and easily modified without updating the helper code.  You can change colors, fonts, borders, add images before or after, you could even use the dreaded text-decoration:blink.  The options are nearly endless.

Update for Rails 4! 


Thanks to the input from alex_m and Dan in the comments below, the follow revised version should work for Rails 4 (with ActiveAdmin):

class ActionView::Helpers::FormBuilder
  alias :orig_label :label

  # add a 'required' CSS class to the field label if the field is required
  def label(method, content_or_options = nil, options = nil, &block)
    if content_or_options && content_or_options.class == Hash
      options = content_or_options
    else
      content = content_or_options
    end

    if object.class.respond_to?(:validators_on) &&
      object.class.validators_on(method).map(&:class).include?(ActiveRecord::Validations::PresenceValidator)

      if options.class != Hash
        options = {:class => "required"}
      else
        options[:class] = ((options[:class] || "") + " required").split(" ").uniq.join(" ")
      end
    end

    self.orig_label(method, content, options || {}, &block)
  end
end

Thanks again alex_m and Dan!

Thursday, May 10, 2012

Rails Rumble 2010

In my long absence from posting, I see I totally skipped 2010.
For the 2010 Rails Rumble we created Commendable Kids.

Here are the results for our entry CommendableKids.com:
  • Finished 4th place out of 180 teams
  • Winners of the Appearance Category
  • Runners Up from Chargify as Most Potential to Monetize
    Read More
  • #1 team from the U.S.
  • Tech Crunch Top 5
    Read the Review

St. Francis Society Animal Rescue

One of my side-projects is the development of the St. Francis Society Animal Rescue web site.  My friend Brian Burridge and I originally converted the site from a static web site to a Ruby on Rails site several years ago (2008 timeframe).  I believe it was initially a Rails 1.2 project.  Besides the public site, it includes a fairly involved back-end administration component that beyond just allowing content management of the web site, performs all the animal rescue administration functions (detailed animal information with health records, adoption records, etc.).  At the time we were both fairly new to Ruby on Rails and we decided to use ActiveScaffold to build this administration component.

At some point we upgraded to Rails 2.1 and then in 2010, Brian left the project to be able to better focus on his other numerous projects and I further upgraded it to Rails 2.3.x.  These Rails version upgrades were more effort than a typical Rails upgrades may be due to various gem dependencies, most specifically ActiveScaffold.  ActiveScaffold is really a pretty nice framework for admin sites, but it had limitations that required work-arounds and those work-arounds often didn't work when upgrading.

During the end of 2011 to the beginning of 2012, I did a more drastic upgrade.  I migrated to Rails 3.1.x.  However, this wasn't just a simple migration, I decided to re-write the entire application.  The most involved part of this re-write is what most people will never see, the administration area.  I decided to totally abandon ActiveScaffold.  Brian told me about ActiveAdmin that he was using on some of his other projects, but after fighting with ActiveScaffold for long, I opted to stay clear of such a major framework dependency and just wrote the entire admin from scratch with straight Rails.

Comparisons of the old and new administration pages.

Here's a comparison of the listing of the cats.  I reduced the amount of information displayed on the list to reduce some clutter, and I've made some of the frequently changed values available to be changed directly on the list (save instantly via Ajax) to eliminate the need to open the edit form just to change a status.


Filtering the list required an extension to ActiveScaffold that was problematic to upgrade, and it resulted in a very large area added to the top of the list.  Now it uses a jQuery UI dialog.


ActiveScaffold constructed its forms very vertically which didn't utilize the space well.  I now have full control of the layout allowing me to organize things better.



Responsive Web Design

Last summer at the 2011 front-end design conference I had the privilege of having Ethan Marcotte introduce me to the idea of responsive web design.  So, I took that to heart and designed both the public site and the admin area to be responsive.  So, if we look at the cat listing page again and compare it between a browser and an iPhone, you can see that the navigation menu has collapsed and the table has dropped several columns.

There are also intermediate changes for tablets, but it's time I wrap this up.

I'm only touching on a very few of the page layouts and features.  Beyond a new look and feel, the re-write of the application also brought performance improvements.  Here is a New Relic report on the week I switched it over from the old to the new.

Note: Thursday was the transition day, so it should be ignored.  The jump in CPU percentage is due to also switching from a shared hosting environment on DreamHost to a Linode slice since DreamHost doesn't support Rails 3.1 at this time -- at least not on my server)




I encourage you to take a look at the St. Francis Society Animal Rescue public site, particularly if you're in the Tampa Bay area and are interested in a new pet.  Be sure to try it out in different sizes to see how the responsive layout works, and please let me know what you think of it.

Adding and removing multiple associated sub-models dynamically on a single form in Rails

The last couple blog posts have been about PeepNote ... its beginning and its ending.  Now I thought I'd share a snippet of code that I developed for it the form of a simple contact manager.

One of the things Brian pointed out in his concluding remarks about PeepNote that I referred to in my last post was,

As I began interacting more with potential customers I realized that my target audience was not what I thought it was. It wasn’t people like me who were heavily using Twitter for career networking and wanted to keep track of how I met people and what I knew about them. Instead, the only people that would pay for the service were companies. Companies that wanted to use it to track potential customers; a CRM.
Part of the CRM functionality that we had incorporated into PeepNote was to add contact information about your peeps.  The idea of a contact manager is well understood by most audiences and are thus commonly used as a demonstration application.  So, what's special about this one and why I'm I writing about it?

This particular contact manager demonstrates (and improves upon) Ryan Bates “Handle Multiple Models in One Form” recipe (#13) from the Advanced Rails Recipes book.  In Ryan’s original recipe, he added discrete field values (individual tasks on a to-do list) to the form. Any tasks that did not have an identifier associated with it was a new task, and any missing task identifiers from those currently in the database were assumed to be deleted.  This is great when you're essentially adding single values to a list, but what about adding multiple pieces of information that need to stay together as a single entity (like the parts of a contact information - street address, city, state, phone, etc)?

The solution is to use a JavaScript variable to assign temporary unique identifiers to newly added nested models (addresses, phone numbers, urls, etc). These newly added models are assigned a negative value in order for the contact controller to distinguish between new and existing records.  So, new records with a negative id are added, existing records are updated as necessary, and any records with ids that are no longer in the submitted list are removed.

This is one of the problems I solved for PeepNote and have extracted into a simple stand-alone Rails application.   Rather than provide a lengthy description here, I placed the source code on GitHub and have a running demo on Heroku.

PeepNote conclusion

I realize it's been a very long time since my last post, but I am still around.

In regards to that last post, I thought I'd share a blog entry by my RailsRumble teammate discussing the status of PeepNote:

PeepNote: The Rumble, the Startup, and now…the Conclusion 

(http://brianburridge.com/2012/03/12/peepnote-the-conclusion/)

Tuesday, August 25, 2009

PeepNotes - my Rails Rumble 2009 entry


This past weekend I took part in Rails Rumble 2009 and help create a site called PeepNote.


If you're not familiar with Rails Rumble, I'll borrow the introduction from their web site:

The Rails Rumble is a 48 hour web application development competition. As a contestant, your team gets one weekend to design, develop, and deploy the best web property that you can, using the awesome power of Ruby and Rails.


Two of my teammates have written up nice articles on our experience, so I encourage you to read them:

Thursday, August 13, 2009

Managing Tags in Rails

In this article I'm going to document a small piece of the administration component of the Ruby Rails Review that I wrote. On Ruby Rails Review we kept a collection of interesting articles regarding Ruby on Rails, and these articles were tagged with various tags so that if you went to browse the articles you could filter them by the various tags. Of course, there's no reason to implement that whole tagging system ourselves since there are plug-ins for that. To make the articles taggable we used the acts_as_taggable_on plug-in. This article just gives a method for easily editing the tags for a given article which works much like the Blogger labeling I used when writing this article. For Ruby Rails Review, the article editor form is shown below, though I've dimmed out everything but the tagging sections: As you can see, there is a text field where you can type in tags in a comma separated list, as well a list of all currently defined tags. Selected tags are shown in blue and underlined to give a visual indication that they are selected, but all tags are clickable to either select or deselect them. As tags from the list at the bottom are selected or deselected, they are added or removed from the text field. If you enter tags in the text field they are either selected or added (or deselected if you remove a tag) from the tag list below. So, how do we do this? First, the form code looks like this:

<% form_for @article_detail, :url => { :action => "update", :id => @article_detail.article_id } do |f| %>
   ...
   <%= f.text_field :tag_list, :onchange => "updateTagList(this.value);" %>
   ...
<% end %>
<div id="tags" style="margin-top:30px;">
<% @tags.each do |tag|%>
<div class="tag<%= ' selectedTag' if @article_detail.tag_list.index(tag.name) %>" onclick="selectTag(this);"><%= tag.name %></div>
<% end %>
</div>
You'll also need a little bit of CSS added to make the selected tags stand out (added to your main.css). I happened to make them blue and underlined, but you can make them highlighted or whatever you please:
.selectedTag {
      color: #247CD4;
      text-decoration: underline;
}
Finally, add the necessary JavaScript in either your application.js or a separate JavaScript file of you choosing:
function selectTag(tagElem) {
    tagElem.toggleClassName('selectedTag');
    var selected_tags = buildTagList();
    $('article_detail_tag_list').value = selected_tags;
    $('article_detail_tag_list').focus();
};

function updateTagList(selected_tags) {
    selected_tags = selected_tags.split(',');
    var tags = $H();
    $$('.tag').each(function(tag) { 
        tags.set(tag.innerHTML, tag);
        tag.removeClassName("selectedTag");
    });
    selected_tags.each(function(tagName) {
        tagName = tagName.strip();
        var tag = tags.get(tagName);
        if (tag) {
            tag.addClassName("selectedTag");
        } else {
            $('tags').insert('<div class="tag selectedTag" onclick="selectTag(this);">'+tagName+'</div>');
        }
    });
};

function buildTagList(tag) {
    var selected_tags = [];
    $$('.selectedTag').each(function(tagElem) {
        selected_tags.push(tagElem.innerHTML);
    });
    if (tag) {
        if (selected_tags.indexOf(tag) === -1) {
            selected_tags.push(tag);
        } else {
            selected_tags = selected_tags.without(tag);
        }
    }
    selected_tags = selected_tags.join(',');
    return selected_tags;
}
That's it! Now you can easily maintain the tags on your taggable items.

Friday, February 06, 2009

Thursday, January 08, 2009

Ruby tip to handle missing records

Here's quick and simple tip to cleanly handle non-existent records in Ruby.

By default if you try to use find(id) to fetch a record from your database and the id doesn't exist you'll get a RecordNotFound error like:


>> r = Record.find(9999)
ActiveRecord::RecordNotFound: Couldn't find Record with ID=9999
from /usr/lib/ruby/gems/1.8/gems/activerecord-2.1.1/lib/active_record/base.rb:1383:in `find_one'
from /usr/lib/ruby/gems/1.8/gems/activerecord-2.1.1/lib/active_record/base.rb:1366:in `find_from_ids'
from /usr/lib/ruby/gems/1.8/gems/activerecord-2.1.1/lib/active_record/base.rb:541:in `find'
from (irb):3

You could wrap it with a begin and rescue and have the rescue clause create a new record such as:

begin
r = Record.find(id)
rescue
r = Record.new
end

Or you could simply use find_by_id which will return nil if the record is not found and re-write it as:

r = Record.find_by_id(id) || Record.new

Tuesday, December 23, 2008

Merb and Rails Merge

The competing Ruby frameworks, Rails and Merb, have decided to join forces!

Here is the announcement from Rails creator David Heinemeier Hansson and the similar announcement from the Merb perspective from Yehuda Katz.

Keep up with all the latest Ruby and Rails news at Ruby Rails Review.

Tuesday, December 16, 2008

Ruby on Rails News Site

I haven't been very active on this blog recently, but I've been keeping plenty active on other endeavors. One of which is the Ruby Rails Review which my friend, Brian Burridge, announced last week. As he mentioned in the announcement I created a very easy to use CMS tool which allows us to drag-and-drop the news stories all over the page for placement (or addition and removal), as well as a bookmarklet to simplify the process of adding new stories. I've very happy with how it turned out, unfortunately no one gets to see it but he and I.


Tuesday, September 09, 2008

Campfire activity notifier for Gnome, KDE, or console

The 37signals blog points out a simply Ruby script for KDE that will give you a visual notification when a new message is posted to a Campfire chat room. I have modified that script to work for Gnome as well as KDE, and additionally a text console. I also made a few other additions such as:

  • Allow you to specify a specific chat room(s) as a command line argument
  • Multiple chats rooms can be monitored concurrently using threading
  • Added the Campfire logo to the notifications
  • Added some initial status messages to display login status, room topic, and current people in the room
  • Display of the last 3 messages of the current day's transcript so you know what's going on without need to login
  • Filter out ads
Here is my version of the script:
#!/usr/bin/env ruby

# == Synopsis
#      Program to monitor a campfire chat room
#      Modified from code provided at: http://www.snailbyte.com/2007/09/13/campfire-activity-notifier-for-kde/
#
# == Usage
#      campfireMonitor [roomName] [roomName] ...
#
# == Author
#      Steven Pothoven and Snailbyte Ltd.

require 'rubygems'
require 'tinder'
require "cgi"

class App
  VERSION = '0.1.1'

  def initialize arguments, stdin
    # default settings
    campfireSubdomain = 'mySubdomain'
    campfireUsername = 'user@email.com'
    campfirePassword = 'password'
    roomNames = ['Room1', 'Room2']
    if arguments.length > 0
      roomNames = arguments
    end

    @ui = 'gnome'
    @campfireIconPath = '/path/to/campfire-logo.png'
    # define you favorite audio player and audio file here for audio notifications
    @soundCommand = 'mplayer /usr/share/sounds/pop.wav'
  

    @campfire = Tinder::Campfire.new campfireSubdomain
    if @campfire.login campfireUsername, campfirePassword
      alert nil, "CampfireMonitor", "Successfully logged in #{campfireUsername}"
      @rooms = roomNames.collect { |roomName| @campfire.find_room_by_name roomName }
      # remove any invalid rooms
      @rooms.delete(nil);
      @rooms.each do |room|
        notify room, "CampfireMonitor", "Entered room."
        notify room, "CampfireMonitor", "Topic is: #{room.topic}.".gsub("'","")
        notify room, "CampfireMonitor", "Current users are:  #{room.users}.".gsub("'","")
      end
    else
      alert nil, "CampfireMonitor", "Failed to log in #{campfireUsername}"
    end
  end

  # display notifcation message
  def notify room, user, msg
    if msg and msg.size > 0
      msg = CGI.unescapeHTML(msg);
      if @ui == 'kde'
        system "dcop knotify default notify eventname \'#{user}\' \'#{''+@campfire.uri.to_s+'/room/'+room.id+'/'+room.name+': ' unless room.nil?} #{msg}\' '' '' 16 2"
      elsif @ui == 'gnome'
        system "notify-send -i #{@campfireIconPath} '#{user}' '#{''+@campfire.uri.to_s+'/room/'+room.id+'/'+room.name+': ' unless room.nil?} #{msg}'"
      else
        puts "#{room.name+':' unless room.nil?}#{user} - #{msg}"
      end
    end
  end
  
  # notify with sound
  def alert room, user, msg
    notify room, user, msg
    if @soundCommand and @soundCommand.length > 0
      system @soundCommand
    end
  end

  def run
    # first get any missed messages for today
    threads = []
    @rooms.each do |room|
      thread = Thread.new do
        room.transcript(Date.today).last(3).each do |m|
          if !m.nil? and m[:message] and m[:message].size > 1
            notify room, m[:person], m[:message].gsub("'","")
          end
        end
      end
      threads << thread
    end
    threads.each { |thread| thread.join}

    # listen for more messages
    threads = []
    @rooms.each do |room|
      thread = Thread.new do
        notify room, "CampfireMonitor", "Waiting for messages..."
        room.listen do |m|
          if !m.nil? and m[:message].size > 1
            unless m[:person] == "Ad"
              alert room, m[:person], m[:message].gsub("'","")
            end
          end
        end
      end
      threads << thread
    end
    threads.each { |thread| thread.join}

  end
end

app = App.new(ARGV, STDIN)
app.run
I'm sure you can think of plenty more customizations such as command-line options for all the default settings, etc. But I wanted to keep it fairly simple. One last thing, here is the cropped version of the Campfire logo I use for my notifications (campfire-logo.png):

Friday, February 08, 2008

acts_as_conference - day 1

I attended the acts_as_conference conference on Ruby on Rails. It was put on by Rails for All whose founder, Robert Dempsey had previously given a free class on Ruby on Rails in Tampa, where I live, for free which was my first real exposure to RoR, and how I learned about this conference.

The conference was attended by around 150 people. There were some technical glitches, particularly with WiFi access for everyone, but overall it ran smoothly.

Here are the topics covered with a brief summary of each:

Advanced DSLs in Ruby - Neal Ford

A DSL is a domain specific languages. Basically this is about building fluent (readable) interfaces and improve abstraction by eliminating noise.

So you can type:

recipe = Recipe.new "Spicy Bread"
recipe.add 200.grams.of Flour
recipe.add 1.lb.of Nutmeg

instead of something like:

recipe = Recipe.new("Spicy Bread")
flour = new Ingredient("Flour")
flour.setMeasurement("grams")
flour.setAmount(200)
recipe.add(flour)
nutmeg = new Ingredient("Nutmeg")
nutmeg.setMeasurement("lb")
nutmeg.setAmount(1)
recipe.add(nutmeg)

This works by using a few techniques. First extending the Numeric class to allow the numbers to converted based on the measurement type to a common measurement in grams, and add an of function to build a new Ingredient object

class Numeric
def gram
self
end
alias_method :grams, :gram

def pound
self * 453.59237
end
alias_method :lb, :pound
alias_method :lbs, :pound
alias_method :pounds, :pound

def of ingredient
if ingredient.kind_of String?
ingredient = new Ingredient(ingredient)
end
ingredient.quantity = self
ingredient
end
end

The reason you can create the Ingredient without surrounding the Ingredient name with quotes is by type transmogrification which uses of the const_missing construct:

class Object
def self.const_missing(sym)
eval "Ingredient.new(sym.to_s)"
end
end

Neal added some additional techniques like using bubble method in order to build a nutrition profile for the recipe as you build the recipe.
Overall, the presentation was very interesting to see how you can manipulate Ruby to make ever more readable code. However, in general I don't think it's worth the effort or the confusion for new developers trying to figure out how the code even works.

Working with others: Best Practices for Rails Teams - Luke Francl

Luke discussed some of the issues that can arise in a team environment, specifically for Rails. He had a nice handout which unfortunately isn't available in softcopy form. Here are the bullet points.

  • Migrations - Migrations have a tendency to stop working (what he calls migration decay). So, use the schema.rb as the authoritative source for your database schema. When creating the database on another system run rake db:schema:load instead of running the migrations.
  • Seed Data - Loading seed data in your migrations can break as the models change and fixtures are for test data. Luke recommends using db-populate to populate seed data along with ActiveRecord::Base.create_or_update

    def self.create_or_update(options = {})
    id = options.delete(:id)
    record = find_by_id(id) || new
    record.id = id
    record.attributes = options
    record.save!
    record
    end

  • 3rd party code - vendor everything using gemsonrails
  • Security - HTML escape everything in your views with h. There are several XSS plugins that can be used as well. Mass assignments (ex. LineItem.new(params[:line_item])) can be dangerous, be sure to protect your attributes with:

    • :attr_protected - attributes you can't write to (via mass parameter assignment)

    • :attr_accessible - attributes you can write to



  • Source control - The source code is the life of your project. Be sure to use source control management (SCM). Commit atomic changes, not batches of changes with informative messages.

  • Bug tracking - Use a bug tracker that has some workflow for various states of completion as well as email integration to inform interested parties and SCM integration to link code fixes to bugs.

  • Continuous integration - Tis ties everything together. Verifies that new code doesn't break anything else right away, ensures all libraries and code packages are available, etc.



Rails on AIR - Peter Armstrong


This was mostly about using Flex and using Flex Builder (Eclipse). Flex is way to build Flash applications without knowing Flash and AIR allows your Flash application to run as a stand alone desktop applications. Frankly, I wasn't too interested in this. It seems like a lot of work for not much gain. Yes, Flash is installed in like 99% of browsers, however there are drawbacks to using it in web apps. For example, you site with not be indexable be search engines which can reduce the traffic to your site, and I've been able to accomplish most everything I would need to use Flash for with HTML/CSS/JavaScript.

Keynote - Dan Benjamin


Dan provided some tips for developing good software development and interface design:

  • As Simple As Possible, But No Simpler
  • Focus on Creating a Great User Experience
  • Anticipate User Actions
  • Think Like your Users
  • You're Probably Not as Good of a Designer as you Think
  • Develop for One Scenario, Not for Ten
  • Good Code Does Not Impress Users
  • Don't Release a Beta
  • Apologize (for any problem)
  • Just Ship It
"simplicity is the key to happiness in the modern world"


That's all for day 1. Watch for day two which has these topics:
  • JRuby - Charles Nutter
  • Shining a Light on the Dark Magic of ActiveRecord - Anthony Eden
  • Smarticus University: BDD With RSpec - Bryan Liles
  • Adding Media to Your Rails Application - Dave Naffis and Josh Owens
  • Lessons from the Trenches – Learning from the Rails Bootcamp - Charles Brian Quinn
  • Keynote - Obie Fernandez

Friday, January 11, 2008