It’s nice to do some Ruby once in a while. Lately I’ve been involved in a Silverlight project and haven’t touched Ruby in 2 months (hence the lack of activity on my Ruby blog). However, I’m making a build script in Ruby.
I have run into a weird exception when using my favorite IO gem RIO (btw, Noobkit page got beaten with the ugly stick and parser needs a spanking).
Exception occur when calling rio(...).rmtree or rio(...).mkpath or rio(...).mkdir. It reads as follows: “wrong number of arguments (0 for 1)”. I know for a fact that these methods don’t take any arguments, but just for kicks, passing a single random argument results in an ironic “wrong number of arguments (1 for 0)”.
Basically the problem came down to the fact that RIO doesn’t like Rake. I can live without Rake, but not without RIO.
I looked all over and couldn’t find a bash script which could check and if missing install a list of gems. I had to hack my own up:
GEMS=( `cat gems.txt` ) # gems.txt has one gem name per line
for gem in "${GEMS[@]}"; do
found=`gem list $gem | grep -i $gem`
if [ "$found" != "" ]; then
parts=( $found )
echo "Found ${parts[0]} ${parts[1]}"
continue
fi
echo "---------------------------"
echo "Installing $gem"
sudo gem install $gem -y
echo "---------------------------"
done
Sometimes you want to focus on just one example in your RSpec file. I had to do this quite often in the last 3 days and I got tired of commenting out stuff back and forth. So, I give you “it_only” example.
module Spec
module DSL
module BehaviourEval
module ModuleMethods
def it(description = :__generate_description, opts = {}, &block)
return if @it_only_found
examples << Example.new(description, opts, &block)
end
# Same as +it+ only blocks all other examples making this the
# only example that would run.
def it_only(description = :__generate_description, opts = {}, &block)
@it_only_found = true
@examples = [Example.new(description, opts, &block)]
end
end
end
end
end
Drop this into your spec_helper.rb and now if you want to focus on a single example, just make it “it_only” instead of “it“.
I’ve recently started experimenting with using Ubuntu for my Ruby development. Some of the Gems like JSON and HPricot have extension compilation step which doesn’t work on a default Ubuntu installation.
Here are a few steps that you need to get it to work.
- Open up Synaptic Package Manager from System > Preferences
- Search for “ruby1.8-dev”, right click and check “Mark for installation”
- Search for “build-essential” and mark it for installation as well
- Click “Apply” in the toolbar
After the update is finished, you should be able to install Gems which require native extensions compilation.
If I were to be asked what is faster, regular sub string operation or a regular expression, I would without hesitation answer that sub string is.
This was the assumption that I approached a simple task with - stripping slashes from beginning and end of a string. Here’s the code:
path = path[1..-1] if path[0, 1] == '/' path = path[0..-2] if path[-1, 1] == '/'
I would naturally assume that the block above would be faster than path.gsub!(/^\/|\/$/, ''). But just in case, lets benchmark to be sure.
require 'benchmark'
original = '/hello/somewhat/long/path/here/'
max = 1_000_000
puts Benchmark.measure {
1.upto(max) do
path = original
path = path[1..-1] if path[0, 1] == '/'
path = path[0..-2] if path[-1, 1] == '/'
end
}
puts Benchmark.measure {
1.upto(max) do
path = original
path.gsub!(/^/|/$/, '')
end
}
# prints out
4.212000 0.000000 4.212000 ( 4.270000)
2.418000 0.000000 2.418000 ( 2.435000)
I don’t understand why, but gsub is 57% faster. I find it hard to believe that a few extra Ruby statements introduce so much overhead that it becomes slower than entire regular expressions engine. Anyone has any explanation for this?
Nested hashes could be used to represent tree structures. Here’s a code to to find a node by key and get all of its parents:
def hash_history(hash, desired_key, &block)
return false unless Hash === hash
hash.each_pair do |key, value|
if key == desired_key or hash_history(value, desired_key, &block)
yield(key, value)
return true
end
end
return false
end
hash = {
:level_1 => {
:level_2 => {
:level_3 => {
:search => 'test'
}
}
}
}
hash_history(hash, :search) { |key, value| puts key }
# prints out...
# search
# level_3
# level_2
# level_1
It would appear that RDoc Ruby parser in Ruby 1.8.6 has a bug which leads to attributes always being public.
Here’s the fix in parser_rb.rb:
...
def parse_attr(context, single, tk, comment)
args = parse_symbol_arg(1)
if args.size > 0
name = args[0]
rw = "R"
skip_tkspace(false)
tk = get_tk
if tk.kind_of? TkCOMMA
rw = "RW" if get_bool
else
unget_tk tk
end
att = Attr.new(get_tkread, name, rw, comment)
att.top_level = @top_level
att.visibility = context.visibility ######### NEW LINE ##########
read_documentation_modifiers(att, ATTR_MODIFIERS)
if att.document_self
context.add_attribute(att)
end
else
warn("'attr' ignored - looks like a variable")
end
end
...
def parse_attr_accessor(context, single, tk, comment)
args = parse_symbol_arg
read = get_tkread
rw = "?"
# If nodoc is given, don't document any of them
tmp = CodeObject.new
read_documentation_modifiers(tmp, ATTR_MODIFIERS)
return unless tmp.document_self
case tk.name
when "attr_reader" then rw = "R"
when "attr_writer" then rw = "W"
when "attr_accessor" then rw = "RW"
else
rw = @options.extra_accessor_flags[tk.name]
end
for name in args
att = Attr.new(get_tkread, name, rw, comment)
att.top_level = @top_level
att.visibility = context.visibility ######### NEW LINE ##########
context.add_attribute(att)
end
end
Here’s a quick 3rdRail overview in screenshots (flickr required).
CodeGear released 3rdRail this morning. A few highlights:
- Full Rails project support.
- Rails specific refactoring, ie renaming a method in a controller will update all references as well as link_to and rename the associated view file.
- Console with command completion.
- Integrated Gecko browser with request monitor, DOM source, CSS and JavaScript
You can watch a screen cast here and download a trial for Windows, Unix and OSX here.
Here’s a quick 3rdRail overview in screenshots (flickr required).
Noobkit is powered at its core by RDoc. Obviously it’s not real time, far from it. In fact, full refresh of Noobkit’s database which includes 26 Gems together with Rails and Ruby Core takes about 25 minutes on a single Core 2 Duo 2.4Ghz with 4GB ram box.
About 20 minutes of that time is taken by RDoc parsing the source code. This is what I’m currently working on making better. I have used this article as a starting point and wrote a client/server script on top of customized RDoc.
The end result is having 4 threads running across 2 boxes each parsing a separate source file fed to it by the server.
This being the first time I’ve worked with DRb or Ruby threads in general, one thing that kept annoying me was inability to break with Ctrl+C when using DRb.thread.join. I’ve devised a simple waiting loop instead:
def wait_for_it(&block)
return if block.nil?
loop do
sleep(0.1)
break unless yield
end
end
# ...do DRb magic...
# This will stall the current thread until DRb thread is finished.
wait_for_it { DRb.thread.alive? }
The same function can also be used to insure that all workers have finished their job before doing something else:
# When a worked is done, it's added back to the queue
wait_for_it { @available_workers.size != $workers.size }
RSpec is great:
it "should be its own root" do @node.root.should == @node end it "should add a child" do @node.children.size.should == 0 child = @klass.new @node.add_child(child) @node.children.size.should == 1 child.parent.should == @node child.root.should == @node end
Looks beautiful. However, look at line #2. What would the output be if that test fails? The message would be something like this: "expected #{@target.inspect} to the same as #{@expected.inspect}".
Inspecting an object like a tree node could result in multiple pages worth of data and the output basically becomes unreadable if there’s an array of objects.
This problem could easily be fixed with a custom expectation matcher.
module BeTheSameAsMatcher
class BeTheSameAs
def initialize(expected)
@expected = expected
end
def matches?(target)
@target = target
@target.eql?(@expected)
end
def failure_message
"expected <#{to_string(@target)}> to " +
"the same as <#{to_string(@expected)}>"
end
def negative_failure_message
"expected <#{to_string(@target)}> not to " +
"be the same as <#{to_string(@expected)}>"
end
# Returns string representation of an object.
def to_string(value)
# indicate a nil
if value.nil?
'nil'
end
# join arrays
if value.class == Array
return value.join(", ")
end
# otherwise return to_s() instead of inspect()
return value.to_s
end
end
# Actual matcher that is exposed.
def be_the_same_as(expected)
BeTheSameAs.new(expected)
end
end
As you can see, methods failure_message and negative_failure_message define our error messages. Instead of default inspect call, I’m using a custom to_string method which will either return a join for an Array or to_s for any other object.
To make this available in all of your specs, the module needs to be added in your `spec_helper.rb` like so:
require 'spec/be_the_same_as' Spec::Runner.configure do |config| config.include(BeTheSameAsMatcher) end
After this, we can change our line #2 from the original script to this:
it "should be its own root" do @node.root.should be_the_same_as(@node) end
After almost 8 months of working with Ruby, it still offers something new every day. Here’s a tricky part about overriding a constructor without any arguments:
class A
def initialize
@foo = 123
end
end
class B < A
def initialize(special_argument)
# This call will result in "wrong number of arguments (1 for 0)"
# exception because Ruby automatically passes all arguments.
super
# ... the same as ...
super(*args)
# Special case of calling super class' constructor without
# argument - you MUST include parentheses to indicate
# that you aren't passing any arguments.
super()
end
end
Obviously, I would’ve known that if I read the manual, but it would be too easy.
Converts a string to a URL style permalink.
class String
def to_permalink
s = self
s = Iconv.iconv('ascii//ignore//translit', 'utf-8', s).to_s
s.gsub!(/^W+|W+$/, '')
s.gsub!(/W+/, '-')
s.strip!
s.downcase!
s.squeeze!(' ')
s.gsub(/ +/, '-')
end
end
Unindenting a block of text involves finding a common minimum indent in every line and removing it. Here’s how I have done it. As with any text processing, the larger the text is, the slower it is.
def unindent_text(src)
indents = []
src.each_line do |line|
indents < < $1.length if not line =~ /^s*$/ and line =~ /^(s+)/
end
indent = indents.min
return src if indent == 0
lines = []
src.each_line do |line|
lines << line.gsub(/^s{#{indent}}/, '').gsub(/(rn|n|r)$/, '')
end
lines.join("n")
end
If you are suddenly getting in `latest_partials': undefined method `[]' for nil:NilClass error when calling some gem methods like Gem.latest_load_paths or Gem.path, check your gems folder (on a PC it’s /Ruby/lib/ruby/gems/1.8/gems). Make sure there are no foreign folders or files in there. Rubygems expects all folders to be in <name>-<version> format.



