A delicious blend of Ruby and Rails…

named_scope with acts_as_tree

June 25th, 2008 benhughes

I fairly often use the acts_as_tree plugin in my applications.  While acts_as_nested_set (and superior variants..) is more powerful, often times a simple two-level deep hierarchy is all I need and acts_as_tree is simple.  I’ve found the new named_scope functionality in Rails 2.1 to be very helpful when dealing with tree data structures.

Firstly, it’s somewhat rare that I have one single root node in the tree structure (which is apparently how it’s meant to be used).  Instead I’ll have multiple “parent” nodes designated with a NULL parent_id and children beneath.  In the past I’ve always done something like: find(:all, :conditions => {:parent_id => nil}) to grab the top entries.  Instead with nested set you can do this:

named_scope :top, :conditions => {:parent_id => nil}

#Then:
Category.top

Another common task when dealing with hierarchical categories is to query on the base object (products for example) for members that are “part of” that category. Specifically, part of in a 2-level heirarchy simply means “where id = ? or parent_id = ?” on the joined categories table. Because this involves a join it was somewhat clunky to do before. Now with nested set, on the product model I can declare this:

named_scope :in_category, lambda {|c| {:include => [:category], :conditions => ["categories.id = ? OR categories.parent_id = ?", c, c]} }

# Then:
Product.in_category(3)

I would also highly encourage you all to check out RailsCasts Episode 112 “Anonymous Scopes” by Ryan Bates where he outlines a pattern for handling conditions elegantly with searches using named_scopes in Rails 2.1, something I’ve always found to be lacking from Rails core and always resorted to using plugins like criteria_query.

Nested URL Parameters

December 20th, 2007 benhughes

One of the great things about Rails is the ability to wire together form logic with extreme ease through Rails’ support of essentially representing hashes through the “object[name]” syntax of URL parameters. Arrays are also supported in a similar manner, making things like many-to-many relationship management cake.

In Rails 1.2 one issue I ran into is that this hash-based access logic cannot be nested when using helpers such as url_for (which is in turned used by helpers such as link_to, etc.) This type of functionality is rather useful when you are trying to encapsulate a set of name-value pairs within one “thing”, such as a “search” which is many criteria => value pairs. For a particularly project of mine I established a pretty nice design pattern for working with searches and results in a very abstract manner, an area of Rails that I generally find underdeveloped with no standard practice.

I was lucky to stumble on a Rails plugin that monkey-patches Rails to support this: nested_params_patch. Information about this plugin is quite sparse but this article does a good job of better-explaining what the plugin does. Essentially it allows you to do things like this:

person_url(:name => {0 => 'Ryan', 1 => 'Kinderman})

Pretty cool stuff! I am always amazed and the kind of plugins and extensions that are possible, all resulting from Ruby’s dynamicism and Rails’ heavily extensible architecture.

User Alert Management with flash and ActiveRecord::Errors

December 11th, 2007 benhughes

A very common paradigm in web application development is presenting the user with some sort of alert or flash message at the top of a page. These alerts are often styled with a background and some sort of icon to the left to indicate what type of error it is. Typically there are a few different types of messages such as “error”, “warning”, “confirmation”, etc., perhaps each type styled differently. Furthermore, such a block typically should support showing a set of messages within one type; not just one string. In Rails the two sources of these messages are usually either the flash object (such as flash[:error]) or ActiveRecord validations errors.

Here is an example of what I mean:

I’ve created a helper method that makes working with these types of messages a breeze.

Essentially it allows you to use the flash object with any of the following keys to specify messages: error, confirm, back, info, and warn (though you can modify it to suit any group of message types). You can either assign a string directly to this which is common, or create it as an array with multiple string messages. The helper will then read these flash objects and display it as a message block that can be styled with CSS, supporting multiple messages per type, separated by type, as well as dynamically grabbing ActiveRecord validation errors off of model object(s) of your choosing and showing those also. The usage of the helper method in your views is as follows

# In Controller:
flash[:confirm] = "Thank you for your input."
flash[:error] = []
flash[:error] << "Wrong Address"
flash[:error] << "Wrong Name"

# In View:
<%= message_block %>

# Or if you want to display errors on @customer
# as well as the flash messages,
<%= message_block :on => :customer %>

# You can even have it watch multiple model
# objects for errors:
<%= message_block :on => [:customer, :order] %>
Here is the helper method that makes this happen:

# Outputs the error messages block.
# The first argument specifies a hash options:
# * :on => :products   Also includes AR validation errors for @products
# * :clear => true     Clears messages after displaying
# * :keep => true      Keeps around messages for next response cycle
#
def message_block(options = {})
  out = ""

  [:back, :confirm, :error, :info, :warn].each do |type|
    next if flash[type].nil? or flash[type].empty?
    flash[type] = [flash[type]] unless flash[type].is_a?(Array)

    out << "<div class=\"container #{type}\"><ul>\n"
    flash[type].each {|msg| out << "<li>#{h(msg.to_s)}</li>\n"}
    out << "</ul></div>\n"

    flash[type] = nil if options[:clear]
    flash.keep[type] if options[:keep]
  end

  if options[:on]
    options[:on] = [options[:on]] unless options[:on].kind_of?(Array)
    models = options[:on].map {|m| instance_variable_get("@" + m.to_s)}.select {|m| !m.nil?}
    errors = models.inject([]) {|b,m| b += m.errors.full_messages}

    if errors.size > 0
      out << "<div class=\"container error\"><ul>\n"
      errors.each {|msg| out << "<li>#{h(msg.to_s)}</li>\n"}
      out << "</ul></div>\n"
    end
  end

  content_tag(:div, out, :id => 'message_block', :class => 'flash')
end

And the CSS code that works with it:

/*** Flash Messages ***/
.flash {

}

.flash ul {
    padding-left: 0pt;
    margin-bottom: 0pt;
    list-style-type: none;
    margin-left: 0pt;
}

.flash ul li {
    background: transparent url(../images/icons/bullets/gt.gif) no-repeat scroll left center;
    margin-bottom: 0.6em;
    padding-left: 1em;
    vertical-align: top;
}

.flash .container {
    padding: 1em;
    padding-left: 5em;
    margin-bottom: 1.5em;
}

.flash .error {
    background: #fcf6d0 url(../images/icons/flashes/error.gif) 1.5em 1em no-repeat;
    border-top: 1px solid #ecd757;
    border-bottom: 1px solid #ecd757;
}
.flash .back {
    background: #e9f3dc url(../images/icons/flashes/back.gif) 1.5em 1em no-repeat;
    border-top: 1px solid #bfcbb0;
    border-bottom: 1px solid #bfcbb0;
}

.flash .confirm {
    background: #e9f3dc url(../images/icons/flashes/confirm.gif) 1.5em 1em no-repeat;
    border-top: 1px solid #bfcbb0;
    border-bottom: 1px solid #bfcbb0;
}

.flash .info {
    background: #dee9f4 url(../images/icons/flashes/info.gif) 1.5em 1em no-repeat;
    border-top: 1px solid #b4c5d5;
    border-bottom: 1px solid #b4c5d5;
}

.flash .warn {
    background: #fcf6d0 url(../images/icons/flashes/warn.gif) 1.5em 1em no-repeat;
    border-top: 1px solid #ecd757;
    border-bottom: 1px solid #ecd757;
}

You may even want to consider writing a before_filter in your application controller that sets the flash types to arrays so you can just add messages to them with the << operator:

class ApplicationController < ActionController::Base
  before_filter :initialize_flash_types

  def initialize_flash_types
    [:back, :confirm, :error, :info, :warn].each {|type| flash[type] = []}
  end
end

HTML-Aware Truncate Text

December 9th, 2007 benhughes

When building a large custom PHP CMS system for DigitalPeach, I ran into a very difficult issue: truncating text but maintaining HTML nested tags correctly. Specifically, we were looking to breaking up large articles composed using FCKEditor into separate pages after a certain character threshold. Once can easily see the problem:

<p>This is a test <strong>with some bold in here</strong>.</p>

Now imaging having to truncate this text to 30 characters, and you end up with this:

This is a test <strong>with

While this example isn’t quite so severe and at worst would only make the rest of the text within the block-level element bold, clearly if we do a truncation that is blind to HTML some serious problems can arise. Furthermore, even if it doesn’t have much practical significance, you are breaking XHTML.

I was lucky to stumble across an excellent article by Mike Burns who describes a Ruby method using REXML’s pull parser that can accomplish this. His example extended the String class, so I modified it to work as a Rails helper all in one method:

def truncate_html(input, len = 30, extension = "...")
  def attrs_to_s(attrs)
    return '' if attrs.empty?
    attrs.to_a.map { |attr| %{#{attr[0]}="#{attr[1]}"} }.join(' ')
  end

  p = REXML::Parsers::PullParser.new(input)
    tags = []
    new_len = len
    results = ''
    while p.has_next? && new_len > 0
      p_e = p.pull
      case p_e.event_type
    when :start_element
      tags.push p_e[0]
      results << "<#{tags.last} #{attrs_to_s(p_e[1])}>"
    when :end_element
      results << "</#{tags.pop}>"
    when :text
      results << p_e[0].first(new_len)
      new_len -= p_e[0].length
    else
      results << "<!-- #{p_e.inspect} -->"
    end
  end

  tags.reverse.each do |tag|
    results << "</#{tag}>"
  end

  results.to_s + (input.length > len ? extension : '')
end

Note that the nested method above is a completely valid use of Ruby, though not widely used.

And now look at what it can do:

truncate_html("<p>Test <strong>bold</strong> done.</p>", 30)
# => "<p>Test <strong>bold</strong></p>..."