.

multiple_auto_complete plugin

Rails’ auto_complete control can be a really handy way to allow users to select an object or string from a list of pre-existing values, however, one big limitation to its use is the fact that you can’t have more than one of them on a single page.

UPDATE: just to clarify this a bit. If you have more than one auto_complete that tries to access the same model/field combo, you get into trouble. You can have multiple auto_completes so long as they access different model/field combinations.

I tried a couple of hacks around this problem (some involving method_missing), and finally found a method that works pretty well and doesn’t mess up your controllers.

The approach I ended up using was to rewrite the ‘auto_complete_for’ and ‘text_field_with_auto_complete’ functions so that they ignore trailing numbers on a dom_id. Once you install the plugin (see below), you simply use it like this…

  1. # View file
  2. <%= text_field_with_auto_complete ‘object_1′, ‘name’ %>
  3. <%= text_field_with_auto_complete ‘object_2′, ‘name’ %>

  1. # controller file
  2. auto_complete_for :object, :name

All the auto_complete fields named /object[-_]d+/ will call the ‘auto_complete_for_object_name’ function in the controller now. The default response function will pull out the correct information and respond as usual.

When you submit the form, the values will be passed in the params hash as

  1. params = { ‘object_1′ => {‘name’=>’name1′}, ‘object_2′=>{‘name’=>’name2′ }}

Installation

script/plugin install svn://sciwerks.com/home/sciwerks/public_svn/multiple_auto_complete/

ruby-units

In scientific (and non-scientific) programming one frequently needs to be able to manipulate units such as ‘10 mm’, ‘6 gal’, or ‘3 weeks’. The traditional way to handle units in programming is to simply save the scalar part of the unit (i.e., the 10 from ‘10 mm’), and then make sure you programmatically convert the unit whenever necessary. Needless to say, this is error prone and it constrains the user to only being able to enter known unit types.

Ruby has a few libraries (notably, Facets) available that handle unit conversions in one form or another. However, most of these systems don’t quite meet my somewhat demanding and arbitrary requirements, so I set out to write a new one.

Installation

You can get ‘ruby-units’ using

gem install ruby-units

Usage

To use this gem, you need to do the standard

  1. require ‘rubygems’
  2. require ‘ruby-units’

Creating a Unit object

Because I use a lot of units in my work, and I’m lazy, there are several different ways to create a new ‘Unit’ object.

  1. unit = Unit.new(”1 mm”)
  2. unit = Unit(”1 mm”)
  3. unit = U(”1 mm”)
  4. unit = “1 mm”.to_unit
  5. unit = “1 mm”.unit
  6. unit = “1 mm”.u
  7. unit = “mm”.unit (yields ‘1 mm’)

Of these, I use #5 the most since it is reasonably terse without becoming obscure.
You can also create Unit objects from Numerics, Arrays, and Time objects. Numerics just become unitless Unit objects, Arrays are interpreted as [scalar, numerator, denominator], and Time objects are converted into a number of seconds.

Unit objects can be serialized through YAML using ‘unit.to_yaml’, which facilitates them being stored in database columns using Rails’ :serialize functionality.

Unit strings generally consist of text in the following format:

  1. R = "8.31451 kJ/mol*degK".unit
  2. a = "9.8 m/s^2".unit

Since Units are constructed from strings, it is easy to have a user specify them in input fields. Because the format is fairly standard, there should not be too many problems with misinterpreted units.

A couple of points:

  1. only one ‘/’ per unit. No ‘m/s/s’
  2. everything after the ‘/’ is in the denominator
  3. you can use negative exponents (’9.8 m*s^-2′)
  4. use either a space or a ‘*’ to separate units (’9.8 ms^-2′ == ‘9.8 1/ms^2′)
  5. ruby-units understands all SI prefixes, and a few others as well

Unit Compatibility

Sometimes it is helpful to know if two units are ‘compatible’. Compatible units can be converted into each other, so ‘feet’ and ‘meters’ are compatbile, but ‘feet’ and ‘liters’ are not.

  1. unit1= ‘1 feet’.unit
  2. unit2 = ‘1 meter’.unit
  3. unit3 = ‘1 liter’.unit
  4.  
  5. unit1 =~ unit2  #=>true
  6. unit1 =~ unit3  #=>false
  7.  
  8. unit1.compatible? unit2  #=> true
  9. unit1.compatible_with? unit3  #=>false

Unit Conversion

Units can be converted to each other so long as they are ‘compatible’. Ruby-units will throw an exception if units need to be compatible, but aren’t, so be sure to trap those exceptions when malformed input is possible.

Explicit Conversion

Explicit unit conversion occurs when the user asks for it. There are a couple of ways to force conversion to a particular unit.

  1. unit = "1 mm".unit
  2. unit >> "ft"
  3. unit.to("inches")
  4. unit2.to(unit1)     # converts unit2 to same units as unit1

Implicit Conversion

There are times when unit conversions are implied or assumed for simplicity. For example, when adding two units, the gem will convert the second unit to the same base units as the first prior to doing the math.

  1. unit1 = "1 meter".unit
  2. unit2 = "50 cm".unit
  3. result = unit1 + unit2 #=> 1.5 m

Unit Math

Unit objects come in really handy when doing complex math calculations:

  1. pr = "1 atm".unit
  2. n = "1 mol".unit
  3. R = "8.3144 J/mol*degK".unit
  4. T = "0 tempC".unit
  5. v = (n*R*T)/pr   # ideal gas law
  6. p v.to_s("l")  #=> 22.4 l

You can add, multiply, subtract, and divide units as you can with any Numeric class. You can also exponentiate or root them, although I restrict roots to integer values, since ‘m^0.5′ doesn’t really make any sense.

As an added bonus, all Trig functions (sin, cos, tan, sinh, …) can accept units that are compatible with radians. The values will be converted to radians before returning the result. So you can do this..

  1. Math.sin("90 deg".unit) #=> 1.0

Temperatures

Note that we specified the temperature in this equation as a ‘tempC’. Specifying a temperature will convert that unit into the equivalent number of degrees kelvin. So in this case, “0 tempC”.unit => 273.15 degK.

This is necessary to make the calculations work out right. Most times unit conversions are done by just adjusting the size of the units, but temperatures require you to also specify the zero point on the scale.

You can convert ‘degree’ units (like ‘degC’, ‘degF’, ‘degR’, or ‘degK’) to a temperature this way…

temp = ‘0 degC’.unit.to(’tempK’)

This really only makes sense when the value represents the degrees between absolute zero and the point of interest. Only the user knows when that is the case, so use it carefully.

Time and Date Math

Ruby-units understands a wide range of time units, and can convert back and forth between them with ease. Nothing new there.

However, ruby-units can add and subtract time units from Date, DateTime, and Time objects. Throw in a few rails-esque helpers and you can write code like this…

  1. one_hour_from_now = "1 h".from_now
  2. in_five_minutes = Time.in("5 min")
  3. an_hour_ago = "1 h".ago
  4. next_week = Time.now + "1 week".unit
  5. waiting_for = "min".until(one_hour_from_now)
  6. days_to_christmas = "days".until("12/25/06")
  7. age = "years".since("4/24/69")

Formatting Output

You can format the output of a Unit object by using the “.to_s” function and string format specifiers…

  1. length = "1 mm".unit
  2. length.to_s("%0.2f")  #=> "1.00 mm"
  3. height = "6 ft 4 in".unit.to_s(:ft)  #=> 6′4"
  4. weight = "8.25 lbs".unit.to_s(:lbs) #=> 8 lbs, 4 oz
  5. time = "1.5 h".unit.to_s("%H:%M") #=> 1:30

Miscellaneous Stuff

You can create ranges of units, so long as they have scalar integers.

  1. ( U("1 mm") .. U("5 mm") ).map  #=>[ 1 mm, 2 mm, 3 mm, 4 mm, 5mm]

If a unit is ‘unitless?’ then it can be converted back to other types of Numerics.

Note that ‘Unit’ objects are pretty slow compared to more common ones like Integers and Floats, so if performance is a big issue, you might want to consider using these sparingly.

Defining new units

You can define custom units by adding a code block like this..

  1. class Unit < Numeric
  2.   @@USER_DEFINITIONS =
  3.     {‘<inchworm>’ =>  [%w{inworm inchworm}, 0.0254, :length, %w{<meter>} ],
  4.      ’<cell>’ => [%w{cells cell}, 1, :counting, %w{<each>}],
  5.      ’<habenero>’   => [%{degH}, 100, :temperature, %w{<celcius>}]}
  6.   Unit.setup
  7. end

The format for the array is [%w{primary_name synonyms}, conversion_factor, unit_type, %w{}]

Useful References

Wikipedia: Units of Measurement

Cascading selects vs. auto_complete

I have lately come to dislike cascading selects (where the value from one select populates a second select with some values). These beasties have their purpose in life, but for the most part they are unnecessary evils.

If you have a ‘Really Long List’ to select from, then using a cascading select makes a lot of sense (unless you want your users to hate you). The problem is that they are a pain to code (well, not so bad if you use the KRJS plugin ), and they are still a pain to use if you actually know what you are looking for (then you have to remember what ‘category’ if falls in).

Lately I have been getting around this little problem using auto-complete boxes.
In Rails you can have an auto_complete box act like a select like this…

  1. # model
  2.  
  3. # id              :integer
  4. # name         :string
  5. # category_id :integer
  6. # full_name   :string
  7.  
  8. class Model < ActiveRecord::Base
  9.   belongs_to :category
  10.   validates_uniqueness_of :full_name
  11.  
  12.   def before_save
  13.     self.full_name = "#{self.category.name} – #{self.name}"
  14.   end
  15. end
  1. # Controller
  2. class ModelController < ActionController::Base
  3.  
  4.   auto_complete_for :model, :full_name
  5.  
  6.   def action
  7.     if request.post?
  8.        @model = Model.find_by_full_name(params[:model][:full_name])
  9.        # do some error handling here if item does not exist… or you could create a new one
  10.     else
  11.       @model = Model.new
  12.    end
  13.  
  14. end
  1. # view for ‘action’
  2. <%= start_form_tag :action=>’action’ %>
  3. <%= text_field_with_auto_complete ‘model’, ‘full_name’ %>
  4. <%= submit_tag ‘Submit’ %>
  5. <%= end_form_tag %>

Now when the user starts typing in the auto_complete field, they will get records that match against both the name and the category of the record. The enterprising reader could also replace certain characters (like space) with the SQL wildcard ‘%’ to get more flexible search results.

A user might be able to just type: ‘f mus’ to find ‘Ford – Mustang’.
(It’s not well known, but ‘f%mus’ will match ‘Ford – Mustang’ using the standard auto_complete box that ships with rails).

This type of entry is really fast and quite intuitive once users get the hang of it.

I find that this system works best when
1. There are a ton of choices
2. The user already has a pretty good idea which choice they want to select, but don’t really want to spend 15 minutes scrolling to find it.

It’s not particularly good if the user has no idea what to enter. In those cases, the standard cascading select may help walk them through the selection process.

Notes: You can avoid using the ‘full_name’ column if you properly design your own custom auto_complete_for function