tag:blogger.com,1999:blog-49702510692133651042024-03-14T14:07:02.538+11:00Ruby NasebyA revival of my blogging habits, and a new beginning playing with Ruby after years in .net.Davidhttp://www.blogger.com/profile/00291686202667292499noreply@blogger.comBlogger4125tag:blogger.com,1999:blog-4970251069213365104.post-81930845877614597452009-01-18T07:32:00.002+11:002009-01-18T07:44:19.277+11:00Generating CSV output in SinatraTo generate CSV output in Sinatra:<br /><br /><code style="font-family: courier new;"><pre>get '/tax/export/:financial_year_start' do<br /> tax = TaxReport.new( params[:financial_year_start] )<br /> headers "Content-Disposition" => "attachment;filename=tax#{tax.year_start}.csv",<br /> "Content-Type" => "application/octet-stream"<br /> result = ""<br /> tax.invoices.each do |inv|<br /> result << "#{inv.date.strftime("%d %b %y")}, #{inv.number}, #{inv.company}, #{inv.total_ex_gst}, #{inv.gst}, #{inv.total}"<br /> end<br />end</pre></code><br /><br /><p>Nothing particularly difficult about it, like the rest of Sinatra: it just works.</p>Davidhttp://www.blogger.com/profile/00291686202667292499noreply@blogger.com1tag:blogger.com,1999:blog-4970251069213365104.post-4731754594554115702008-11-26T10:36:00.003+11:002009-01-04T08:02:36.536+11:00Sinatra FrameworkMy current obsession in the Ruby world is <a href="http://sinatra.rubyforge.org/">Sinatra</a> + <a href="http://sequel.rubyforge.org/">Sequel</a>. Sinatra is a gorgeously light web framework sitting on top of <a href="http://rack.rubyforge.org/">Rack</a>. I've written my invoicing tool for work in Sinatra, using Sequel to talk to a <a href="http://sqlite-ruby.rubyforge.org/">SQLite</a> database, and <a href="http://jquery.com/">jQuery</a> for the fancy stuff.<br /><br />I once was a prototype/scriptaculous devotee, but jQuery has won me over for almost everything lately. It's better documented (or rather, by the time I jumped on board, the documentation for jQuery was better than my usual prototype references, noting that I was a prototype early adopter and docs were a bit thin on the ground. The current state of prototype docs may be much better).<br /><br />Anyway. The point here (you, blessed reader(s), may be forgiven for missing it) is my new iPhone, and this link: <a href="http://devver.net/blog/2008/11/building-a-iphone-web-app-in-under-50-lines-with-sinatra-and-iui/">Building an iPhone web app with Sinatra</a>. And also <a href="http://bacontoday.com/turbaconducken-turducken-wrapped-in-bacon/">Turbaconducken</a>, which is great. Next project is adapting my invoicer to work nicely on the iPhone, for shits and giggles.Davidhttp://www.blogger.com/profile/00291686202667292499noreply@blogger.comtag:blogger.com,1999:blog-4970251069213365104.post-66352094851474785362008-11-25T15:40:00.000+11:002008-11-25T15:49:51.642+11:00Nothing more of worthGoing through the wayback machine to my old blog, I've found nothing else that bears revival outside of the <a href="http://ruby-naseby.blogspot.com/2008/11/traits-in-ruby.html">traits proto-implementation</a>. Everything else was topical, and an attempt to monitor the conversation in my corner of the Ruby world. Most of the libraries I commented on no longer exist.<br /><br />So the slate is clean, and I'm blogging again. I seem to write in the same style as my former self, which is refreshing, although a bit disconcerting considering the changes in my life over the last 4 years.<br /><br />A tiny bit about me: my name is David Naseby; I'm a programmer; working in VB.Net professionally in the travel industry; working in Ruby for the last 8 years as a hobby or as backdoor projects or (briefly) professionally; learner of many languages; living in Sydney. This blog is about whatever takes my fancy, as blogs tend to be.Davidhttp://www.blogger.com/profile/00291686202667292499noreply@blogger.comtag:blogger.com,1999:blog-4970251069213365104.post-80925633982109641562008-11-25T14:20:00.000+11:002008-11-25T14:35:27.361+11:00Traits in Ruby<span style="font-style: italic;">(This post is copied from my old blog, which has disappeared from the internets by and large. So it contains thoughts that I'm not currently thinking, and which have largely been superseded in these modern times. This was published </span>Sat Feb 14, 2004<span style="font-style: italic;">.)<br /><br /></span><div><p>In a post to ruby-talk, Daniel Berger asked about any discussion of traits. Matz weighed in with the opinion that <i>" "module" in Ruby and "trait" are very similar idea. " </i> (Matz, ruby-talk 92858). Not content to let that rest, I looked up <a href="http://web.archive.org/web/20071008161120/http://www.iam.unibe.ch/%7Escg/Archive/Papers/Scha02bTraits.pdf">the original paper</a>, and found some food for thought. So I've worked up something of a Ruby implementation of Traits, as presented by the paper.</p> <p>Briefly, traits are proposed by the authors of the paper as a primitive unit of code reuse. Classes should be constructed by composing traits, and viewed normally as a receiver of messages, that is, relating normally to other classes. According to the paper, traits have the following properties:</p> <ul class="quote"><li>A trait provides a set of methods that implement behaviour.</li><li>A trait requires a set of methods that parameterize the provided behaviour.</li><li>Traits do not specify any state variables, and the methods provided by traits never directly access state variables.</li><li>Traits can be composed: trait composition is symmetric and conflicting methods are excluded from the composition.</li><li>Traits can be nested, but the nesting has no semantics for classes - nested traits are equivalent to flattened traits.</li></ul> <p class="quote">From <em>Traits: Composable Units of Behavior</em>, Nathanael Scharli, Stephane Ducasse, Oscar Nierstrasz and Andrew Black</p> <p>Superficially, it appears that modules do have these properties. But the paper does address the problems of mixin inheritence. These are total ordering: where mixins are added linearly to a class, one at a time, with later mixins overriding all named features of earlier mixins; a lack of control from the composite entity about the composition of mixins, occasionally requiring tricky code to obtain the final topology of the composed class; and fragile heirarchies: the proposition that a change to any module may cause silent changes and overrides to the composed class.</p> <p>The example implementation of traits is done in Squeak. Over the course of my recent work, I've become almost conversant in the language, so I'll attempt to translate their example into Ruby. The example given is the ol' shape drawing trick. I hope nobody mentions anything about rectangles, squares, chickens or eggs in this thing. The TDrawing trait, in Ruby, becomes:</p> <pre class="code">module TDrawing<br /> # requires #bounds, #draw_on<br /><br /> def draw; draw_on( World.canvas ); end<br /> def refresh; refresh_on( World.canvas ); end<br /><br /> def refresh_on( a_canvas )<br /> a_canvas.form.defer_updates_in( bounds ) do<br /> draw_on( a_canvas )<br /> end<br /> end<br />end<br /></pre> <p>Note that the requirements for the mixin are expressed only as comments. This is similar to the Enumerable mixin problem - it requires a hook into <i>#each</i> to get it going, but this is only expressed in docs, or at runtime when you call an Enumerable method on an Enumerable descended object without <i>#each</i>. There are other rules to traits, such as <i>glue methods</i>, that need to be respected, too. Is it possible to trivially extend Ruby to handle a trait style composition? I'll start with the trivial requirement: requirements.</p> <pre class="code">module Trait<br /> class RequiredMethodMissing < RuntimeError; end<br /><br /> def append_features( mod )<br /> @ids.each do | id |<br /> unless mod.instance_methods( true ).include?( id.to_s )<br /> raise Trait::RequiredMethodMissing, id.to_s<br /> end<br /> end<br /> super mod<br /> end<br /><br /> def required_methods( *ids )<br /> @ids = ids<br /> end<br />end<br /><br /># example usage:<br /><br />module T1<br /> extend Trait<br /> required_methods :blah<br />end<br /><br />class A<br /> def blah; end<br /> include T1<br />end<br /><br /></pre> <p>Normally, I'd probably consider testing to be sufficient, to pick up the lack of hook method, rather than this quasi-static typing hack. But I'm just trying to stay faithful to the paper here, and it does have some "hard-documentation" style elegance. I'm going to tiptoe around the static typing thing, and move on to the next requirements: <b>Class methods take precedence over Trait methods</b>, and <b>Trait methods take precedence over superclass methods</b>. Time to write up some unit tests:</p> <pre class="code">module Trait<br /> class RequiredMethodMissing < RuntimeError; end<br /><br /> def append_features( mod )<br /> @ids.each do | id |<br /> unless mod.instance_methods( true ).include?( id.to_s )<br /> raise Trait::RequiredMethodMissing, id.to_s<br /> end<br /> end<br /> super mod<br /> end<br /><br /> def extend_object( obj ) #callback on obj.extend( A_Trait )<br /> append_features( obj.class )<br /> end<br /><br /> def required_methods( *ids )<br /> @ids = ids<br /> end<br />end<br /><br />module T1<br /> extend Trait<br /> required_methods :to_s, :blah<br /> def bb; "T1#bb"; end<br />end<br /><br />class A<br /> def to_s; end<br /> def blah; end<br /> def bb; "A#bb"; end<br />end<br /><br />class B < A<br />end<br /><br />class C; end<br /><br />require 'test/unit'<br />class TC_Trait < Test::Unit::TestCase<br /> def test_requirements_met<br /> a = A.new<br /> assert_nothing_raised { a.extend( T1 ) }<br /> end<br /><br /> def test_requirements_not_met<br /> assert_raises( Trait::RequiredMethodMissing ) { C.new.extend( T1 ) }<br /> end<br /><br /> def test_existing_method_not_overridden<br /> a = A.new<br /> a.extend T1<br /> assert_equal "A#bb", a.bb<br /> end<br /><br /> def test_super_method_overridden<br /> b = B.new<br /> b.extend T1<br /> assert_equal "T1#bb", b.bb<br /> end<br />end<br /></pre> <p>They all pass, except for <i>test_super_method_overridden</i>. After a whole bunch of ugly hacking, I've come up with an ugly hack:</p> <pre class="code"># in Trait<br /> def append_features( mod )<br /> @ids.each do | id |<br /> unless mod.instance_methods( true ).include?( id.to_s )<br /> raise Trait::RequiredMethodMissing, id.to_s<br /> end<br /> end<br /> super_methods = mod.instance_methods( true ) - mod.instance_methods( false )<br /> self.instance_methods( false ).each do | meth |<br /> if super_methods.include?( meth )<br /> alt_name = "_trait_#{self.class}_#{meth}"<br /> send( :alias_method, alt_name, meth )<br /> mod.module_eval <<-END_EVAL<br /> def #{meth}( *args, &block )<br /> #{alt_name}( *args, &block )<br /> end<br /> END_EVAL<br /> end<br /> end<br /> super<br /> end<br /></pre> <p>The code needs some tidying, obviously, but it passes the tests. Moving on again. The paper describes <b>Nested Traits</b>, allowing Traits to be composed of other Traits. Nested traits are nasty, because if you include a Trait into another Trait, you need to avoid checking for requirements. Its only when a Trait is composed into a class should the sum of all requirements be added. I can test this with the following additions to the code:</p> <pre class="code">module T2<br /> extend Trait<br /> required_methods :t2hook<br /> include T1<br /> def cc; "T2#cc"; end<br />end<br /><br /></pre> <p>The code, in its current state, will throw a <i>RequiredMethodMissing</i> error immediately on running, with this line here. The following code makes it all run ok:</p> <pre class="code"># in module Trait<br /> def append_features( mod )<br /> @ids.each do | id |<br /> unless mod.instance_methods( true ).include?( id.to_s )<br /> raise Trait::RequiredMethodMissing, id.to_s<br /> end<br /> end if mod.is_a? Class<br />)<br /><br /> #... etc<br /></pre> <p>Passes the tests, although under the glaringly poor assumption that all Modules are Traits. I should really test for that foreseeable problem, but I'm going to ignore it for now. Next up: combining <i>required_methods</i>, and thinking of a better name for them. Add the following tests:</p> <pre class="code">class D; def t2hook; end; end<br /><br />#add to TC_Trait<br /> def test_composed_trait_requirements_not_met<br /> assert_raises( Trait::RequiredMethodMissing ) { D.new.extend( T2 ) }<br /> end<br /><br /> def test_composed_trait_requirements_met<br /> a = A.new<br /> a.class.send( :define_method, :t2hook, proc{} )<br /> assert_nothing_raised{ a.extend( T2 ) }<br /> end<br /></pre> <p>More hackery pokery makes this one pass. I need to get the existing Trait's <i>required_methods</i>, and add them to the composed Trait's, when the Traits are composed:</p> <pre class="code"># in module Trait<br /> def append_features( mod )<br /> unless mod.is_a? Class<br /> list_of_methods = ( [ mod.get_required_methods ] << @required_methods ).flatten<br /> mod.required_methods( *list_of_methods.compact )<br /> else<br /> @required_methods.each do | id |<br /> unless mod.instance_methods( true ).include?( id.to_s )<br /> raise Trait::RequiredMethodMissing, id.to_s<br /> end<br /> end<br /> end<br /> # ... etc<br /></pre> <p>The next hurdle is <b>Conflict Resolution</b>. By default, mixins in Ruby overwrite existing mixed in methods in the order of their inclusion. Traits are specified to allow aliases and method removal on inclusion: the Squeak code looks like:</p> <pre class="code">"dynamic aliasing:"<br />traits: { TCircle @ {#circleHash -> #hash. #circleEqual: -> #=} . TDrawing .<br /> TColor @ {#colorHash -> #hash. #colorEqual: -> #=} }<br />"dynamic removal:"<br />traits: { TCirle . TDrawing - {#=. #hash. #=} . TColor }<br /></pre> <p>Even though this is a major part of the whole Traits business, I'm going to completely ignore its implementation right now, because I've done enough to form a few opinions on the subject, and I'm getting tired. I'd probably have to alias and override Object#extend and Module#include, or (better) write Object#extend_trait and Module#include_trait, to implement Conflict Resolution. I'll leave this as an exercise for the reader.</p> <p>This hacked up implementation of Traits is a proof-of-concept that Traits can fit reasonably easily into Ruby, using straight Ruby without extensions. Without implementing anything, its possible to code in a Mixin-Oriented style in Ruby - composing classes from small, well-defined modules, paying attention to inclusion order and the names of methods across all of the mixins one includes. Developing Traits would make this informal, possibly difficult to scale process quite a bit easier - resolving clashes and ensuring, when a class is parsed, that it meets requirements for using a Mixin (failing early).</p> <p>Despite that, I'm still not sold on the Traits concept. I'm not sure that sprinkling methods over a distributed set of Mixins would be fun to maintain, on a large scale. The Squeak implementation took advantage of the browser: you only ever see one method at a time in Squeak (smalltalk) code, anyway, and if the browser integrates with Traits, you could view and edit the methods as "part" of the class anyway. With Ruby, without a great IDE, you'd need to flick across files and use a bit of imagination to get a good "view" of your composed class - to even see what methods it's composed of. I believe that traits lose a good deal of their appeal without the Smalltalk browser, or a great Trait-aware IDE.</p> <p>The final version of the Trait module, along with its tests, is presented below, if anyone cares to take it further. As a postscript, there's a bit of module magic code, far more mature than this, out there now: <a href="http://web.archive.org/web/20071008161120/http://blade.nagaokaut.ac.jp/%7Esinara/ruby/import-module/">import-module</a>, by Shin-ichiro HARA and <a href="http://web.archive.org/web/20071008161120/http://raa.ruby-lang.org/list.rhtml?name=aliasing_module">aliasing_module</a> by Nobu (which seems to handle the implementation of aliasing I neglected above).</p> <pre class="code">module Trait<br /> class RequiredMethodMissing < RuntimeError; end<br /><br /> def append_features( mod )<br /> unless mod.is_a? Class #not the best check, probably...<br /> list_of_methods = ( [ mod.get_required_methods ] << @required_methods ).flatten<br /> mod.required_methods( *list_of_methods.compact )<br /> else<br /> @required_methods.each do | id |<br /> unless mod.instance_methods( true ).include?( id.to_s )<br /> raise Trait::RequiredMethodMissing, id.to_s<br /> end<br /> end<br /> end<br /> super_methods = mod.instance_methods( true ) - mod.instance_methods( false )<br /> self.instance_methods( false ).each do | meth |<br /> if super_methods.include?( meth )<br /> alt_name = "_trait_#{self.class}_#{meth}"<br /> send( :alias_method, alt_name, meth )<br /> mod.module_eval <<-END_EVAL<br /> def #{meth}( *args, &block )<br /> #{alt_name}( *args, &block )<br /> end<br /> END_EVAL<br /> end<br /> end<br /> super<br /> end<br /><br /> def extend_object( obj )<br /> append_features( obj.class )<br /> end<br /><br /> def required_methods( *ids )<br /> @required_methods = ids<br /> end<br /><br /> def get_required_methods; @required_methods; end<br />end<br /><br />#tests<br />if $0 == __FILE__<br /><br /> module T1<br /> extend Trait<br /> required_methods :to_s, :blah<br /> def bb; "T1#bb"; end<br /> end<br /><br /> module T2<br /> extend Trait<br /> required_methods :t2hook<br /> include T1<br /> def cc; "T2#cc"; end<br /> end<br /><br /> class A<br /> def to_s; end<br /> def blah; end<br /> def bb; "A#bb"; end<br /> end<br /><br /> class B < A<br /> end<br /><br /> class C; end<br /><br /> class D; def t2hook; end; end;<br /><br /> require 'test/unit'<br /> class TC_Trait < Test::Unit::TestCase<br /> def test_requirements_met<br /> a = A.new<br /> assert_nothing_raised { a.extend( T1 ) }<br /> end<br /> <br /> def test_requirements_not_met<br /> assert_raises( Trait::RequiredMethodMissing ) { C.new.extend( T1 ) }<br /> end<br /> <br /> def test_existing_method_not_overridden<br /> a = A.new<br /> a.extend T1<br /> assert_equal "A#bb", a.bb<br /> end<br /> <br /> def test_super_method_overridden<br /> b = B.new<br /> b.extend T1<br /> assert_equal "T1#bb", b.bb<br /> end<br /> <br /> def test_composed_trait_requirements_not_met<br /> assert_raises( Trait::RequiredMethodMissing ) { D.new.extend( T2 ) }<br /> end<br /> <br /> def test_composed_trait_requirements_met<br /> a = A.new<br /> a.class.send( :define_method, :t2hook, proc{} )<br /> assert_nothing_raised{ a.extend( T2 ) }<br /> end<br /> end<br /><br />end<br /></pre></div>Davidhttp://www.blogger.com/profile/00291686202667292499noreply@blogger.com0