The unbearable flexibility of schemalesness

Everybody knows composition > inheritance, right? Well, this concept
becomes truly powerful when you combine the DataMapper pattern with a
schemaless datastore. In the gist below I demonstrate using MongoMapper’s plugin system to
compose objects, in place of pure inheritance, which generally ends up
as Single Collection/Table Inheritance. Nobody loves STI, and plugins
allow for ruby style multi-inheritance in your models, as you can
still pick an over-arching hierarchy of classes, but mix in various
fields and their related methods.

I think this is the real power of NoSQL: not “web-scale”, which people
have been doing with SQL for years, but instead the flexibility that
comes from defining your data type once, in your model, with the full
power of ruby. I was skeptical about key-value stores as a default
storage engine for web apps, until very recently, and still think
MySQL is the right answer for many people and projects. But my most
recent client project uses MongoMapper, and as we started to refactor
and DRY up the models, I finally saw how truly useful not being bound
to a datastore’s schema is. Migrations? Yes, you don’t get AR migrations. The ‘proper’
migrations, which only affect schema, are unneeded anyways, in a
DataMapper. And data migrations are done wrong 99% of the time
anyways, imo; pretending they are instantaneous is a mistake. In
either AR or MM you should handle you data migrations more robustly,
via code switches and async tasks to transform the data. If I get a
chance to make this a talk, I may explore that more fully.

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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 
#rvm use 1.9.2
#brew install mongo
#gem install mongo_mapper
#gem install bson_ext

require ‘rubygems’
require ‘mongo_mapper’

MongoMapper.database = ‘mm_demo’

module MMDemo
  module HasDoorsAndWindows
    def self.configure(model)
      model.key :doors, Integer, :default => 0
      model.key :windows, Integer, :default => 0
      model.key :sun_roof, Boolean, :default => false
    end
    
    module InstanceMethods
      def enterable?
        doors > 0
      end
      
      def well_lit?
        windows >= (doors * 5)
      end
    end
  end
end


class House
  include MongoMapper::Document
  
  plugin MMDemo::HasDoorsAndWindows
end

class Vehicle
  include MongoMapper::Document
  
  key :wheels, Integer
end

class Motorcyle < Vehicle
end

class Car < Vehicle
  plugin MMDemo::HasDoorsAndWindows
  def well_lit?
    sunroof?
  end
end

require ‘minitest/autorun’

describe “a House” do
  it “has doors” do
    House.new(:doors => 2).doors.must_equal 2
  end
  
  it “has windows” do
    House.new(:doors => 7).doors.must_equal 7
  end
  
  it “is enterable if there are any doors” do
    House.new(:doors => 0).enterable?.must_equal false
    House.new(:doors => 4).enterable?.must_equal true
  end
  
  it “is enterable if there are any doors” do
    House.new(:doors => 0).enterable?.must_equal false
    House.new(:doors => 4).enterable?.must_equal true
  end
  
  it “is well lit if there are 5x as many windows as doors” do
    House.new(:doors => 1, :windows => 5).well_lit?.must_equal true
    House.new(:doors => 1, :windows => 4).well_lit?.must_equal false
  end
end

describe “a Car” do
  it “is well lit if it has a sunroof” do
    Car.new(:sunroof => true).well_lit?.must_equal true
    Car.new(:sunroof => false).well_lit?.must_equal false
  end
end

Posted via email from a timocracy of one | Comment »

Notes