« January 2009 | Main | March 2009 »

February 2009 Archives

February 12, 2009

RailRoad III

Testing

You can't really follow the the Test Driven Development methodology without writing tests as you develop. While the ROR documentation is fairly complete to get started on building an application, the organization of the documentation (as well as most reference material) seems to put testing at a lower priority in the context of the learning the Rails platform. It would be nice if the documentation illustrated TDD within the examples as well.

Fixtures

Rails provides a facility for quickly creating reproducible testing data through YAML fixture files. The key point of using a fixture as of 2.2.2 is that it is essentially a fully fledged ActiveRecord. When you run a test against a feature, one of the first things that happens is that Rails populates the database table with the fixture information. It is important that your fixture YAML file is syntactically correct - if the data specified in the file references an undefined column or violates a database constraint, Rails will fail to run the test and generate a whack tonne of arcane error messages. Don't do this unless you like to read stack traces.

Strange Boolean Behavior. If validates_presence_of is placed on a boolean attribute, it can only be assigned true values within the fixture. Attempting to assign false to the fixture attribute will break unit tests with the following error:

Attribute_name can't be blank.
 is not true.

In addition, when assigning boolean values within tests, you cannot use the false keyword. Doing so will create an invalid record. The numeric convention to assign true or false is to use 0 and 1 respectively.

Validation of Date versus DateTime Objects. Fixtures can use eRB to embed ruby code within the fixture itself. Unfortunately, when it comes to Date / DateTime attributes, the object returned by the fixture record is database dependent. In most cases, to validate date/time attributes requires using the strftime comparison. There is no way to get a Date object from a DateTime object. Seems intuitive, but Rails does not provide this facility. For simplicity, I've included the format parameters for posterity:

%a - The abbreviated weekday name (``Sun'')
%A - The  full  weekday  name (``Sunday'')
%b - The abbreviated month name (``Jan'')
%B - The  full  month  name (``January'')
%c - The preferred local date and time representation
%d - Day of the month (01..31)
%H - Hour of the day, 24-hour clock (00..23)
%I - Hour of the day, 12-hour clock (01..12)
%j - Day of the year (001..366)
%m - Month of the year (01..12)
%M - Minute of the hour (00..59)
%p - Meridian indicator (``AM''  or  ``PM'')
%S - Second of the minute (00..60)
%U - Week  number  of the current year,
        starting with the first Sunday as the first
        day of the first week (00..53)
%W - Week  number  of the current year,
        starting with the first Monday as the first
        day of the first week (00..53)
%w - Day of the week (Sunday is 0, 0..6)
%x - Preferred representation for the date alone, no time
%X - Preferred representation for the time alone, no date
%y - Year without a century (00..99)
%Y - Year with century
%Z - Time zone name
%% - Literal ``%'' character
t = Time.now
t.strftime("Printed on %m/%d/%Y")  
    #=> "Printed on 04/09/2003"
t.strftime("at %I:%M%p")            
    #=> "at 08:56AM"

Attempting to resolve the fixture issues highlights one of Rails glaring deficiencies - the lack of authoritative, comprehensive documentation. From IRC, to the blogs I researched to troubleshoot these issues, it was apparent that everyone had rather unsubstantiated opinions regarding built-in testing facilities on Rails which were largely incomplete. No application is perfect, but this is a rather obvious omission.

testhelper.rb

By default, the testhelper.rb file has the fixtures :all parameter enabled. If your scaffold YAML files are not valid, this setting will only throw a lot of unintelligible errors. To build up your test suite one model at a time, it might be useful to simply have a script with ruby commands to run tests you've already finished. For example, I have a file that I've chown to be executable which contains commands to run individual tests.

ruby unit/model1_test.rb
ruby unit/model2_test.rb

The testhelper.rb file is not all bad. In fact when used in it's primary capacity, it's quite useful. For example, instead of writing custom assertions for my unit tests, I placed a bunch of these in testhelper.rb. This allows you to follow the DRY (Don't Repeat Yourself) principle an simplify test suite creation. Combined with TextExpander, it's positively stunning how quickly you can create a fairly thorough test suite for a model. I've included some of my custom assertions so that others don't have quite the barrier to entry that I encounter:

def assert_not_valid(object, 
    msg="Object is valid when it should be invalid")
  assert(!object.valid?, msg)
end
alias :assert_invalid :assert_not_valid
  
def assert_presence_required(object, field)
  # Test that the initial object is valid
  assert_valid object
   
  # Test that it becomes invalid by removing the field
  temp = object.send field
  object.send "#{field}=", nil
  assert_invalid object
  assert !object.save, 
    "Cannot save record with invalid #{field}"
  assert object.errors.invalid?(field), "Invalid #{field}"
   
  # Make object valid again
  object.send("#{field}=", temp)
end
     
def assert_has_attribute(object, field)
  assert object.has_attribute?(field), 
     "#{object}.klass is missing #{field}"
end
	
def assert_doesnt_have_attribute(object, field)
  assert !object.has_attribute?(field), 
    "#{object}.klass is includes #{field}"
end 
	
def assert_has_readonly_attribute(class_type, field)
  attribute = class_type.readonly_attributes.find { |attr| attr == field }
  assert_not_nil attribute, 
     "#{field} read only attribute was not found."
end
	
def assert_required_length_less_than(object, field, length)
  duplicate = object.dup

  # Valid at length-1
  duplicate.send "#{field}=", "a"*(length-1)
  assert_valid duplicate

  # Valid at length
  duplicate.send "#{field}=", "a"*length
  assert_valid duplicate

  # Invalid at length+1
  duplicate.send "#{field}=", "a"*(length+1)
  assert_invalid duplicate
  assert duplicate.errors.invalid?(field), 
    "Invalid length of : #{field}"
end
	
def assert_inclusion (object, *list)
  assert list.include?(object), "#{object} not in #{list}"	
end
	
def assert_numerical(field)
  assert Integer(field) || Float(field), 
    "The field is not a number."
end
	
def assert_positive_number(field)
  assert_numerical field
  assert field > 0 , "The field is not a positive number."
end
	
def assert_negative_number(field)
  assert_numerical field
  assert field < 0 , "The field is not a negative number."
end
	
def assert_uniqueness_of(object, field)
  duplicate = object.clone
		
  assert_invalid duplicate, 
    "The #{field} attribute must be unique."
  assert duplicate.errors.invalid?(field), "Invalid #{field}"
end
	
def assert_inheritance(object_class, object)
  assert_kind_of object_class, object, 
     "#{object} is not a kind of #{object_class}."
end

Pluralization Conventions

One of the things that I needed to get used to was the use of pluralization of objects in Rails. From table names in migrations, to polymorphic has_many associations, being able to specify either single or multiple objects requires being aware of when rails expects plurals. As I mentioned in a previous article, pluralization is controlled through inflections.rb. This is something to reiterate again, unless you want to learn the hard way.

Counter Cache attributes. A counter cache attribute added to a model, is expected to be a pluralized (and possibly inflected) name. For example, to add a Comment object counter cache to another object requires the comments_count attribute to be present.

Limitiations of Single Table Inheritance

The implementation of object inheritance in Rails can only be described as extremely rudimentary. As per Fowler's description of this pattern, a single table is responsible for all base and subclass data. While this is great for subclasses that simply add attributes to a base class when they are subclassed, this does not work so well when derived subclasses have different attributes.

Lack of Class Attribute Encapsulation. Essentially, Rails implementation of STI does not allow subclasses to specify which attributes belong exclusively to themselves. An attribute that is available from one subclass is automatically available to all sibling subclasses. There is no mechanism to disable attribute accessors for certain classes, and enable them for others. It seems the only advantage of STI in rails is to provide convenient way to encapsulate methods and provide a mechanism for storing an object Class name in a table. Not exactly what I would consider Object Oriented .... While it has been suggested that attribute accessors can be overridden within the models, this seems like too much of hack that could have unintended consequences with regards to ActiveRecord behavior. The trade off is to probably code in logic in the classes to ensure that only certain attributes have valid values - the lesser of two evils.

Storage of Class Name as the Base Type in Associations. If polymorphic associations are used in conjunction with STI, the association_type by default must be the base Class name. If it is the actual class name, the association will fail. There are hacks that will allow the actual Class name to be stored by overriding the association_type attribute, but doing so is less than all encompassing. For example, having :dependent => :destroy conditions on the association will fail when this hack is implemented. This would then require manually destroying associated objects. Not really a great compromise in my opinion.

Miscellaneous

Sometimes things just don't work the way you expect them to. In most cases, it's probably just a case of the documentation not being updated with changes in the code. One particularly pernicious bug was the way in which ActiveRecord deals with obj.clone versus obj.dup.

Limitations actually a Good Thing?

While these limitations suggest that these areas of Rails are under developed, Martin Fowler has an interesting take on the rigidity shown by the core rails team to solving "enterprise" problems. In his article he suggests that Rails resistance to antiquated conventions which give strength to the existing system. This goes to prove just because you can do something, doesn't mean you should (such as attempting to create an Object Relational Mapper for Rails - RHibernate anyone?).

I reserve the right to withhold judgment ...

February 26, 2009

Fink, ImageMagick, and RMagic Installation for Rails

It took me a while to figure out how to get Fink to install ImageMagick and the RMagic gem for use with attachment_fu and I thought I would share my method. From the #rubyonrails channel on freenode, it seems that I am a Luddite; most users prefer MacPorts. As I've written before, I think there is value in understanding the ports system you are using in OS X and that there be a clear separation between system binaries and those you add yourself. Polluting the /usr directory with unsupported 3rd party code can only lead to heartache. It also helps to acknowledge the underlying software dependencies in case your production environment varies from development (which is my case). That being said, I've provided the link for those who just have to use MacPorts.

ImageMagick & Fink

Before the rmagic gem can be used, ImageMagick needs to be installed on the machine. Fink is great at being able to build from source and resolving dependencies, but in this case, it took a bit of coercion to get ImageMagick installed. At various points during dependency resolution (for ghostscript and dbus-dev), I was required to abandon FinkCommander and issue the following commands from a Terminal:

# fink scanpackages
# sudo apt-get
# sudo apt-get install ghostscript=8.61-5
# fink scanpackages
# sudo apt-get
# sudo apt-get install dbus-dev=1.2.6-1

I'm assuming that order is important in the dependency resolution process, so I included both sets of commands which needed to be run separately at different points in the installation process.

This eventually installed ImageMagick, but unfortunately, the default configuration included the dreaded --enable-hdri option. Fixing this required digging into Fink source code management internals. By default, Fink keeps ports metadata information in .info files which are arranged based on stable/unstable trees as well as by function. For ImageMagick, the relevant files were located in the /sw/fink/10.5/unstable/main/finkinfo/graphics directory. A grep against the ImageMagick files revealed that imagemagick-nox.info and imagemagic.info both contain the option. Simply removing it from these files and rebuilding the ImageMagic port resulted in an rmagic compatible installation.

# fink rebuild imagemagick

There are a number of supplementary packages that are required to build rmagic; I've included a list of the required ports:

# imagemagick
# imagemagick1-dev
# imagemagick1-shlibs
# imagemagick1-svg

In particular, be sure to have the imagemagick1-dev port installed, otherwise Magick-config will not be installed which is required for gem installation.

scripts/plugin fail

I usually try to follow the path of least resistance - In this case, it was to use the built-in plugin managment utility in Rails. Unfortunately, no matter how I spelled rmagic, the plugin could not be found in the sources packaged with 2.2.2. I even tried to find the plugin with rapt. C'est la vie.

RMagic Installation from Source

Although the preferred method of installing Rails components is by using gem, OSX's fonts break the rmagic tests that run when documentation examples are being generated. This leaves the method of last resort - installing from source. The trick is to change the allow-example-errors=no parameter to yes in the .config located in the source directory. Once this is done, run the following commands:

# sudo ruby ./setup.rb
# sudo ruby ./setup.rb install

Presto! At this point RMagic should be installed on your system. You can verify by running the following commands (as per the MacPorts installation doc):

# irb -rubygems -r RMagick
>> puts Magick::Long_version
This is RMagick 2.9.1 ($Date: 2009/01/12 23:08:35 $) Copyright (C) 2008 by Timothy P. Hunter
Built with ImageMagick 6.4.1 02/25/09 Q16 http://www.imagemagick.org
Built for ruby 1.8.6
Web page: http://rmagick.rubyforge.org
Email: rmagick@rubyforge.org

All in all, the process is not unlike a contortionist attempting to pick their nose with their pinky toe .... doable, but certainly uncomfortable. Comments welcome.

About February 2009

This page contains all entries posted to Z1R0 in February 2009. They are listed from oldest to newest.

January 2009 is the previous archive.

March 2009 is the next archive.

Many more can be found on the main index page or by looking through the archives.

Creative Commons License
This weblog is licensed under a Creative Commons License.