What are Ruby Symbols
One of the hardest things to explain about Ruby (especially to newcomers) is what symbols are. Many explanations of symbols that I see or hear often only cover a low-level/technical difference between symbols and strings. That is, that symbols use one memory location for each unique instance whereas strings use separate memory locations for each instance. Also that symbols are kept around in memory for the life of the program while strings get cleaned up when they are no longer used.
While these differences are accurate, they are just implementation details and not really what Ruby symbols are all about and those who quote only these differences by way of explanation of what Ruby symbols are, are missing the point.
What Ruby symbols are really about is beauty and semantics. Ruby symbols give the programmer the distinction between identity and information.
Consider this method call:
validates_presence_of :firstname, :lastname, :on => :create, :message => "can't be blank"
and if we single out
:message => "can't be blank"
:message is a symbol and "can't be blank" is a string. We could have easily written this statement like:
"message" => "can't be blank"
But the difference here is that :message is just a label for something, some concept, that a part of our program needs to work with. Whereas "can't be blank" is actual data or information. Another way to look at it is that we could mess with one of the two and the program would not care but if we mess with the other and the program does care.
If I change it to:
:my_message => "can't be blank"
The program no longer works as it did before. The method does not look for anything labeled :my_message. It is not a concept it knows about. The label :message is there only so the programmer and the method can talk about the same concept.
Now if I change it to:
:message => "can't be blank, sorry about that."
or
:message => "darf nicht leer sein"
The program still works as it did before, it just displays a different message. The "can't be blank, sorry about that." is clearly different to :message. It's not a label or an identity at all, it's information.
Many other languages don't have this distinction between information and identity, or at least not in such a nice, convenient way. As a result, strings are often used to perform the task of labeling.
$car = array ("weight"=>"1000", "unit_of_measure"=>"kg", "year"=>"2004", "price"=>"7000");
If the program knows about the concept of weight and unit_of_measure, using strings for those labels doesn't quite feel right.
car = {:weight => '1000', :unit_of_measure => 'kg', :year => '2004', :price => '7000'}
Now we have a separation between the concepts our program deals with and the associated data. And with syntax highlighting it reads so much nicer.
Take this example again
validates_presence_of :firstname, :lastname, :on => :create, :message => "can't be blank"
It reads so much nicer and is easier to see which bits, if changed, will affect the semantics of the program and which bits won't, but if we use strings for our labels, we lose that distinction.
validates_presence_of "firstname", "lastname", "on" => "create", "message" => "can't be blank"
An easy way to work out if you should use a symbol or a string is this: Does the program care about the thing I am typing, or does the user care about it?
if the program cares:
:symbolif the user cares:
"string"
Finding Descendants of a Class
Ruby offers a method for finding all the ancestors of a class (ancestors funnily enough) but, as far as I know, no easy way to get at all the descendants of a class.
I thought it had to be possible, this *is* Ruby after all! But in thinking about it a little more, I figured it could well be a tricky thing to do and probably require some hook in the Ruby language for it all to be even possible.
Luckily I found this beauty: ObjectSpace. With ObjectSpace you can eacherate over every object in the Ruby object space. What else?
So it tuns out that finding all the descendants of a particular class is not such a big deal after all. Just eacherate over ObjectSpace and do a little recursion:
class Object def self.show_descendants(klass = nil, indent_level = 1) unless klass klass = self puts ' - ' * (indent_level) + klass.to_s end ObjectSpace.each_object do |obj| if obj.class == Class and obj.superclass == klass puts ' - ' * (indent_level + 1) + obj.to_s show_descendants(obj, indent_level + 1) end end end def show_descendants(klass, indent_level = 1) klass.show_descendants(nil, indent_level) end end # you can pass the class as an argument show_descendants(ScriptError) # or you can send the method to the class Numeric.show_descendants
This code could be modified a little to return a list of descendants, each containing their own list of descendants rather than putsing it all to the screen.
Enumerable Constants for Active Record Models
A problem that had often bothered me in the past is this:
You're modelling a concept in a database like, say, property listings and a property has an attribute of "type". The possible values of "type" are not arbitrary. They are some fixed set of values like "rental", "for sale" and "auction", and the software needs to do stuff depending on the value of "type".
So this is like a :belongs_to where a property belongs to a suburb say. But there is an important difference. With a belongs_to :suburb the values for suburb are arbitrary. That is to say that the software doesn't care about the values, only that there is some value (which the user cares about, of course). But the program does care about the set of values available for the "type" attribute.
Setting up a has_many/belongs_to relationship always seemed overkill but it gave me a nice list of available attributes and I could enforce the relationship. The other problem was that the data in the table would mirror some set of constants in the code. If you changed one, you had to make sure you changed the other or things got out of whack. Not to mention problems of populating a new database and making sure all such "non-arbitrary" value set tables had the correct values (including correct ids to match the constants in code) in them.
Making the attribute "type" a string and stuffing values "rental", "for sale" and "auction" in it felt particularly bad.
Solution
I felt the correct way to do it was to use integer values in the field and represent these values as constants in the code (which is where the values have meaning).
But writing validation for these and coming up with a list of human readable descriptions (for drop-downs) was tedious. It felt like the values belonged in the database... but wait, no, the values have meaning in the code...
So I've written a Rails plugin to take away the tediousness of it all and make it a lot more DRY. With it I get
- an easy way to define, in a nice DSL looking way, what the constants are
- validation code generated dynamically
- a nice way of getting the list of values and descriptions (useful for UI stuff)
Examples
Model definition:
class Property < ActiveRecord::Base enumerable_constant :type, :constants => [:rental, :for_sale, :auction] end
Some view code:
<p><label for="property_type">Property Type</label><br/> < %= collection_select(:property, :type, Property::Type.constants, :id, :title, {:prompt => 'Select a Property Type'}) % ><br />
Note the use of Property::Type.constants in place of where you would normally use a collection object like @suburbs
Some code that tests the value
property = Property.find :first if property.type == Property::Type::FOR_SALE # do some stuff end
Other things you can access
>> Property::Type.constants.first.name => "RENTAL" >> Property::Type.constants.first.title => "Rental" >> Property::Type.constants.first.value => 1
Download the Enumerable Constants project
Rails Workshop - Sydney
Last weekend I attended a Rails Workshop in Sydney. The workshop was put together by wwworkshops and presented by Geoffrey Grosenbach of Top Funky Corp.
The workshop was great and I got a lot out of it. Geoffrey gave us a lot of material to wade thru and learn from including code (lots of code), screencasts and cheatsheets.
Geoff is a great presenter. Although Geoff knows a ridiculous amount of stuff, he always comes across as being very approachable willing to learn from those he's presenting to. I like his style.
It was also great to meet up with some talented people in the Sydney Rails community.
Convertible to csv format options
The latest version of convertible to csv can be told how to format particular fields when it returns data for the to_csv method. The default behaviour is to use the to_s method for each field. But sometimes you might want to display the data a little differently.
Use :format_options to specify a format method to be called when outputting a field. This is useful, for example, if you have a Time field and you want to format it differently to how Time.to_s displays a time.
Here's an example:
class Customer < ActiveRecord::Base acts_as_convertible_to_csv :header => true, :fields => %w(id firstname lastname email_address), :format_options => {:lastname => :format_lastname, :firstname => :format_firstname} def format_firstname firstname.upcase end def format_lastname lastname.reverse end end
As Customer.find(:all).to_csv is invoked, each time the fields "lastname" and "firstname" are output, instead of calling to_s on the fields, calls are made to the format_firstname and format_lastname methods respectively and your csv output will contain upcased firstname and reversed lastname.
Markdown on Rails
I have renamed the Markdown Template Handler project to Markdown on Rails. "Markdown Template Handler" was kinda dull and "Markdown on Rails" has a much better ring to it.
If you install the new renamed version you will need to change your config/environemt.rb file:
from this
require 'markdown_template' ActionView::Base.register_template_handler('md', MarkdownTemplate) ActionView::Base.register_template_handler('text', MarkdownTemplate) MarkdownTemplate::map_headings_down_by 0
to
require 'markdown_on_rails' ActionView::Base.register_template_handler('md', MarkdownOnRails) ActionView::Base.register_template_handler('text', MarkdownOnRails) MarkdownOnRails::map_headings_down_by 0
simple as that.
Convertible to Csv
Convertible to csv now has a project page.
I've also added some unit tests.
Convertible to csv allows you to specify that an ActiveRecord model should respond to a to_csv message. You can also specify if a header row of field names should be used and which fields (and their order) should be used in the csv output.
Markdown template handler for rails
Writing is all about content. Style and presentation is a separate concern. When I am writing, be it for the web or some documentation or whatever, I like to write pure text. Style and presentation comes later.
Markdown allows me to do that very effectively. I get to concentrate purely on the content of what I am writing and the semantics of the content (this is a heading, this is a list, this should be emphasised...)
For me, this desire to write plain text extends to writing content for web sites. Of course, html has it place but if I am writing a content page for a client's web site, I would much rather just write the content and not have the html markup of that content get in my way.
I solved this by writing a rails markdown template handler so I could write web site content in my rails apps in markdown.
These two images show a rails view (this site's about page) as html and markdown. In the markdown version, the content is free of the clutter of html tags.
The code is released under the MIT licence and can be found at the markdown_template_handler project home
acts_as_convertible_to_csv
update: convertible to csv now has a project page
A little ruby magic in the form of meta-programming allows you to define your models as being able to output data in csv format. The definition is a DSL style acts_as_convertible_to_csv with :header and :fields as valid specifications.
Here's how you would use it.
In your model declaration:
class Customer < ActiveRecord::Base acts_as_convertible_to_csv :header => true, :fields => %w(id firstname lastname email_address) end
The first row will contain fields names if you specify :header => true. You can specify which fields and the order in which they appear by using :fields. If you omit :fields, the field list will default to those returned by the human_readable method.
Getting the csv data from a collection of records:
Customer.find(:all).to_csv
You can pass a block:
Customer.find(:all).to_csv do |line| # write the line to a file or something end
You can also convert individual records to csv:
Customer.find(:first).to_csv
The code
class ActiveRecord::Base def self.acts_as_convertible_to_csv(*args) @@use_header_fields = false args.each do |param| if param.is_a? Hash if param.include? :header and param[:header] @@use_header_fields = true end if param.include? :fields and param[:fields] and param[:fields].is_a? Array class_eval <<-CEEND def self.csv_header %w(#{param[:fields].join(' ')}) end CEEND else class_eval <<-CEEND def self.csv_header field_names = Array.new self.content_columns.each do |column| field_names << column.name end field_names end CEEND end end end class_eval <<-CEEND def self.use_header_fields @@use_header_fields end def to_csv line = "" self.class.csv_header.each do |field_name| line = line + "," + self.send(field_name).to_s end # remove first ', ' line.gsub! /^,/, '' end CEEND end end # Allow a collection class to repond to to_csv class Array def to_csv(&block) data = Array.new # check to see if the objects in the array are capable of to_csv if self[0] and self[0].class.method_defined?(:to_csv) if self[0].class.use_header_fields data << self[0].class.csv_header.join(',') yield self[0].class.csv_header.join(',') if block_given? end each do |object| data << object.to_csv yield object.to_csv if block_given? end end data end end
