Watching changes 0

Posted by Matt Williams

The ruby2ruby gem allows you to peer into a class's methods and watch changes due to metaprogramming.

#define rubyfied 0

Posted by Matt Williams

Remember #defined FOO = 1; from your C days? Or public static int FOO = 1; from java? Well, I recently had reason to use this functionality within Ruby when I wanted to define a series of types which would each have a unique id used in a database, but I really didn't want to waste the space on a string.

I ended up with two methods, the first which takes a name and an enumerable, sets class variables for each element of an array (such as %w(The fat cat sat on a hat)) as well as a method for resolving an integer to a string.

Taking the following class:

1
2
3
4
5
6

require 'definer'
class Project
  define_class_types :states, %w(planning development testing production)
  define :myself, 1
end

you would have the following definitions:

  • Project.planning
  • Project.development
  • Project.testing
  • Project.prodution
  • Project.@@states(not accessible outside of class, but that's easily remedied)
  • Project.myself

Additionally, you would have a method Project.state_name(state) which would take a integer and return the string associated with that state. So, Project.state_name(0) or Project.state_name(Project.planning) would both return "planning".

In the define_class_types method, the name can be either pluralized or singular. The array of the types will be pluralized (as in states above) and the method for getting the string singularized (as in Project.state_name above.

There is one dependency -- it uses ActiveSupport's pluralize and singularize. The code follows below or you can download it here: definer.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

require 'active_support'

class Module
  def class_attr_reader(*symbols)
    symbols.each do |symbol|
      self.class.send(:define_method, symbol) do
        self.class_eval "@@#{symbol}"
      end
    end
  end

  def define(name,value)
    name = name.to_s
    class_variable_set("@@#{name}",value)
    class_attr_reader(name)
  end

  def add_class_types(name, types = [])
    name = name.to_s
    class_variable_set("@@#{name.pluralize}",types)
    types.each_with_index do |type, index|
      class_variable_set("@@#{type}", index)
      class_attr_reader(type)
    end
    self.class.send(:define_method,"#{name.singularize}_name") do |arg|
      self.class_eval "@@#{name.pluralize}[#{arg}]"
    end
  end
end

Order Matters! 2

Posted by Matt Williams

The order in which operations occur can often make a difference in Ruby:
1
2
3
4
5
6
7
8
9

$ irb
>> 5 * "a"
TypeError: String can't be coerced into Fixnum
  from (irb):1:in `*'
  from (irb):1
>> "a" * 5
=> "aaaaa"
>> 

The reason is quite simple — everything in Ruby is an object. The '*' behaves differently when applied to different objects (and, in fact, can be overridden). So, because the '*' method as implemented for Fixnum expects a number as its argument, and a String isn't a number, it complains. By the same token, the behaviour of '*' for String is for the string to repeat itself as many times as the argument specifies, it works.

Reconsidering Forth 0

Posted by Matt Williams

Recently I re-read Rick Cook's Wizard's Bane and The Wizardry Compiled. I strongly recommend that series to anyone who wishes to be a serious developer.

And I realized that I'd been missing out on a lot of fun lately. Ruby's become so dry and boring, it isn't funny. I really miss the days of having a function named corned_beef which uses such wondrous numbers as 65353. And stack based programming really is da-bomb! I remember all of the fun I used to have with it and have realized it's time to go back to it.

Gems Server

Posted by Matt Williams

Ever wonder how to view information about:
  • What ruby gems are installed?
  • Documentation for each gem?

rubygems has a built-in server which you can use to peruse the rdocs for a gem. To use it, type gem server at a prompt and then point your browser to href://localhost:8808.

It's pretty nifty.

Ruport Graphs

Posted by Matt Williams

ruport comes with an extension, ruport-util which adds, among other things, the ability to create graphs from reports. However, it's hardcoded to do line graphs. This is a code snippet which will (for png and jpg graphs, the others should follow) allow you to create other types -- it's just bar graphs, but the others would follow....

Frozen

Posted by Matt Williams

You only see what your eyes want to see
How can life be what you want it to be
You're frozen... -- Madonna

The way to ensure that your production rails apps don't get messed up should the provider update gems is to freeze them. Moreover, it's a good idea to embed the gems/plugins needed for a particular application; it makes distribution of different versions easier, not to mention that you know exactly with which resources your code is working.

To freeze rails, you can either do a rake rails:freeze:gems, which uses the installed gems to freeze, or rake rails:freeze:edge which freezes off of the rails subversion repository. The last one allows you to specify a version to which to freeze. When it's time to thaw, rake rails:unfreeze will remove the frozen rails installation.

To embed gems in your application, use Gems On Rails, which will place gems into vendor/gems.

Lots of Gems

Posted by Matt Williams

This isn't much of a tip, per se, but it's something I ran into today.

The way to having multiple rubygem repositories is to set the GEM_PATH environmental variable. This will allow you to use gems from more than one location -- something useful on shared servers.

Next, to figure out how to specify the version of rubygems you're using -- I've tried GEM_HOME and RUBY_LIB and my PATH, but it's not working properly on the shared host. More once I figure that one out.

map_by_method

Posted by Matt Williams

If you have a collection of objects, such as an array of students, and you want to quickly and easily capture their names, you can use: students.map_by_name which will return the names of the students as an array. This is a ruby gem and you can find more information about it at: map_by_method

Find or Create

Posted by Matt Williams

I found myself recently typing code like:
1
2

foo = Bar.find(:first, :conditions => {:name => "Fred"}) || Bar.new(:name => "Fred")
As it turns out, there's a rails method for this: Model.find_or_create_by_COLUMN, where COLUMN is the name of the column.

Ruby2Ruby

Posted by Matt Williams

Ruby2Ruby is a gem which allows you to look at the code of a class. To install do:
gem install ruby2ruby
(you may need to do a sudo in front of it)

Here's an example of its use:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

irb(main):001:0> require 'rubygems'
=> true
irb(main):002:0> require 'ruby2ruby'
=> true
irb(main):003:0> class Foo
irb(main):004:1> def fud
irb(main):005:2> puts "groo"
irb(main):006:2> end
irb(main):007:1> end
=> nil
irb(main):008:0> Ruby2Ruby.translate Foo
=> "class Foo < Object\n  def fud\n    puts(\"groo\")\n  end\nend"
irb(main):009:0> class Foo
irb(main):010:1> def fi
irb(main):011:2> puts "fo"
irb(main):012:2> end
irb(main):013:1> end
=> nil
irb(main):014:0> Ruby2Ruby.translate Foo
=> "class Foo < Object\n  def fi\n    puts(\"fo\")\n  end\n  \n  def fud\n    puts(\"groo\")\n  end\nend"
irb(main):015:0> class Foo
irb(main):016:1> def fud
irb(main):017:2> puts "Hello World!"
irb(main):018:2> end
irb(main):019:1> end
=> nil
irb(main):020:0> Ruby2Ruby.translate Foo
=> "class Foo < Object\n  def fi\n    puts(\"fo\")\n  end\n  \n  def fud\n    puts(\"Hello World!\")\n  end\nend"
irb(main):021:0>

Notice how it changes the class output. One thing I have found is that if you create instance variables outside of a method they won't show up in the output. Otherwise, it works quite well and is a good addition to a metaprogramming toolkit.

sub vs sub!

Posted by Matt Williams

sub does not do the same thing as sub!:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

irb(main):001:0> a="something"
=> "something"
irb(main):002:0> b=a.sub(/e/,"E")
=> "somEthing"
irb(main):003:0> b
=> "somEthing"
irb(main):004:0> a
=> "something"
irb(main):005:0> c=a.sub!(/thing/,"one")
=> "someone"
irb(main):006:0> c
=> "someone"
irb(main):007:0> a
=> "someone"
irb(main):008:0> b=a.sub(/thing/,"body")
=> "someone"
irb(main):009:0> b
=> "someone"
irb(main):010:0> c=a.sub!(/thing/,"body")
=> nil

They behave the same when there is a pattern matched and a change, however, when there is not a pattern matched they behave differently. This can cause grief as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13

irb(main):011:0> a="something"
=> "something"
irb(main):012:0> b=a.tr("n-za-m","a-z")
=> "fbzrguvat"
irb(main):013:0> b=a.sub(/thing/,"one").tr("n-za-m","a-z")
=> "fbzrbar"
irb(main):014:0> a
=> "something"
irb(main):015:0> b=a.sub!(/body/,"one").tr("n-za-m","a-z")
NoMethodError: undefined method `tr' for nil:NilClass
        from (irb):15
        from :0

This is the general behaviour of any method ending in !, so be careful when you are using them.

Super!

Posted by Matt Williams

One common mistake people make with ruby's method_missing is to leave out a super. This means that if you don't handle all the cases which could cause an invocation of method_missing then the exception will be lost. So, always include super, unless you have a good reason not to do so.

DateTime to Seconds since Epoch

Posted by Matt Williams

Here's how to convert ruby DateTIme objects to the seconds since the epoch:
1
2
3
4
5
6
7
8
9
10

>> d=DateTime.now
=> Thu, 03 Jan 2008 15:49:21 -0500
>> t=Time.parse(d.strftime("%c"))
=> Thu Jan 03 15:49:21 -0500 2008
>> t.to_f
=> 1199393361.0
>> t.to_f.floor
=> 1199393361
>> 

Hashing properties

Posted by Matt Williams

This is just a simple piece of code to create a hash of the values of properties of a Ruby object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

class Object                    # All objects will inherit this method
  def make_hash
    h={ }                       # start out with a "known quantity"
    self.instance_variables.each do |v| # for each instance variable do
      # Instance variables start with an @, so strip it
      v.sub!(/@/,"")

      # add our key/value pair to the array only if the variable has
      # been exposed
      h[v] = self.send(v) if self.respond_to? v
    end
    h                           # return the hash
  end
end

Ok, let's test it out:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

$ irb
>> load 'nifty.rb'
=> true
>> "a".instance_variables
=> []
>> "a".make_hash
=> {}
>> class Foo
>>   attr_reader :bar, :fud
>>   def initialize
>>     @bar="Hello World"
>>     @fud=123
>>     @groo = Time.now
>>   end
>> end
=> nil
>> f=Foo.new
=> #<Foo:0xb7857174 @groo=Wed Jan 02 10:20:36 -0500 2008, @fud=123, @bar="Hello World">
>> f.instance_variables
=> ["@groo", "@fud", "@bar"]
>> f.make_hash
=> {"bar"=>"Hello World", "fud"=>123}
>> quit

Note that although Foo has three instance variables, only two of those have been exposed (the attr_reader line).