Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Why You Should Never Use an ORM

Why You Should Never Use an ORM

My presentation at RailsConf 2011 on thinking about your interface, your data and your code.

John Nunemaker

May 17, 2011
Tweet

More Decks by John Nunemaker

Other Decks in Programming

Transcript

  1. Ordered List
    @jnunemaker
    RailsConf Baltimore, MD
    May 17, 2011
    Why You Should Never
    Use an ORM

    View full-size slide

  2. You crazy man...

    View full-size slide


  3. Albert Einstein
    Any intelligent fool can
    make things bigger, more
    complex, and more
    violent.

    View full-size slide

  4. Violence
    My Path Of

    View full-size slide

  5. Three Points

    View full-size slide

  6. 1) Interface
    Think About Your

    View full-size slide

  7. 1) Interface
    Think About Your
    2) Data

    View full-size slide

  8. 1) Interface
    Think About Your
    2) Data
    3) Code

    View full-size slide

  9. Your Interface
    Think About

    View full-size slide


  10. John Nunemaker
    ORMs too often lead to
    interface laziness.

    View full-size slide

  11. site.memberships.create({
    :user_id => user.id,
    :owner => true,
    })

    View full-size slide

  12. site.add_owner(user)

    View full-size slide

  13. membership.update_attributes({
    :state => 1,
    })

    View full-size slide

  14. membership.update_attributes({
    :state => 'maximized',
    })

    View full-size slide

  15. user.maximize(site)

    View full-size slide

  16. content.to_json({
    'a crap ton': 'of options'
    })

    View full-size slide

  17. ContentPresenter.new(site, date, {
    :page => params['page'],
    }).to_json

    View full-size slide

  18. class ContentPresenter
    include BasePresenter
    def initialize(site, date, options={})
    @site, @date = site, date
    @options = options
    end
    def page # ...
    def per_page # ...
    def total # ...
    def path # ...
    def prev_path # ...
    def next_path # ...
    def next_page_path # ...
    def prev_page_path # ...
    def paginated # ...
    def content # ...

    View full-size slide

  19. def path # ...
    def prev_path # ...
    def next_path # ...
    def next_page_path # ...
    def prev_page_path # ...
    def paginated # ...
    def content # ...
    def as_json(options = nil)
    {
    'date' => @date.to_s,
    'prev_path' => prev_path,
    'next_path' => next_path,
    'content' => content,
    'page' => page,
    'per_page' => per_page,
    'total' => total,
    'prev_page_path' => prev_page_path,
    'next_page_path' => next_page_path,
    }
    end
    end

    View full-size slide

  20. Content.paginate({
    :conditions => {
    :site_id => site.id,
    :date => date,
    },
    :page => params['page'],
    :per_page => 15,
    })

    View full-size slide

  21. site.content_for_date(date, {
    :page => params['page'],
    })

    View full-size slide

  22. Your Interface
    Think About

    View full-size slide

  23. Your Data
    Think About

    View full-size slide


  24. John Nunemaker
    ORMs sometimes hide
    creative ways you can
    store and retrieve
    your data.

    View full-size slide

  25. Failure and Triumph
    A Story of

    View full-size slide

  26. {
    '_id' => 'site_id:2011-03-28',
    '/' => {'v' => 200, 't' => 'Home'},
    '/about/' => {'v' => 50, 't' => 'About'},
    '/foo/' => {'v' => 23, 't' => 'Foo!'},
    }

    View full-size slide

  27. You say heresy! I say use case...

    View full-size slide

  28. Content.get("#{site.id}:2011-03-28")

    View full-size slide

  29. write data
    ensure_index(site_id, date, path)

    View full-size slide

  30. read data
    ensure_index(site_id, date, views)

    View full-size slide

  31. {
    '_id' => BSON::ObjectId.new,
    'sid' => site_id,
    'p' => '/about/',
    'd' => '2011-03-28',
    'v' => 50,
    }

    View full-size slide

  32. NewContent.for_site_and_date(site, date)

    View full-size slide

  33. db['c.2011.5']

    View full-size slide

  34. db['c.2011.5']
    read index
    write index

    View full-size slide

  35. db['c.2011.4'] => db['c.2011.5']
    read index
    write index

    View full-size slide

  36. /this/is/my/really/long/
    and/descriptive/path/and/
    of/course/we/need/to/
    have/a/query/string?
    gibberish=true&yunolikesh
    orturls=false#DontForgetT
    oHashTagIt

    View full-size slide

  37. Zlib.crc32('...really long path...')
    762151429

    View full-size slide

  38. Counts
    Every bit

    View full-size slide

  39. Site.create(:state => 'enabled')

    View full-size slide

  40. class Site
    States = {
    'enabled' => 1,
    'disabled' => 2,
    }
    end
    Site.create({
    :state => Site::States['enabled'],
    })

    View full-size slide

  41. class Site
    def enabled?
    state == States['enabled']
    end
    def disabled?
    !enabled?
    end
    end

    View full-size slide

  42. require 'rsmaz'
    compressed = RSmaz.compress(str)
    decompressed = RSmaz.decompress(compressed)

    View full-size slide

  43. require 'zlib'
    compressed = Zlib::Deflate.deflate(str)
    decompressed = Zlib::Inflate.inflate(compressed)

    View full-size slide

  44. require 'msgpack'
    ids = [1,2,3,4,5,6,7,8,9,10]
    compressed = MessagePack.pack(ids)
    decompressed = MessagePack.unpack(compressed)

    View full-size slide

  45. class Stylesheet
    class RSmazCompressor
    def self.compress(str)
    RSmaz.compress(str)
    end
    def self.decompress(str)
    RSmaz.decompress(str)
    end
    end
    class ZlibCompressor
    def self.compress(str)
    Zlib::Deflate.deflate(str)
    end
    def self.decompress(str)
    Zlib::Deflate.inflate(str)
    end
    end

    View full-size slide

  46. Compressors = {
    1 => RSmazCompressor,
    2 => ZlibCompressor,
    }
    key :compressor_id, Integer
    key :contents, String
    validates_inclusion_of :compressor_id,
    :within => Compressors.keys
    def contents
    value = read_attribute(:contents)
    compressor.decompress(value)
    end
    def compressor
    Compressors[compressor_id]
    end
    end

    View full-size slide

  47. Partial Updates

    View full-size slide

  48. site.atomic_update_attributes(attrs)

    View full-size slide

  49. Unused Fields

    View full-size slide

  50. Memory/Disk/Network

    View full-size slide

  51. class SiteMode
    include Scam
    attr_accessor :name
    def password_required?
    id == 2
    end
    def item_cache?
    id == 1
    end
    end

    View full-size slide

  52. SiteMode.create({
    :id => 1,
    :name => 'live'
    })
    SiteMode.all
    # find by id or string id
    pp SiteMode.find(2)
    pp SiteMode.find('2')

    View full-size slide

  53. class Plan
    include Toy::Store
    store(:memory, {})
    attribute(:title, String)
    attribute(:price, Integer)
    end

    View full-size slide

  54. class Asset
    plugin Joint
    attachment :file
    def page_cache(version=nil)
    page_cache_original
    page_cache_version(version)
    end
    end

    View full-size slide

  55. current_user.sites.map do |site|
    Site.get(site['id'])
    end

    View full-size slide

  56. ids = current_user.sites.map do |site|
    site['id']
    end
    Site.all(:_id.in => ids)

    View full-size slide

  57. Go A Long Way
    A Little Ruby Can

    View full-size slide

  58. Move
    BigintMove

    View full-size slide

  59. Move
    BigintMove
    MakeYouWannaMove

    View full-size slide

  60. Move
    BigintMove
    MakeYouWannaMove
    DaMove

    View full-size slide

  61. Move
    BigintMove
    MakeYouWannaMove
    DaMove
    SmoothMove

    View full-size slide

  62. Move
    BigintMove
    MakeYouWannaMove
    DaMove
    SmoothMove
    NightMove

    View full-size slide

  63. Move
    BigintMove
    MakeYouWannaMove
    DaMove
    SmoothMove
    NightMove
    DanceMove

    View full-size slide

  64. Partition.create({
    :association => :moves,
    :model => Move,
    :first_id => 1,
    :writer => false,
    })

    View full-size slide

  65. Partition.for_writes.model.create(...)

    View full-size slide

  66. Partition.since(since_id, last_move_id).map do |p|
    send(p.association).since(since_id)
    end

    View full-size slide

  67. Your Data
    Think About

    View full-size slide

  68. Your Code
    Think About

    View full-size slide


  69. No code is faster
    than no code.

    View full-size slide

  70. $ bx ruby performance/reads.rb
    Collection#find_one
    0.270231008529663
    Site.first
    0.69925594329834

    View full-size slide

  71. /track - no
    /content - no
    /referrers - no
    /sites - yes
    /users - yes

    View full-size slide

  72. class Hit
    def site
    @site ||= begin
    query = {'_id' => site_id}
    options = {:fields => ['tz']}
    collection.find_one(query, options)
    end
    end
    end

    View full-size slide

  73. write code
    Don’t be afraid to

    View full-size slide

  74. read code
    Don’t be afraid to

    View full-size slide

  75. fail
    Don’t be afraid to

    View full-size slide

  76. Site.find(id)
    Site.create({
    :title => 'RailsTips',
    })
    site.update_attributes(attrs)
    site.to_json

    View full-size slide

  77. Your Code
    Think About

    View full-size slide


  78. Albert Einstein
    It takes a touch of genius
    —and a lot of courage—to
    move in the opposite
    direction.

    View full-size slide

  79. Ordered List
    Thank you!
    [email protected]
    John Nunemaker
    RailsConf Baltimore, MD
    May 17, 2011
    @jnunemaker

    View full-size slide