Recently in Ruby Category

Array#natural_sort

| | Comments (0)

Sitting in omnifocus for ~4 years...

require 'fileutils'

20.times do |n|
  FileUtils.touch "f#{n}.txt"
end

p Dir["f*.txt"].sort
# => ["f0.txt", "f1.txt", "f10.txt", "f11.txt", ..., "f2.txt", "f3.txt", ...]

class Array
  def human_sort
    sort_by { |item| item.to_s.split(/(\d+)/).map { |e| [e.to_i, e] } }
  end
end

p Dir["f*.txt"].human_sort
# => ["f0.txt", "f1.txt", "f2.txt", "f3.txt", "f4.txt", "f5.txt", "f6.txt", ...]

Hoe is a rake/rubygems helper for project Rakefiles. It helps you manage and maintain, and release your project and includes a dynamic plug-in system allowing for easy extensibility. Hoe ships with plug-ins for all your usual project tasks including rdoc generation, testing, packaging, and deployment.

See class rdoc for help. Hint: ri Hoe or any of the plugins listed below.

For extra goodness, see: http://seattlerb.rubyforge.org/hoe/Hoe.pdf

Changes:

2.13.0 / 2012-01-23

  • 3 minor enhancements:

    • Added :dcov task so you can easily check documentation coverage.
    • Added Rake monkeypatch so that Task#clear will clear comments. (github)
    • Added coverage sorting and added tmp/isolate to rcov flags
  • 2 bug fixes:

    • Quelled 1.9.3 warning. (erikh)
    • rcov plugin should invoke isolate task if isolate plugin is being used.
  • https://github.com/seattlerb/hoe

Enumerable#uniq_by

| | Comments (1)

Random code sitting around waiting to be blogged...

module Enumerable
  def uniq_by
    r, s = [], {}
    each do |e|
      v = yield(e)
      next if s[v]
      r << e
      s[v] = true
    end
    r
  end
end

assert_nothing_tested

| | Comments (1)

Check this rails test out:

def test_remove_column_with_multi_column_index
  ActiveRecord::Base.connection.create_table(:hats) do |table|
    table.column :hat_name, :string, :limit => 100
    table.column :hat_size, :integer
    table.column :hat_style, :string, :limit => 100
  end
  ActiveRecord::Base.connection.add_index "hats", ["hat_style", "hat_size"], :unique => true

  assert_nothing_raised { Person.connection.remove_column("hats", "hat_size") }
ensure
  ActiveRecord::Base.connection.drop_table(:hats)
end

The file this test came from is chock full of tests written just like this one. What exactly is it testing? The test name implies that it is testing removecolumn when there is a multi-column index. Does the test ensure that removecolumn

Better yet, here's how to make it pass:

def remove_column(*)
end

Tada! No exceptions raised!

This is exactly why minitest doesn't have assert_nothing_raised. You wind up with file after file of useless junk tests.

tenderlove recently converted the rails tests from test/unit to minitest and had to get these tests working since they were all calling assert_nothing_raised. Here is his implementation:

def assert_nothing_raised(*)
  yield
end

Certainly easier to do this than to go through all the tests and remove the call (never mind adding real assertions to make sure something is actually being tested).

Seattle.rb Logo

I'm incredibly proud to announce Seattle.rb's 10 Year anniversary party!

Seattle's world-famous Ruby Brigade is 10 years old in February! Come celebrate with us at Substantial with drink, food, music and more goodies TBA. Plus, you'll get a snazzy t-shirt from PeepCode.

Space is limited, so register now!

Simulating .PHONY in Rake

| | Comments (0)

Makefiles have a construct called .PHONY. You declare your task a non-filesystem based task like so:

.PHONY: mytask

and then make knows that it shouldn't force your task to run just because "mytask" doesn't exist in the filesystem.

Rake doesn't bother with such nonsense. It doesn't assume that all tasks map to the filesystem. That's great. What's not great is that it does assume that if your file task has non-file-task dependencies, that they should probably rebuild even if those tasks have run. The problem code looks like:

def out_of_date?(stamp) # on FileTask
  @prerequisites.any? { |n| application[n, @scope].timestamp > stamp}
end

In my case, I was trying to intelligently hook up some generated files to isolate so they could access their compiler:

file "lib/ruby18_parser.rb" => :isolate
file "lib/ruby19_parser.rb" => :isolate

The problem is that :isolate is a regular task, and they calculate their dependencies like so:

def timestamp # on Task
  prerequisite_tasks.collect { |pre| pre.timestamp }.max || Time.now
end

:isolate doesn't have any prerequisites, so it falls back to Time.now. This is wrong in my case, as it forces my parser files to regenerate every time.

What I need is a prerequisite that fixes this with a timestamp earlier than my files:

def (task(:phony)).timestamp # omg I love/hate this grammar construct
  Time.at 0
end

task :isolate => :phony

and everything is happy.

I'm going to officially ask that :phony gets added to rake so others can be spared this pain.

Reverse Sort

| | Comments (0)

It's fairly common knowledge that you can sort and reverse a collection in one pass by using sort_by with a negated number:

tally = [["a", 2], ["b", 1], ["c", 2]]

p tally.sort_by { |(str, num)| [-num, str] }
# => [["a", 2], ["c", 2], ["b", 1]]

but what happens if you need to reverse something like strings? Strings don't respond to unary minus, so you can't do this:

tally.sort_by { |(str, num)| [-str, num] } rescue nil
# => undefined method `-@' for "a":String (NoMethodError)

What you want to do is make this behavior dynamic and available everywhere:

module Reversed
  def <=> target
    -super
  end
end

class Object
  def -@
    self.dup.extend Reversed
  end
end

p tally.sort_by { |(str, num)| [-str, num] }
# => [["c", 2], ["b", 1], ["a", 2]]

What would be really special is if Ruby allowed you to apply modules multiple times so that -(-a) == a.

Check out the time it takes just to start up some of these systems:

    # # of iterations = 1000
    #                                user     system      total        real
    # true                       0.030000   0.180000   1.780000 (  2.698916)
    # ruby -e 0                  0.040000   0.170000   4.770000 (  6.149741)
    # ruby -e 'eval "1 + 1"'     0.030000   0.180000   6.030000 (  6.811871)
    # csi -e 0                   0.050000   0.200000   5.720000 (  8.098138)
    # perl -e 0                  0.050000   0.200000   6.410000 (  8.193336)
    # csi -e eval 1+1 (scheme)   0.050000   0.230000   7.060000 (  8.663545)
    # perl -e 'eval "1 + 1"'     0.040000   0.200000   7.580000 (  8.552224)
    # ruby193 -e 0               0.050000   0.210000  14.700000 ( 15.981277)
    # ruby193 -e 'eval "1 + 1"'  0.060000   0.220000  14.760000 ( 16.010711)
    # python -c 0                0.060000   0.280000  46.230000 ( 49.630480)
    # python -c 'eval("1 + 1")'  0.060000   0.250000  47.080000 ( 49.251939)

What's up with python? Still using getc to load all your core libraries? dtruss doesn't seem to think so but obviously something is going on.

(I think something is wrong with my build of ruby 1.9.3, I'll look into it)

From:

    require 'benchmark'

    MAX = (ARGV.shift || 1_000).to_i

    puts "# of iterations = #{MAX}"
    Benchmark::bm(25) do |x|
      def x.go cmd, name=cmd
        report name do
          for i in 0..MAX do
            system cmd
          end
        end
      end

      ruby18 = "~/.multiruby/install/1.8.7-p330/bin/ruby"
      ruby18 = "ruby"
      ruby19 = "~/.multiruby/install/1.9.3-p0/bin/ruby --disable-gems"
      csi_code = "csi -e '(eval (with-input-from-string \"(+ 1 1)\" read))'"

      x.go "true"
      x.go "#{ruby18} -e 0",                "ruby187 -e 0"
      x.go "#{ruby18} -e 'eval \"1 + 1\"'", "ruby187 -e 'eval \"1 + 1\"'"
      x.go "csi -e 0"
      x.go "perl -e 0"
      x.go csi_code,                        "csi -e eval 1+1 (scheme)"
      x.go "perl -e 'eval \"1 + 1\"'"
      x.go "#{ruby19} -e 0",                "ruby193 -e 0"
      x.go "#{ruby19} -e 'eval \"1 + 1\"'", "ruby193 -e 'eval \"1 + 1\"'"
      x.go "python -c 0"
      x.go "python -c 'eval(\"1 + 1\")'"
    end

Hoe is a rake/rubygems helper for project Rakefiles. It helps you manage and maintain, and release your project and includes a dynamic plug-in system allowing for easy extensibility. Hoe ships with plug-ins for all your usual project tasks including rdoc generation, testing, packaging, and deployment.

See class rdoc for help. Hint: ri Hoe or any of the plugins listed below.

For extra goodness, see: http://seattlerb.rubyforge.org/hoe/Hoe.pdf

Changes:

2.12.5 / 2011-12-19

Ruby Classes are Open, Damnit

| | Comments (0)

Ruby Classes are Open. Use it to your advantage. Ruby's Date.parse changed between 1.8 and 1.9 so that European date formats are preferred over US date formats. I've seen a lot of American rubyists whine about this. It took about 5 minutes looking at Date code to figure out that the solution is trivial:

#!/usr/bin/ruby -w

require 'date'

class Date
  class << self
    alias :_temp_us  :_parse_us
    alias :_parse_us :_parse_eu
    alias :_parse_eu :_temp_us
  end
end if RUBY_VERSION =~ /1\.9/

puts Date.parse("10/4/2010") # => 2010-10-04

Recently, Jeremy Evans' released ruby-american_date to address this issue. You should probably use his solution over mine. Mine is just here as an illustration that it really doesn't take much time or effort to address your whining.

About this Archive

This page is a archive of recent entries in the Ruby category.

Rubinius is the previous category.

RubyHitSquad is the next category.

Find recent content on the main index or look in the archives to find all content.

Pages

Powered by Movable Type 4.32-en