Download pdf - A Tour of Wyriki

Transcript
Page 1: A Tour of Wyriki

Enable Labs @mark_menard

A Tour of Wyriki

Mark Menard

Ruby Nation!June 7, 2014

@mark_menard !Enable Labs

Page 2: A Tour of Wyriki

Enable Labs @mark_menard

Jim Weirich

Page 3: A Tour of Wyriki

Enable Labs @mark_menardhttp://www.flickr.com/photos/langalex

Page 4: A Tour of Wyriki

Enable Labs @mark_menard

TDD

Page 5: A Tour of Wyriki

Enable Labs @mark_menard

Yea… whatever…

Page 6: A Tour of Wyriki

Enable Labs @mark_menard

What is Wyriki?

Page 7: A Tour of Wyriki

Enable Labs @mark_menard

The Wyriki Domain

Page

Wiki

*

1

Create Wiki

Create Page

Update Page

Create User

Loged In User

Anonymous User

Page 8: A Tour of Wyriki

Enable Labs @mark_menard

Business Logic

ActiveRecord

ActionPack

Controllers

MySQL MongoDB PostgreSQL

Redis

Sidekiq

Resque

What was Jim trying to accomplish?

Page 9: A Tour of Wyriki

Enable Labs @mark_menard

Testing

Page 10: A Tour of Wyriki

Enable Labs @mark_menard

Why?!!

When?

Page 11: A Tour of Wyriki

Enable Labs @mark_menard

Typical Rails

Page 12: A Tour of Wyriki

Enable Labs @mark_menard

Create Page

Loged In User

Action Controller ::

Base

ActiveRecord :: Base

Pages Controller

Application Controller

Page

Wiki

Page 13: A Tour of Wyriki

Enable Labs @mark_menard

Create Page

Loged In User

Action Controller ::

Base

ActiveRecord :: Base

Pages Controller

Application Controller

Page

Wiki

Page 14: A Tour of Wyriki

Enable Labs @mark_menard

Create Page

Loged In User

Action Controller ::

Base

ActiveRecord :: Base

Pages Controller

Application Controller

Page

Wiki

Page 15: A Tour of Wyriki

Enable Labs @mark_menard

# app/controllers/pages_controller.rb def create @wiki = Wiki.find(params[:wiki_id]) @page = @wiki.pages.new(page_params) if @page.save redirect_to [@wiki, @page], notice: "#{@page.name} created" else render :new end end

Page 16: A Tour of Wyriki

Enable Labs @mark_menard

Wyriki Style

Page 17: A Tour of Wyriki

Enable Labs @mark_menard

Runnersclass Runner attr_reader :context ! def initialize(context) @callbacks = NamedCallbacks.new @context = context yield(@callbacks) if block_given? end ! def repo context.repo end ! def success(*args) callback(:success, *args) end ! def failure(*args) callback(:failure, *args) end ! def callback(name, *args) @callbacks.call(name, *args) args end end

class Model < SimpleDelegator include BlockActiveRecord ! def data datum = self while datum.biz? datum = datum.__getobj__ end datum end ! def ==(other) if other.respond_to?(:data) data == other.data else data == other end end ! def biz? true end ! def class data.class end ! def self.wrap(model) model ? new(model) : nil end ! def self.wraps(models) models.map { |model| wrap(model) } end !end

Business Models

Repositories

module UserMethods def all_users Biz::User.wraps(User.all_users) end ! def new_user(attrs={}) Biz::User.wrap(User.new(attrs)) end ! def find_user(user_id) Biz::User.wrap(User.find(user_id)) end ! def save_user(user) user.data.save end ! def update_user(user, attrs) user.data.update_attributes(attrs) end ! def destroy_user(user_id) User.destroy(user_id) end end

Page 18: A Tour of Wyriki

Enable Labs @mark_menard

Action Controller ::

Base

ActiveRecord :: Base

Page Controller

Application Controller

Page

Wiki

Create Page Runner

<<protocol>>Repo

Repo

<<protocol>>context

<<protocol>>Biz Page

<<protocol>>Biz Wiki Biz::Wiki

Biz::Page

<<protocol>>Wiki Data

<<protocol>>Page Data<<wraps>>

<<wraps>>

Page 19: A Tour of Wyriki

Enable Labs @mark_menard

Action Controller ::

Base

ActiveRecord :: Base

Page Controller

Application Controller

Some Model

Runner

Repo

Biz Model

<< wraps >><< gets and saves

stuff >>

Page 20: A Tour of Wyriki

Enable Labs @mark_menard

Action Controller ::

Base

ActiveRecord :: Base

Page Controller

Application Controller

Some Model

Runner

Repo

Biz Model

<< wraps >><< gets and saves

stuff >>

Page 21: A Tour of Wyriki

Enable Labs @mark_menard

Page 22: A Tour of Wyriki

Enable Labs @mark_menard

Runners

Page 23: A Tour of Wyriki

Enable Labs @mark_menard

Page 24: A Tour of Wyriki

Enable Labs @mark_menard

Action Controller ::

Base

ActiveRecord :: Base

Page Controller

Application Controller

Page

Wiki

Create Page Runner

<<protocol>>context

Rails

Not Rails

Create Page

Loged In User

Page 25: A Tour of Wyriki

Enable Labs @mark_menard

This is the !Domain

Page 26: A Tour of Wyriki

Enable Labs @mark_menard

This is the !Domain

This is Rails

Page 27: A Tour of Wyriki

Enable Labs @mark_menard

This is our Context.

Page 28: A Tour of Wyriki

Enable Labs @mark_menard

# app/controllers/page_controller.rb def create run(Create, params[:wiki_id], page_params) do |on| on.success { |page| redirect_to [page.wiki, page], notice: "#{page.name} created" } on.failure { |wiki, page| render :new } end end

Page 29: A Tour of Wyriki

Enable Labs @mark_menard

# app/controllers/page_controller.rb def create run(Create, params[:wiki_id], page_params) do |on| on.success { |page| redirect_to [page.wiki, page], notice: "#{page.name} created" } on.failure { |wiki, page| render :new } end end

Page 30: A Tour of Wyriki

Enable Labs @mark_menard

# app/controllers/page_controller.rb def create run(Create, params[:wiki_id], page_params) do |on| on.success { |page| redirect_to [page.wiki, page], notice: "#{page.name} created" } on.failure { |wiki, page| render :new } end end

Page 31: A Tour of Wyriki

Enable Labs @mark_menard

# app/runners/page_runners.rb class Create < Runner def run(wiki_id, page_params) wiki = Wiki.find(params[:wiki_id]) page = wiki.pages.new(page_params) if page.save success(page) else failure(wiki, page) end end end

Page 32: A Tour of Wyriki

Enable Labs @mark_menard

# app/runners/page_runners.rb class Create < Runner def run(wiki_id, page_params) wiki = Wiki.find(params[:wiki_id]) page = wiki.pages.new(page_params) if page.save success(page) else failure(wiki, page) end end end

# app/controllers/page_controller.rb def create Create.new(self, params[:wiki_id], page_params).run do |on| on.success { |page| redirect_to [page.wiki, page], notice: "#{page.name} created" } on.failure { |wiki, page| render :new } end end

Page 33: A Tour of Wyriki

Enable Labs @mark_menard

# app/runners/page_runners.rb class Create < Runner def run(wiki_id, page_params) wiki = Wiki.find(params[:wiki_id]) page = wiki.pages.new(page_params) if page.save success(page) else failure(wiki, page) end end end

# app/controllers/page_controller.rb def create Create.new(self, params[:wiki_id], page_params).run do |on| on.success { |page| redirect_to [page.wiki, page], notice: "#{page.name} created" } on.failure { |wiki, page| render :new } end end

Page 34: A Tour of Wyriki

Enable Labs @mark_menard

Action Controller ::

Base

ActiveRecord :: Base

Page Controller

Application Controller

Page

Wiki

Create Page Runner

<<protocol>>context

Rails

Not Rails

Create Page

Loged In User

Page 35: A Tour of Wyriki

Enable Labs @mark_menard

Enough Architecture! !What about the Ruby!!

!

How did Jim actually !do the callbacks and the

runners?

Page 36: A Tour of Wyriki

Enable Labs @mark_menard

Runner

Named Callbacks

<<protocol>>context

<<protocol>>Repo

Page 37: A Tour of Wyriki

Enable Labs @mark_menard

# app/controllers/pages_controller.rb def create run(Create, params[:wiki_id], page_params) do |on| on.success { |page| redirect_to [page.wiki, page], notice: "#{page.name} created" } on.failure { |wiki, page| render :new } end end

Runner

Named Callbacks

<<protocol>>context

<<protocol>>Repo

Page 38: A Tour of Wyriki

Enable Labs @mark_menard

# app/controllers/pages_controller.rb def create run(Create, params[:wiki_id], page_params) do |on| on.success { |page| redirect_to [page.wiki, page], notice: "#{page.name} created" } on.failure { |wiki, page| render :new } end end

Runner

Named Callbacks

<<protocol>>context

<<protocol>>Repo

Page 39: A Tour of Wyriki

Enable Labs @mark_menard

# app/controllers/pages_controller.rb def create run(Create, params[:wiki_id], page_params) do |on| on.success { |page| redirect_to [page.wiki, page], notice: "#{page.name} created" } on.failure { |wiki, page| render :new } end end

Runner

Named Callbacks

<<protocol>>context

<<protocol>>Repo

Page 40: A Tour of Wyriki

Enable Labs @mark_menard

# app/runners/runner.rb class Runner attr_reader :context ! def initialize(context) @callbacks = NamedCallbacks.new @context = context yield(@callbacks) if block_given? end ! def repo context.repo end ! def success(*args) callback(:success, *args) end ! def failure(*args) callback(:failure, *args) end ! def callback(name, *args) @callbacks.call(name, *args) args end end

# app/controllers/pages_controller.rb def create run(Create, params[:wiki_id], page_params) do |on| on.success { |page| redirect_to [page.wiki, page], notice: "#{page.name} created" } on.failure { |wiki, page| render :new } end end

Runner

Named Callbacks

<<protocol>>context

<<protocol>>Repo

Page 41: A Tour of Wyriki

Enable Labs @mark_menard

# app/runners/runner.rb class Runner attr_reader :context ! def initialize(context) @callbacks = NamedCallbacks.new @context = context yield(@callbacks) if block_given? end ! def repo context.repo end ! def success(*args) callback(:success, *args) end ! def failure(*args) callback(:failure, *args) end ! def callback(name, *args) @callbacks.call(name, *args) args end end

# app/controllers/pages_controller.rb def create run(Create, params[:wiki_id], page_params) do |on| on.success { |page| redirect_to [page.wiki, page], notice: "#{page.name} created" } on.failure { |wiki, page| render :new } end end

Runner

Named Callbacks

<<protocol>>context

<<protocol>>Repo

Page 42: A Tour of Wyriki

Enable Labs @mark_menard

# app/runners/runner.rb class Runner attr_reader :context ! def initialize(context) @callbacks = NamedCallbacks.new @context = context yield(@callbacks) if block_given? end ! # … end

# app/runners/named_callbacks.rb class NamedCallbacks def initialize @callbacks = {} end ! def method_missing(sym, *args, &block) @callbacks[sym] = block end ! # … end

# app/controllers/pages_controller.rb def create run(Create, params[:wiki_id], page_params) do |on| on.success { |page| redirect_to [page.wiki, page], notice: "#{page.name} created" } on.failure { |wiki, page| render :new } end end

Page 43: A Tour of Wyriki

Enable Labs @mark_menard

# app/runners/runner.rb class Runner attr_reader :context ! def initialize(context) @callbacks = NamedCallbacks.new @context = context yield(@callbacks) if block_given? end ! # … end

# app/runners/named_callbacks.rb class NamedCallbacks def initialize @callbacks = {} end ! def method_missing(sym, *args, &block) @callbacks[sym] = block end ! # … end

# app/controllers/pages_controller.rb def create run(Create, params[:wiki_id], page_params) do |on| on.success { |page| redirect_to [page.wiki, page], notice: "#{page.name} created" } on.failure { |wiki, page| render :new } end end

Page 44: A Tour of Wyriki

Enable Labs @mark_menard

# app/runners/named_callbacks.rb class NamedCallbacks def initialize @callbacks = {} end ! def method_missing(sym, *args, &block) @callbacks[sym] = block end ! # … end

# app/controllers/pages_controller.rb def create run(Create, params[:wiki_id], page_params) do |on| on.success { |page| redirect_to [page.wiki, page], notice: "#{page.name} created" } on.failure { |wiki, page| render :new } end end

Page 45: A Tour of Wyriki

Enable Labs @mark_menard

# app/runners/named_callbacks.rb class NamedCallbacks def initialize @callbacks = {} end ! def method_missing(sym, *args, &block) @callbacks[sym] = block end ! # … end

# app/controllers/pages_controller.rb def create run(Create, params[:wiki_id], page_params) do |on| on.success { |page| redirect_to [page.wiki, page], notice: "#{page.name} created" } on.failure { |wiki, page| render :new } end end

Page 46: A Tour of Wyriki

Enable Labs @mark_menard

# app/runners/named_callbacks.rb class NamedCallbacks def initialize @callbacks = {} end ! def method_missing(sym, *args, &block) @callbacks[sym] = block end ! # … end

# app/controllers/pages_controller.rb def create run(Create, params[:wiki_id], page_params) do |on| on.success { |page| redirect_to [page.wiki, page], notice: "#{page.name} created" } on.failure { |wiki, page| render :new } end end

Page 47: A Tour of Wyriki

Enable Labs @mark_menard

# app/controllers/pages_controller.rb def create run(Create, params[:wiki_id], page_params) do |on| on.success { |page| redirect_to [page.wiki, page], notice: "#{page.name} created" } on.failure { |wiki, page| render :new } end end

Weirich Block Style

Page 48: A Tour of Wyriki

Enable Labs @mark_menard

# app/controllers/pages_controller.rb def create run(Create, params[:wiki_id], page_params) do |on| on.success { |page| redirect_to [page.wiki, page], notice: "#{page.name} created" } on.failure { |wiki, page| render :new } end end

# app/controllers/pages_controller.rb def create run(Create, params[:wiki_id], page_params) do |on| on.success { |page| redirect_to [page.wiki, page], notice: "#{page.name} created" } on.failure { |wiki, page| render :new } end end

Weirich Block Style

Page 49: A Tour of Wyriki

Enable Labs @mark_menard

Some Lessons

Page 50: A Tour of Wyriki

Enable Labs @mark_menard

Repositories

Page 51: A Tour of Wyriki

Enable Labs @mark_menard

Domain Rails

Page 52: A Tour of Wyriki

Enable Labs @mark_menard

Page 53: A Tour of Wyriki

Enable Labs @mark_menard

Page 54: A Tour of Wyriki

Enable Labs @mark_menard

Action Controller ::

Base

ActiveRecord :: Base

Page Controller

Application Controller

Page

WikiCreate Page Runner

<<protocol>>Repo Repo

<<protocol>>context

Page 55: A Tour of Wyriki

Enable Labs @mark_menard

Action Controller ::

Base

ActiveRecord :: Base

Page Controller

Application Controller

Page

WikiCreate Page Runner

<<protocol>>Repo Repo

<<protocol>>context

Domain

Page 56: A Tour of Wyriki

Enable Labs @mark_menard

Action Controller ::

Base

ActiveRecord :: Base

Page Controller

Application Controller

Page

WikiCreate Page Runner

<<protocol>>Repo Repo

<<protocol>>context

Domain

Page 57: A Tour of Wyriki

Enable Labs @mark_menard

Action Controller ::

Base

ActiveRecord :: Base

Page Controller

Application Controller

Page

WikiCreate Page Runner

<<protocol>>Repo Repo

<<protocol>>context

Domain

Page 58: A Tour of Wyriki

Enable Labs @mark_menard

# app/services/wiki_repository.rb class WikiRepository include Repo::UserMethods include Repo::WikiMethods include Repo::PageMethods include Repo::PermissionMethods end

Page 59: A Tour of Wyriki

Enable Labs @mark_menard

# app/services/repo/page_methods.rb module PageMethods def find_wiki_page(wiki_id, page_id) wiki = Wiki.find(wiki_id) page = wiki.pages.find(page_id) ! # … end ! # … ! def save_page(page) page.data.save end ! # … end

Page 60: A Tour of Wyriki

Enable Labs @mark_menard

Domain

Page 61: A Tour of Wyriki

Enable Labs @mark_menard

Biz Objects

Page 62: A Tour of Wyriki

Enable Labs @mark_menard

Action Controller ::

Base

ActiveRecord :: Base

Page Controller

Application Controller

Page

WikiCreate Page Runner

<<protocol>>Repo Repo

<<protocol>>context

Domain

Page 63: A Tour of Wyriki

Enable Labs @mark_menard

Biz Model ActiveRecord :: Base

Simple Delegator

Page 64: A Tour of Wyriki

Enable Labs @mark_menard

# app/models/biz/model.rb module Biz class Model < SimpleDelegator include BlockActiveRecord ! def data datum = self while datum.biz? datum = datum.__getobj__ end datum end ! def ==(other) if other.respond_to?(:data) data == other.data else data == other end end

def biz? true end ! def class data.class end ! def self.wrap(model) model ? new(model) : nil end ! def self.wraps(models) models.map { |model| wrap(model) } end ! end end

Page 65: A Tour of Wyriki

Enable Labs @mark_menard

# app/models/biz/model.rb module Biz class Model < SimpleDelegator include BlockActiveRecord ! def data datum = self while datum.biz? datum = datum.__getobj__ end datum end ! def ==(other) if other.respond_to?(:data) data == other.data else data == other end end

def biz? true end ! def class data.class end ! def self.wrap(model) model ? new(model) : nil end ! def self.wraps(models) models.map { |model| wrap(model) } end ! end end

Page 66: A Tour of Wyriki

Enable Labs @mark_menard

# app/models/biz/model.rb module Biz class Model < SimpleDelegator include BlockActiveRecord ! def data datum = self while datum.biz? datum = datum.__getobj__ end datum end ! def ==(other) if other.respond_to?(:data) data == other.data else data == other end end

def biz? true end ! def class data.class end ! def self.wrap(model) model ? new(model) : nil end ! def self.wraps(models) models.map { |model| wrap(model) } end ! end end

Page 67: A Tour of Wyriki

Enable Labs @mark_menard

# app/models/biz/model.rb module Biz class Model < SimpleDelegator include BlockActiveRecord ! def data datum = self while datum.biz? datum = datum.__getobj__ end datum end ! def ==(other) if other.respond_to?(:data) data == other.data else data == other end end

def biz? true end ! def class data.class end ! def self.wrap(model) model ? new(model) : nil end ! def self.wraps(models) models.map { |model| wrap(model) } end ! end end

Page 68: A Tour of Wyriki

Enable Labs @mark_menard

# app/models/biz/model.rb module Biz class Model < SimpleDelegator include BlockActiveRecord ! def data datum = self while datum.biz? datum = datum.__getobj__ end datum end ! def ==(other) if other.respond_to?(:data) data == other.data else data == other end end

def biz? true end ! def class data.class end ! def self.wrap(model) model ? new(model) : nil end ! def self.wraps(models) models.map { |model| wrap(model) } end ! end end

Page 69: A Tour of Wyriki

Enable Labs @mark_menard

# app/models/biz/model.rb module Biz class Model < SimpleDelegator include BlockActiveRecord ! def data datum = self while datum.biz? datum = datum.__getobj__ end datum end ! def ==(other) if other.respond_to?(:data) data == other.data else data == other end end

def biz? true end ! def class data.class end ! def self.wrap(model) model ? new(model) : nil end ! def self.wraps(models) models.map { |model| wrap(model) } end ! end end

Page 70: A Tour of Wyriki

Enable Labs @mark_menard

# app/services/repo/page_methods.rb module PageMethods def find_wiki_page(wiki_id, page_id) wiki = Wiki.find(wiki_id) page = wiki.pages.find(page_id) Biz::Page.wrap(page) end ! # … ! def save_page(page) page.data.save end ! # … end

Page 71: A Tour of Wyriki

Enable Labs @mark_menard

# app/services/repo/page_methods.rb module PageMethods def find_wiki_page(wiki_id, page_id) wiki = Wiki.find(wiki_id) page = wiki.pages.find(page_id) Biz::Page.wrap(page) end ! # … ! def save_page(page) page.data.save end ! # … end

Page 72: A Tour of Wyriki

Enable Labs @mark_menard

module Biz class Page < Model def wiki Biz::Wiki.wrap(super) end ! def html_content(context) Kramdown::Document.new(referenced_content(context)).to_html end ! def referenced_content(context) content.gsub(/(([A-Z][a-z0-9]+){2,})/) { |page_name| if wiki.page?(context.repo, page_name) "[#{page_name}](#{context.named_page_path(wiki.name,page_name)})" elsif context.current_user.can_write?(wiki) "#{page_name}[?](#{context.new_named_page_path(wiki.name, page_name)})" else page_name end } end end end

Page 73: A Tour of Wyriki

Enable Labs @mark_menard

module Biz class Page < Model def wiki Biz::Wiki.wrap(super) end ! def html_content(context) Kramdown::Document.new(referenced_content(context)).to_html end ! def referenced_content(context) content.gsub(/(([A-Z][a-z0-9]+){2,})/) { |page_name| if wiki.page?(context.repo, page_name) "[#{page_name}](#{context.named_page_path(wiki.name,page_name)})" elsif context.current_user.can_write?(wiki) "#{page_name}[?](#{context.new_named_page_path(wiki.name, page_name)})" else page_name end } end end end

Page 74: A Tour of Wyriki

Enable Labs @mark_menard

Action Controller ::

Base

ActiveRecord :: Base

Page Controller

Application Controller

Some Model

Runner

Repo

Biz Model

<< wraps >>

Page 75: A Tour of Wyriki

Enable Labs @mark_menard

Action Controller ::

Base

ActiveRecord :: Base

Page Controller

Application Controller

Some Model

Runner

Repo

Biz Model

<< wraps >>

Page 76: A Tour of Wyriki

Enable Labs @mark_menard

More Lessons

Page 77: A Tour of Wyriki

Enable Labs @mark_menard

Why?

Page 78: A Tour of Wyriki

Enable Labs @mark_menard

Isolated Business Logic

Page 79: A Tour of Wyriki

Enable Labs @mark_menard

Incremental Approach

Page 80: A Tour of Wyriki

Enable Labs @mark_menard

Fast Tests

Page 81: A Tour of Wyriki

Enable Labs @mark_menard

$ time rspec spec/runners spec/models/biz (git)-[master] ...................................................................................................... !Finished in 0.17573 seconds 102 examples, 0 failures rspec spec/runners spec/models/biz 0.61s user 0.07s system 99% cpu 0.683 total

Page 82: A Tour of Wyriki

Enable Labs @mark_menard

Should we decouple?

Page 83: A Tour of Wyriki

Enable Labs @mark_menard

http://www.flickr.com/photos/dwortlehock/

Thanks for Everything Jim!

Page 84: A Tour of Wyriki

Enable Labs @mark_menard

Start Today

http://www.enablelabs.com/

[email protected]

866-895-8189

Enable Labs@mark_menard