Hash#translate_keys_and_values
1 2 3 4 5 6 7 8 9 |
module CoreExtensions module Hash def translate_keys_and_values(&block) inject({}) {|a, (key, value)| a.update(block.call(key) => block.call(value))} end end end Hash.send(:include, CoreExtensions::Hash) |
It’s like symbolize_keys but a bit more flexible. It calls the block for every key and value in the hash. Of course you could tune it just do keys or values if you wanted. I do not want!
1 2 |
{"1" => "2"}.translate_keys_and_values(&:to_i) # => {1 => 2}
{1 => 2}.translate_keys_and_values {|x| x + 1 } # => {2 => 3}
|
Array#collapse
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
module CoreExtensions module Array def collapse self.inject([]) do |a, v| if existing = a.find {|o| o.eql?(v)} yield(existing, v) else a << v end a end end end end Array.send(:include, CoreExtensions::Array) |
Kind of handy for reporting, where you need to collapse line items into a summary. This example may make it clear:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class Item < Struct.new(:code, :quantity) def eql?(b) code == b.code end alias_method :==, :eql? def hash code.hash end def to_s "#{code} - #{quantity}" end end summary = [Item.new("a", 1), Item.new("a", 2), Item.new("b", 5)].collapse {|a, b| a.quantity += b.quantity} summary.collect(&:to_s) # => ["a - 3", "b - 5"] |
Facets patch
1 2 |
$ svn log svn://rubyforge.org/var/svn/facets/trunk -r 383 -v --------------------------------------------------------------------- |
r383 | transami | 2007-11-03 23:31:54 +1100 (Sat, 03 Nov 2007) | 2 lines Changed paths: M /trunk/lib/core/facets/hash/op.rb M /trunk/test/unit/hash/test_op.rb
Fixed bug in Hash#- Thanks to Xavier Shay.
1 2 3 4 |
--- ruby
require 'facets/hash/op'
{:a => 1, :b => 2, :c => 3} - [:a, :b] # => {:c => 3}
{:a => 1, :b => 2, :c => 3} - {:a => 1, :b => 99} # => {:b => 2, :c => 3}
|
It may be small, but it’s authentic. In the 2.0.5 gem.
Enumerable#inject is my favourite method
Combines the elements of enum by applying the block to an accumulator value (memo) and each element in turn. At each step, memo is set to the value returned by the block. – RubyDoc
It just doesn’t sound very helpful. I must confess, it isn’t something I use everyday. But I love that when you do want to use it, it is oh so sweet. The canonical example is summing the elements in an array:
1 |
[1,2,3].inject(0) {|sum, n| sum + n} # => 6 |
Probably the most used pattern is converting an array to a hash:
1 |
[1,2,3].inject({}) {|a, v| a.update(v => v * 2)} # => {1 => 2, 2 => 4, 3 => 6} |
Someone in IRC today wanted a nested send, something like @”string”.send(“trim.downcase”)
1 |
"trim.downcase".split('.').inject("HELLO ") {|obj, method| obj.send(method)} # => "hello" |
What do you inject?
Object#send_with_default
Avoid those pesky whiny nils! send_with_default won’t complain.
1 2 3 |
"hello".send_with_default(:length, 0) # => 5 nil.send_with_default(:length, 0) # => 0 "hello".send_with_default(:index, -1, 'e') # => 1 |
So sending parameters is a little clunky, but I don’t reckon’ you’ll want to do that much. Here is the extension you want:
1 2 3 4 5 6 7 8 9 |
module CoreExtensions module Object def send_with_default(method, default, *args) !self.nil? && self.respond_to?(method) ? self.send(*args.unshift(method)) : default end end end Object.send(:include, CoreExtensions::Object) |
YAML Tutorial
So you’ve got all these tasty ruby objects lying around in memory, and they’re going to be lost when your program ends. Such a tragic end. What’s a robot to do? Why, store them to disk in a language agnostic format, of course! Enter YAML, a language perfectly suited to the task, more so than it’s heavier bretheren, XML. YAML support comes built in to the ruby language, and it couldn’t be easier to use. Every object automagically gets a to_yaml method that returns a string containing appropriate YAML markup when you include the right file.
1 2 3 |
require 'yaml' # Assumed in future examples puts "hello".to_yaml |
Of course this works for any object, using some of that oh-so-sweet reflection. to_yaml recursively calls itself on all of your instance variables, and even knows how to handle complex structure like arrays and hashes. It even copes with cyclic references! How’s that for value?
1 2 3 4 5 6 7 8 9 10 |
class Square def initialize width, height @width = width @height = height @bonus = ['yo', {:msg => 'YAML 4TW'}] @me = self end end puts Square.new(2, 2).to_yaml |
Now that you’ve got a handy YAML string you can do whatever you like with it: write it to disk, store it in a database, email it to your cousin Benny. But Benny is going to spin out – how does he reproduce your shiny ruby objects? Thoughtfully, ruby makes it just about as easy to create an object from YAML markup – in other words to go the other way. The YAML::load method takes either a string or an IO object and gives you back an object, ready to use. It’s worth noting that the initialize method is not called on the new object – a fact that will become pertinent later.
1 2 3 |
serialized = Square.new(2, 2).to_yaml new_obj = YAML::load(serialized) puts new_obj.width |
Transience
The YAML serializer works in essentially the same manner as a sledgehammer. There’s no finesse – it will serialize all of your instance variables. Always. This is generally not a problem, but every now and then for reasons of space, security, beauty or public health you will have a transient variable that you really just don’t want to be serialized. There is no neat way in the supplied library to do this. You could override to_yaml and blank out the transient fields before you call super, but then you need to restore them afterwards. And what if those fields were calculated on initialization – how do you restore them when the object is deserialized?
Not to worry, our gallant hero (yours truly) has created a helper script that allows you to specify which fields are to be persisted in a declarative manner using a class attribute.
1 2 3 4 5 6 7 8 9 10 11 |
require 'rhnh/yaml_helper' # Assumed in future examples class Square persistent :width, :height def initialize width, height @width = width @height = height @me = self # @me will not be serialized end end |
The script also provides a post_deserialize hook that is called, not surprisingly, after deserialization. It essentially acts as initialize for deserialized objects. No setup is necessary to use this hook, it’s mere presence will attract enough attention.
1 2 3 4 5 6 7 |
class OnTheBall def post_deserialize puts "I'm awake!" end end YAML::load(OnTheBall.new.to_yaml) |
In closing
YAML is an excellent choice for serializing your Ruby objects. Its brevity and readability give it the edge over both XML and Marshal, and with the addition of YAML Helper it becomes more flexible as well.