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.
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 |