Ruby Stub Variations: OpenStruct
Ruby Stub Variations: TestStub
Ruby Stub Variations: Struct
Ruby Stub Variations: Stubba
Shortly after I begin to program full time using Ruby I was exposed to OpenStruct. I was quite pleased with the simplicty OpenStruct offered. OpenStruct allows you to create objects with their attributes initialized. For example:
require 'ostruct'OpenStruct provided value by allowing me to easily create stubs in one line of code. The code below represents a more robust version of the example test. OpenStruct helps reduce the fragility of the test by removing the dependency on the Select class.
record = OpenStruct.new(:name=>'jay')
puts record.name # -> jay
def test_values_are_appened_to_insert_statementDespite the value of OpenStruct, it also has some limitations. The first issue we encountered was that it did not respond as expected when the OpenStruct instance was frozen.
statement = Insert.into[:table_name].values do
OpenStruct.new(:to_sql=>'select column1, column2 from table2')
end
assert_equal "insert into table_name select column1, column2 from table2", statement.to_sql
end
Another issue with OpenStruct is that it does not respond as expected if you specify a value for an already defined method. My team noticed this behavior while using an OpenStruct instance in place of a ActiveRecord::Base subclass instance. The test required the OpenStruct instance to respond to
id
; however, the number that was returned did not match the value passed in the constructor. The following failing test should demonstrate the described issue.require 'test/unit'This issue stems from the current implementation of OpenStruct. OpenStruct stores it's attribute values in a Hash. When you attempt to access an attribute the call is actually delegated to method_missing. OpenStruct's implementation of method_missing returns the value from the Hash if it finds a matching key, otherwise nil. Unfortunately, method_missing will never be called if a method, such as id, is previously defined.
require 'ostruct'
class OpenStructTest < Test::Unit::TestCase
def test_id
assert_equal 2, OpenStruct.new(:id=>2).id
end
end
You may want to look at Builder::BlankSlate.
ReplyDeleteIt manages to hide all method names, an idea like this could allow you to make a better OpenStruct for your own use.
Jay, I ran into this issue today and after some digging I figured out you can simply access the desired values out of the internal hash. So to get the right id value in your example, you'd have to add a method such as
ReplyDeletedef id
@table[:id]
end
I find the interface not quite uniform:
ReplyDelete$ irb
irb(main):001:0> shallow_hash = {:first => 'a', :second => 'b'}
=> {:second=>"b", :first=>"a"}
irb(main):002:0> deep_hash = {:first => {:foo => 'bar1'}, :second => {:foo => 'bar2'}}
=> {:second=>{:foo=>"bar2"}, :first=>{:foo=>"bar1"}}
irb(main):003:0> require 'ostruct'
=> true
irb(main):005:0> shallow_ostruct = OpenStruct.new(shallow_hash)
=> #< OpenStruct second="b", first="a">
irb(main):007:0> shallow_ostruct.first
=> "a"
irb(main):008:0> shallow_ostruct.first.class
=> String
irb(main):009:0> deep_ostruct = OpenStruct.new(deep_hash)
=> #< OpenStruct second={:foo=>"bar2"}, first={:foo=>"bar1"}>
irb(main):011:0> deep_ostruct.first
=> {:foo=>"bar1"}
irb(main):012:0> deep_ostruct.first.class
=> Hash
Not sure if this is salvagable.
Stephan