Saturday, April 15, 2006

Hacking IntelliJ for syntax highlighting

Currently, IntelliJ does not provide support for Ruby. This is quite unfortunate in my opinion since I'm primarily developing in Ruby these days. However, Obie and I found a trick/hack for providing custom syntax highlighting via regular expressions.

IntelliJ provides the ability to set up custom TODO markers by specifying a regular expression. When a custom TODO match is found the style attributes of the text matched are altered by the definition of the TODO. The available style attributes you can change include color, bold, italics, and many more. To create a custom TODO, click CTRL + ALT + S, then click R.

Creating patterns for Ruby range from simple to impossible. I have about 10 defined, such as coloring symbols red :[a-z][A-Za-z]*\w+ and making classes bold ([A-Z]+[a-z]+)+. Problems occur because any part of the match will be affected. Therefore, checking for ',true' can be problematic. It's impossible to highlight some things without highlighting a comma or parenthesis. Remember, this is just a hack after all.

The last part of the hack requires setting the block comment. IntelliJ only performs TODO matching on comments. Therefore, you have to set the begin block comment in your File Types (CTRL + ALT + S, K, Ruby) to something that you are willing to put at the top of every file you wish to be highlighted. We considered using 'require' but in the end decided on '#rh'. For block commenting to correctly work you will need to specify an end block comment also. For this we chose '#/rh'; however, this never appears in our code. Because we want the entire file to appear as a comment, you never need to include the end block comment text.

Hopefully the IntelliJ team is working to include Ruby support. Until then, I hope this hack can provide you some value. Here's what developing Ruby in IntelliJ looks like to me:

Thursday, April 13, 2006

Ruby Initialization Chain Module

The Ruby Initialization Chain module comes from my current project where I was using the Initialization Chain pattern. The Initialization Chain module allows you to call setter methods that set a value and return the instance. This chaining of method calls allows you to initialize an object with one line of code.
foo = Foo.new.set_bar("baz).set_cat("dog")
If you were to define the setter methods, they would all follow the same basic pattern
def bar=(value)
@bar = value
end

def set_bar(value)
bar=value
end
The Initialization Chain module assumes you will follow this pattern and removes the need to define them.

Code and Tests:
module InitializationChain
alias :pre_init_chain_method_missing :method_missing
def method_missing(sym, *args, &block)
if matches_set_and_writer_exists?(sym)
self.send convert_set_to_setter(sym), *args, &block
return self
end
pre_init_chain_method_missing(sym, *args, &block)
end

def matches_set_and_writer_exists?(sym)
matches_set?(sym) && writer_exists?(sym)
end

def match_regex
/^set_/
end

def convert_set_to_setter(sym)
"#{sym.to_s.sub(match_regex,'')}=".to_sym
end

def writer_exists?(sym)
self.respond_to?(convert_set_to_setter(sym))
end

def matches_set?(sym)
sym.to_s =~ match_regex
end
end

class Foo
include InitializationChain

attr_accessor :bar

def cat
@cat
end

def cat=(value)
@cat=value
end
end

require 'test/unit'

class InitializationChainTest < Test::Unit::TestCase
def test_matches_set
assert(Foo.new.matches_set?(:set_foo))
end

def test_writer_exists
assert(Foo.new.writer_exists?(:bar))
end

def test_add_dynamic_set_for_attr
foo = Foo.new.set_bar("baz")
assert_equal "baz", foo.bar
end

def test_add_dynamic_set_for_defined_setter
foo = Foo.new.set_cat("dog")
assert_equal "dog", foo.cat
end
end

Tuesday, April 04, 2006

Execute Ruby Tests in IntelliJ

IntelliJ is a great IDE. Unfortunatley, the support for Ruby is lacking. However, using the External Tools you can set up IntelliJ to run your Ruby TestUnit tests. (Jeremy Stell-Smith had the original idea, Obie Fernandez and I tweaked it a bit to get the following directions.)
  1. Open the settings dialog. (Ctrl + Alt + S)
  2. Click External Tools. (P)
  3. Add. (Alt + A)
  4. Set the name to whatever you want.
  5. put "ruby" in the Program text box.
  6. put "$FileDirRelativeToProjectRoot$\$FileName$" in the Parameters text box.
  7. put "$ProjectFileDir$" in the Working directory text box.
  8. click OK.
That's it. Your external program should now be listed in the context menu when you right click in the editing area of any ruby file.

NOTE: If you put a breakpoint in a ruby file and execute that file the breakpoint will be hit and you will drop into irb in the Run window of IntelliJ.

Additional suggestions.
  • Create a Keymap for quickly running the tests without the need for a context menu.
  • Create an external program that checks valid Ruby syntax. This can be done by adding an additional external program and setting the Program to "ruby", Parameters to "-c $FileName$", and the Working directory to "$FileDir$"

Rails schema cloning

While recently working through an Oracle schema cloning issue we were using rake db:test:clone to clone the database. We noticed that the cloning was not working correctly; therefore, we needed to go back to dumping the schema to development_structure.sql. This is easily done by changing config.active_record.schema_format = :sql in environment.rb.

However, even after making the above change in environment.rb, rake db:test:clone kept creating a schema.rb file and not a development_structure.sql. This surprised us, because running rake did create a development_structure.sql and did not create a schema.rb.

After a brief look at the list generated by rake -T, we noticed the rake test:db:clone_structure task. rake test:db:clone_structure does generate development_structure.sql.

This is easy enough to remember once you learn, but I'm not sure that it's the most intuitive option. To me, rake db:test:clone is very generic and should behave similar to rake by reading the environment.rb file and creating the expected file.

If it's truly necessary to have a task that generates specifically schema.rb or development_structure.sql they could be name more explicitly as test:db:clone_using_ruby and test:db:clone_using_sql, or something along those lines. Though, I have to wonder if anyone is actually setting their environment.rb to one option and running rake with the other option.

Migration Class and File naming

When running rake migrate I recently faced this output:

C:\work>rake migrate
(in C:/work)
rake aborted!
uninitialized constant CreateFooTable

(See full trace by running task with --trace)

C:\work>

Not the most informative message in the world. Running with trace didn't really help me out either. The error I made, on it's own, is fairly easy to spot, but I was also trying to load records of type Foo in this migration. Because I was trying to load records, I thought perhaps the error was occurring in the local definition of the Foo class.

Unfortunately, I missed the simple mistake. I had renamed the class to CreateFoosTable, because in Rails table names are plural.

Match the file name with the class name and everything works fine. A simple mistake that hopefully now we can both easily avoid in the future.