Adventures in Ruby MVVM – Wrapping it up

More Adventures in MVVM Shout it

Source code for the RubyMVVM playground on BitBucket

It has been quite a while since I blogged.  Actually, I was just about to sit down to write this blog post about 7 weeks ago when my wife went into labor.  Soon afterwards, she gave birth to our second child — a son named Eli Hecker Genisio.  Needless to say, I have been neglecting blog for a while.

Previous to that, I had gotten to a place where I felt like I was ready to wrap up my experiments in using Ruby for ViewModels and Models.  Here is what I would have written:

What Worked, What Didn’t?

Using Ruby as the primary logic and model platform for MVVM (Presenter Model) style apps in WPF was a ton of fun.  It gave me the chance to learn Ruby pretty well (I’m still a hack, but who isn’t?) and got to use many of the more advanced topics such as meta programming and mix-ins.  My ViewModel code was succinct and my model code was even more trivial once I employed the HTTParty gem.  What was most fun (and likely my biggest take-away) was how wonderful writing tests using rspec was.  All of my tests were written using rspec and I think I might start writing my C# tests using rspec in the future. 

What didn’t work: The general coding workflow.  Since there is no tooling support for IronRuby in VisualStudio 2010, it became a disjoined development effort.  This was not completely bad, but things like debugging and deployment became difficult.  I cringe when I think about deploying ruby gems with a Silverlight app. In addition ,the integration between .Net and IronRuby at runtime still needs work.  For instance, defining anything but a trivial property in Ruby did not translate well into the .Net side.  Also, WPF had difficulty binding to ruby strings.  Some controls worked well, but some completely failed.  I ended up writing a ValueConverter in WPF that called ToString() on the Ruby string in order to bind properly.  Silverlight can’t even try to bind to a dynamic property, so that was even more difficult.

In all, I left feeling like the “goods” and “bads” canceled themselves out and I was stuck asking myself: “Why would I want to do this for real applications?”.  My answer is: “Until there is good tooling support for IronRuby in Visual Studio (don’t hold your breath), this story probably doesn’t have any legs”.

The Playground – A Twitter Search App

RubyMVVM_Twitter

With that, I’d like to document my playground in case anyone feels like running with it.  I created a WPF Twitter Search application (the 2010 hello world) and hoste d the code on BitBucket.  Once I built all of the mix-ins necessary to support automatic property notification and convention-based command creation (amongst others), I came up with a ViewModel which is (mostly) void of .Net integration issues that looks like this:

   1: class TwitterViewModel

   2:   include ViewModelSupport

   3:  

   4:   declare_notifiable :search_text, :responses

   5:  

   6:   def responses

   7:     @responses ||= NotifiableArray.new

   8:   end

   9:  

  10:   def execute_search

  11:     responses.clear

  12:  

  13:     Twitter.search(@search_text)["results"].each do |tweet| 

  14:       responses.push(TweetViewModel.new(tweet)) 

  15:     end

  16:   end

  17:  

  18:   map_commands

  19: end

1. Notice that the very first thing I do is add the ViewModelSupport mixin.  This provides the support for the magic that happens under the hood. 

2. Immediately following, I use the declare_notifiable directive.  This will create a property that will notify on change, using INotifyPropertyChanged in the .Net world.  This was included with the mix-in.

3. The responses method (read-only property in .Net) is a NotifiableArray. This is a Ruby array I built that will send collection changed events to .Net.

4. The execute_search method uses the execute_ convention to generate an ICommand property named search that can be bound to in the view.  This is what gets executed when the user presses the “Search Twitter” button.  It goes off to Twitter (Twitter.search to be explained later) to search for the text entered into the search_text property.  For each tweet that comes back, wrap it in a TweetViewModel (explained later) and put it into the responses collection.  The View will update appropriately.

5. The map_commands line is a class method that tells Ruby to go map any methods named execute_* to command properties (*) that can be bound to by the view.

As for the individual TweetViewModel, it looks like this:

   1: class TweetViewModel

   2:    include HashExposer

   3:    expose :text, :profile_image_url, :created_at, :from_user

   4: end

It uses HashExposer which is a mix-in I wrote that will find any items in the hash with the key declared with expose as a property.  It is a very simple wrapper to allow for binding in the WPF view down to the individual tweet properties found in the hash.  The hash that is passed in in the initializer is what these properties map to.

Finally, here is what the Twitter service looks like:

   1: class Twitter

   2:   include HTTParty

   3:   base_uri 'search.twitter.com'

   4:   format :json

   5:   

   6:   def self.search(query)

   7:     get('/search.json', :query =>; {:q => query})

   8:   end

   9:     

  10: end

By using the HTTParty gem, I get to define what Twitter.search(text) looks like in a minimal way.  The response of search is a parsed hash which I wrap with the TweetViewModel.  This is an AMAZINGLY simple way to consume web services in Ruby.  I was shocked to see how easy it was. 

That is everything on the Ruby side!  All of the application-specific ruby code has been shown in this blog post.  By using the ViewModelSupport mix-in (found in the source code), the rest of the .Net integration is abstracted away.  With this alone, I felt like I was successful. 

Next, I’d like to show the WPF side of the application.  This is what the view looks like:

   1: <UserControl

   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   4:     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

   5:     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

   6:     xmlns:local="clr-namespace:IronRubyMVVM"

   7:     xmlns:SampleData="clr-namespace:Expression.Blend.SampleData.Tweets"

   8:     mc:Ignorable="d"

   9:     x:Class="IronRubyMVVM.TwitterView"

  10:     x:Name="UserControl" d:DesignHeight="651.96" d:DesignWidth="669"

  11:     >

  12:  

  13:     <UserControl.Resources>

  14:         <SampleData:Tweets x:Key="Tweets" d:IsDataSource="True"/>

  15:         <local:StringConverter x:Key="ToString" />

  16:         <DataTemplate x:Key="responsesItemTemplate1">

  17:             <Border BorderThickness="2" CornerRadius="4" BorderBrush="Black" Margin="3">

  18:                 <Grid>

  19:                     <Grid.ColumnDefinitions>

  20:                         <ColumnDefinition Width="Auto" />

  21:                         <ColumnDefinition Width="*" />

  22:                     </Grid.ColumnDefinitions>

  23:                     <Grid.Background>

  24:                         <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">

  25:                             <GradientStop Color="#FFD1EBFB" Offset="0.694"/>

  26:                             <GradientStop Color="White" Offset="0.116"/>

  27:                         </LinearGradientBrush>

  28:                     </Grid.Background>

  29:                     <StackPanel Grid.Column="0" Margin="2">

  30:                         <Border BorderBrush="Black" BorderThickness="3" Height="100" Width="100" CornerRadius="5" Margin="3">

  31:                             <Image Source="{Binding profile_image_url, Converter={StaticResource ToString}}" Margin="7" />

  32:                         </Border>

  33:                         <TextBlock Text="{Binding from_user}" HorizontalAlignment="Center" />

  34:                         <TextBlock Text="{Binding created_at}" HorizontalAlignment="Center" />

  35:                     </StackPanel>

  36:                     <TextBlock Grid.Column="1" Text="{Binding text}" VerticalAlignment="Top" TextWrapping="Wrap" Margin="8,30,0,0" FontSize="18.667" />

  37:                 </Grid>

  38:             </Border>

  39:         </DataTemplate>

  40:         <ItemsPanelTemplate x:Key="ItemsPanelTemplate1">

  41:             <StackPanel IsItemsHost="True" />

  42:         </ItemsPanelTemplate>

  43:     </UserControl.Resources>

  44:     <UserControl.Background>

  45:         <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">

  46:             <GradientStop Color="#FFD6D6D6" Offset="0"/>

  47:             <GradientStop Color="White" Offset="1"/>

  48:         </LinearGradientBrush>

  49:     </UserControl.Background>

  50:  

  51:     <UserControl.DataContext>

  52:         <Binding Path="TwitterViewModel" Source="{StaticResource VMLocator}"/>

  53:     </UserControl.DataContext>

  54:  

  55:     <Grid x:Name="LayoutRoot">

  56:         <Grid.RowDefinitions>

  57:             <RowDefinition Height="Auto" />

  58:             <RowDefinition Height="*" />

  59:         </Grid.RowDefinitions>

  60:         <StackPanel Orientation="Horizontal" Grid.Row="0">

  61:             <TextBox Width="200" VerticalAlignment="Top" Text="{Binding search_text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="5,0" />

  62:             <Button Content="Search Twitter" VerticalAlignment="Top" Command="{Binding search}" />

  63:         </StackPanel>

  64:         <ItemsControl Grid.Row="1" ItemsSource="{Binding responses}" ItemTemplate="{DynamicResource responsesItemTemplate1}" d:DataContext="{Binding Source={StaticResource Tweets}}" HorizontalContentAlignment="Stretch" ItemsPanel="{DynamicResource ItemsPanelTemplate1}">

  65:             <ItemsControl.Template>

  66:                 <ControlTemplate>

  67:                     <ScrollViewer x:Name="ScrollViewer" Padding="{TemplateBinding Padding}">

  68:                         <ItemsPresenter />

  69:                     </ScrollViewer>

  70:                 </ControlTemplate>

  71:             </ItemsControl.Template>

  72:         </ItemsControl>

  73:     </Grid>

  74: </UserControl>

Just as in any MVVM application, all of the connections between the View and the ViewModel happen through binding.  This is no exception.

The only C# in this entire application are in the bootstrapping of the ruby classes (ViewModelLocator) and the StringConverter which helps bind the view to Ruby strings.

The code for these support classes can be found in the source code on BitBucket.

This puts to rest my Ruby-based MVVM experiments.  I learned a ton and I think there is a good foundation here for improvement if anyone wants to run with it.

For me, I will be moving on to some posts about Flex and ActionScript – another learning project for me to experiment with.  Stay tuned :)

Tags: ,

3 Responses to “Adventures in Ruby MVVM – Wrapping it up”

  1. Adventures in Ruby MVVM – Wrapping it up…

    Thank you for submitting this cool story – Trackback from DotNetShoutout…

  2. [...] This post was mentioned on Twitter by Dianne Marsh, Brian Genisio and D, Larry King. Larry King said: Adventures in Ruby MVVM – Wrapping it up « Brian Genisio's House … http://bit.ly/camYPb #SL #RIA [...]

  1. Bill Dines says:

    Good stuff. I know very little about Ruby but am investigating combining a Dynamic Language (Ruby or Python or both) with silverlight to allow non-programmers (or at least lower skilled programmers) to easily create simple silverlight UI’s to extend our product. I’d dismissed Ruby as there was no VS support, but I notice VS tools are now available in V1.1.1 so it is now a viable option.

    At the moment I am using a C# viewmodel (utilising binding to the C# vm’s indexer to provide dynamic capability) but had wondered about whether I could have a pure Dynamic Language VM. It seems I can, so these posts are very helpful :)

Leave a Reply