Category: Ruby on Rails

What if the graphic design hue was adjustable in production?

In my current project, I needed to come up with a way to visually differentiate subdomain accounts. Adding rich theming support didn't make sense since:

  • End users are not responsible for the site design and hiring a graphic designer is low priority
  • The web application is for automating a workflow as much as possible. Less options and configuration means less maintenance and manual intervention

In order to meet these guidelines, I wondered about the possibility of asking end users to choose just one site color. Could the web application do something meaningful and seamlessly with that single choice?

Game Plan

Every time one of the stylesheets is linked to in the view:

  • open up the stylesheet
  • find all RGB color values and convert to match the requested color
  • save stylesheet to a cache directory

We'll be using the HSL color model. It breaks down a color into three components: hue, saturation, and lightness. Hue is what we're interested in. By shifting only a color's hue, we can, for instance, turn a red color to yellow without breaking the design legibility or contrast.

Conveniently, CSS3 allows us to adjust the hue by specifying an angle in degrees on a color wheel where red is both 0 and 360. Using this method, we only need to do basic substitution without any math conversions.

The bad news is HSL color values are not supported by older browsers or Internet Explorer 8. It is reassuring that modern versions of the other major web browsers (Safari, Firefox, Opera, and Chrome) do support the HSL model but we'll have to play the waiting game for legacy browsers to go out of use. To be safe, our color values will start off and end up as RGB values.

Finally, once we convert the stylesheets, we'll need to cache them somewhere. I'm storing them in #{Rails.root}/public/stylesheets/hue#{hue_value} because:

  • reuse: it is possible for several subdomain accounts to share the same hue value
  • it'll be served as a static file by Apache
  • free cache sweeping: upon deployment via Capistrano, old cached files are not carried over

Implementation Limitations

The colors in my stylesheets are RGB hex strings such as #1234AA and #123. There is no support for color names like 'red' in my implementation since I don't use them.

Another consideration are the graphic design constraints. Since only a dominate hue is allowed, image usage should be minimized or you'll have to worry about image conversions (not covered in this tutorial). Shoot for a minimalist design with blocks of solid color.

Implementation

install the Color gem. It is used to handle our color model and hue conversions.

# config/environment.rb config.gem 'color', :version => '1.4.0' rake gems:install

We're going to add several methods into ActionView::Helpers::AssetTagHelper. First, use alias_method_chain() to append our manipulation logic to run just before the original stylesheet_link_tag().

def stylesheet_link_tag_with_hue(*sources) options = sources.extract_options! sources.map! do |stylesheet_path| if stylesheet_path.starts_with?('/') || stylesheet_path.starts_with?('http') stylesheet_path else stylesheet_path = stylesheet_path + '.css' if File.extname(stylesheet_path) == '' ensure_stylesheet_for_hue(HUE, stylesheet_path) "hue/#{HUE}/" + stylesheet_path end end sources << options stylesheet_link_tag_without_hue *sources end alias_method_chain :stylesheet_link_tag, :hue

It'll run through the list of stylesheet sources. If any source is a candidate for hue conversion, ensure_stylesheet_for_hue() is called and the source is prepended with the hue directory path.

Here is where the file operations and hue conversions take place:

def ensure_stylesheet_for_hue(hue, path_suffix) source_path = Rails.root.join('public', 'stylesheets', path_suffix) destination_path = Rails.root.join('public', 'stylesheets', 'hue', hue.to_s, path_suffix) return nil unless File.exist?(source_path) return nil if File.exist?(destination_path) && Rails.env != 'development' File.open(source_path) do |file| output = file.read.gsub(/#([0-9a-f]{6}|[0-9a-f]{3})/i) do color = hsl_color_from_rgb_hex_string($1) color.hue = hue color.html end FileUtils.mkdir_p(File.dirname(destination_path)) File.open(destination_path, 'w') {|output_file| output_file.write(output)} end end

The color library doesn't have a constructor for our RGB color hex strings so we'll add one here.

def hsl_color_from_rgb_hex_string(hex) args = case hex.length when 3: [hex[0,1]*2, hex[1,1]*2, hex[2,1]*2] when 6: [hex[0,2], hex[2,2], hex[4,2]] end args.map! {|arg| arg.to_i(16)} Color::RGB.new(*args).to_hsl end

Results

Overall, I'm satisfied with the functionality. The biggest issue is the connotations we give to various colors and their shadings. For my site, it is hard to select an orange hue since dark orange doesn't look like orange. Also, it's not pink, it's lightish red. Some minor tweaking of saturation and lightness helps to make it work well with all hues though.

Design Ruby on Rails. June 01, 2009 - 10:10AM. 0 Comments

Exception Notifier Plugin Fix for Rails 2.2.2+

For those of us who are still using the Exception Notifier Plugin, be careful when upgrading to a recent version of Rails. Doing so with Rails 2.2.2+ and the configuration is in an environment file (#{RAILS_ROOT}/config/environments/production.rb for instance) will not send notification emails. Instead, it'll fail with:

Net::SMTPSyntaxError (501 5.1.3 Bad recipient address syntax ): /opt/ruby-enterprise-1.8.6-20090113/lib/ruby/1.8/net/smtp.rb:679:in `check_response' /opt/ruby-enterprise-1.8.6-20090113/lib/ruby/1.8/net/smtp.rb:652:in `getok' /opt/ruby-enterprise-1.8.6-20090113/lib/ruby/1.8/net/smtp.rb:634:in `rcptto' /opt/ruby-enterprise-1.8.6-20090113/lib/ruby/1.8/net/smtp.rb:545:in `send0' /opt/ruby-enterprise-1.8.6-20090113/lib/ruby/1.8/net/smtp.rb:544:in `each' /opt/ruby-enterprise-1.8.6-20090113/lib/ruby/1.8/net/smtp.rb:544:in `send0' /opt/ruby-enterprise-1.8.6-20090113/lib/ruby/1.8/net/smtp.rb:471:in `sendmail' /opt/ruby-enterprise-1.8.6-20090113/lib/ruby/1.8/net/smtp.rb:378:in `start'

Several ways to work around this problem. You could wrap the configuration inside an after_initialize block:

config.after_initialize do ExceptionNotifier.exception_recipients = %w(joe@schmoe.com bill@schmoe.com) ExceptionNotifier.sender_address = %("App Error" ) ExceptionNotifier.email_prefix = "[APP] " end

The other solution is to put the configuration in an initializer file.

Of course, there is the option to instead use Hoptoad or Exceptional.

There is a lighthouse ticket if you want to follow the discussion.

Ruby on Rails. March 23, 2009 - 07:45PM. 0 Comments

Reusing Rails 2.2.2 strip_tag view helper

In one of my Rails projects, I have this one class that strips HTML from some user submitted text. Instead of reinventing the wheel, I reused the Rails view helper strip_tags() by including the relevant module:

class Presenter include ActionView::Helpers::SanitizeHelper end

This worked great until I upgraded the Rails framework to v2.2.2. The following error began appearing:

undefined method `full_sanitizer' for Presenter:Class

Turned out it was easy to fix. Looking at the strip_tags method in actionpack/lib/action_view/helpers/sanitize_helper.rb:

def strip_tags(html) self.class.full_sanitizer.sanitize(html) end

It's trying to access a class method that doesn't exist in Presenter. While I included the instance methods, now I also have to extend the necessary class methods.

class Presenter include ActionView::Helpers::SanitizeHelper extend ActionView::Helpers::SanitizeHelper::ClassMethods end

Now all is good.

Ruby on Rails. March 12, 2009 - 09:38PM. 0 Comments

RSpec + Rails 2.2 = Neglected Rescue Handlers

Upgrading a Rails project to version 2.2 broke controller specs and I couldn't figure out why. The problem i was facing was that the default Rails exception rescuing would not work in the test environment.

I'm seeing alot of something like the following with 'rake spec':

ActiveRecord::RecordNotFound in 'PagesController responding to GET show should not find the record' ActiveRecord::RecordNotFound

An exception is raised during the page request as expected but it prematurely ends the spec instead of going through the rescue handlers I've setup through rescue_from(). I had no idea if this new behavior is expected or a regression. Sure I could do...

lambda{do_get}.should raise_error(ActiveRecord::RecordNotFound)

...but I wasn't looking forward to updating the spec suite to conform. Also, it wouldn't be testing the rescue handler behaviors. Initially, searching via the almighty Google yield no answers. Stepping through code execution using rdebug revealed that RSpec modifies Rails to not rescue exceptions as it normally would. However, the modification also includes a simple way to revert to the default behavior. Score! So now I'm doing:

before(:each) do controller.use_rails_error_handling! end

With that solved, I now know which search terms to use and, lo and behold, Google reveals that somebody had the same problem and already figured it out. Google, why had you forsaken me earlier?

Also, there is more context if you're interested.
Ruby on Rails. December 04, 2008 - 09:01AM. 0 Comments

Git Submodules, External Repositories, and Deployment

Even though I switched a few of my projects from Subversion to Git a couple of months ago, I still had lots to learn. So I dedicated last weekend to use submodules for Rails plugins. Unfortunately, it wasn't the quick conversion I thought it would be but it turned out for the best.

Git Submodules

External repository references was handled so elegantly by Piston for Subversion repositories that I was willing to wait for its Git support. But I came across this blog post that dispelled misconceptions and it turns out submodules act just like Piston. In short, submodules will keep track of which revision it is on and won't hit external resources unless it has to. Sweet!

Submodules are great and simple to work with on the development side. Do:

git submodule add git://github.com/rails/acts_as_list.git vendor/plugins/acts_as_list git submodule init git submodule update

And you're golden. But there is still much to do. In order to deploy efficiently with Capistrano, I'd needed to do a "set :deploy_via, :remote_cache". Onwards in search of a Internet accessible Git repository that I can call my own.

Git Repository Hosting

Enter Railsplayground.com Ruby on Rails Hosting. They have been hosting this blog for several years and have always been great for shared hosting. Bonus points for free Subversion and Trac hosting, up to 1GB disk space, with any hosting account.

Little did I know that they have been doing Git repository hosting for the past several months. Their homepage doesn't mention it although their forum as well as their sister site do.

If you're thinking about their git repository hosting solution, there are a couple of things to keep in mind.

PRO: Custom control panel: easy to create/delete git repositories and manage user accounts that has access to them

PRO: Trac integration is underway

CON: The only protocols they support are HTTP and HTTPS. Update: Railsplayground will support the Git protocol very soon. See comments below.

CON: It requires creating a ~/.netrc with my login and password. I don't want to store any passwords in plain text on my laptop so I symlinked the file to an encrypted vault where my project files are anyways. Update: Railsplayground will support the Git protocol over ssh very soon. See comments below.

MacPorts Pain

I'm using MacPorts on my Macbook Pro to compile git. This setup worked fine before but I couldn't access the newly created git repository.

$ git push upload master fatal: git-push is not available for http/https repository when not compiled with USE_CURL_MULTI error: failed to push some refs to 'https://secure2.svnrepository.com/g_username/repository_name/'

Unfortunately, MacPorts added in a patch to remove the USE_CURL_MULTI flag for stability reasons. Since the bug is triggered by a very large checkout on a powerpc mac, not applicable in my case, I removed the patch by editing the git Portfile.

#/opt/local/var/macports/sources/rsync.macports.org/release/ports/devel/git-core/Portfile patchfiles patch-Makefile.diff patch-http.h.diff # Before patchfiles patch-Makefile.diff # After

I'm not sure how to tell MacPorts to recompile git. I cheapened out and added a variant to force a recompile.

sudo port deactivate git-core @1.6.0_1+doc+svn sudo port install git-core +doc +svn +bash_completion

Once that was resolved, I encountered yet another error.

$ git push upload master error: Cannot access URL https://secure2.svnrepository.com/username/repository_name/, return code 1 error: failed to push some refs to 'https://secure2.svnrepository.com/username/repository_name/'

Turned out that curl needs to be compiled with the ssl variant. Normally, this should work:

$ sudo port install curl +ssl ---> Fetching curl-ca-bundle ---> Attempting to fetch certdata-1.48.txt from http://lxr.mozilla.org/seamonkey/source/security/nss/lib/ckfw/builtins/certdata.txt?raw=1 ---> Verifying checksum(s) for curl-ca-bundle Error: Checksum (md5) mismatch for certdata-1.48.txt Error: Checksum (sha1) mismatch for certdata-1.48.txt Error: Checksum (rmd160) mismatch for certdata-1.48.txt Error: Target org.macports.checksum returned: Unable to verify file checksums Error: The following dependencies failed to build: curl-ca-bundle Error: Status 1 encountered during processing.

MacPorts should already have this fixed but just in case, I got around the error by skipping checksums.

sudo port install curl +ssl checksum.skip=yes

Deployment Issues

Here's the git related entries in my Capistrano deployment script:

set :repository, "https://secure2.svnrepository.com/g_username/project_name" set :scm, 'git' set :deploy_via, :remote_cache set :branch, "master" set :git_shallow_clone, 1 set :git_enable_submodules, 1 default_environment['GIT_SSL_NO_VERIFY'] = 'true'

But deployment failed midway:

$ cap staging deploy Initialized empty Git repository in /home/username/project_name/shared/cached-copy/vendor/plugins/acts_as_list/.git/ github.com[0: 65.74.177.129]: errno=Connection timed out ** fatal: unable to connect a socket (Connection timed out) ** Clone of 'git://github.com/rails/acts_as_list.git' into submodule path 'vendor/plugins/acts_as_list' failed

The server firewall was getting in the way so I punched a hole for the git protocol:

iptables -I OUTPUT -p tcp --dport 9418 -j ACCEPT

Sweet Oblivion

With that all out of the way, I can now successfully deploy to the server the way I want to. Now I can get back to work :)

Ruby on Rails Web. August 25, 2008 - 05:41PM. 3 Comments