Enable Labs @mark_menard A Tour of Wyriki Mark Menard Ruby Nation June 7, 2014 @mark_menard Enable Labs
Jan 19, 2015
Enable Labs @mark_menard
A Tour of Wyriki
Mark Menard
Ruby Nation!June 7, 2014
@mark_menard !Enable Labs
Enable Labs @mark_menard
Jim Weirich
Enable Labs @mark_menardhttp://www.flickr.com/photos/langalex
Enable Labs @mark_menard
TDD
Enable Labs @mark_menard
Yea… whatever…
Enable Labs @mark_menard
What is Wyriki?
Enable Labs @mark_menard
The Wyriki Domain
Page
Wiki
*
1
Create Wiki
Create Page
Update Page
Create User
Loged In User
Anonymous User
Enable Labs @mark_menard
Business Logic
ActiveRecord
ActionPack
Controllers
MySQL MongoDB PostgreSQL
Redis
Sidekiq
Resque
What was Jim trying to accomplish?
Enable Labs @mark_menard
Testing
Enable Labs @mark_menard
Why?!!
When?
Enable Labs @mark_menard
Typical Rails
Enable Labs @mark_menard
Create Page
Loged In User
Action Controller ::
Base
ActiveRecord :: Base
Pages Controller
Application Controller
Page
Wiki
Enable Labs @mark_menard
Create Page
Loged In User
Action Controller ::
Base
ActiveRecord :: Base
Pages Controller
Application Controller
Page
Wiki
Enable Labs @mark_menard
Create Page
Loged In User
Action Controller ::
Base
ActiveRecord :: Base
Pages Controller
Application Controller
Page
Wiki
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
Enable Labs @mark_menard
Wyriki Style
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
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>>
Enable Labs @mark_menard
Action Controller ::
Base
ActiveRecord :: Base
Page Controller
Application Controller
Some Model
Runner
Repo
Biz Model
<< wraps >><< gets and saves
stuff >>
Enable Labs @mark_menard
Action Controller ::
Base
ActiveRecord :: Base
Page Controller
Application Controller
Some Model
Runner
Repo
Biz Model
<< wraps >><< gets and saves
stuff >>
Enable Labs @mark_menard
Enable Labs @mark_menard
Runners
Enable Labs @mark_menard
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
Enable Labs @mark_menard
This is the !Domain
Enable Labs @mark_menard
This is the !Domain
This is Rails
Enable Labs @mark_menard
This is our Context.
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
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
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
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
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
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
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
Enable Labs @mark_menard
Enough Architecture! !What about the Ruby!!
!
How did Jim actually !do the callbacks and the
runners?
Enable Labs @mark_menard
Runner
Named Callbacks
<<protocol>>context
<<protocol>>Repo
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
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
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
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
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
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
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
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
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
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
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
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
Enable Labs @mark_menard
Some Lessons
Enable Labs @mark_menard
Repositories
Enable Labs @mark_menard
Domain Rails
Enable Labs @mark_menard
Enable Labs @mark_menard
Enable Labs @mark_menard
Action Controller ::
Base
ActiveRecord :: Base
Page Controller
Application Controller
Page
WikiCreate Page Runner
<<protocol>>Repo Repo
<<protocol>>context
Enable Labs @mark_menard
Action Controller ::
Base
ActiveRecord :: Base
Page Controller
Application Controller
Page
WikiCreate Page Runner
<<protocol>>Repo Repo
<<protocol>>context
Domain
Enable Labs @mark_menard
Action Controller ::
Base
ActiveRecord :: Base
Page Controller
Application Controller
Page
WikiCreate Page Runner
<<protocol>>Repo Repo
<<protocol>>context
Domain
Enable Labs @mark_menard
Action Controller ::
Base
ActiveRecord :: Base
Page Controller
Application Controller
Page
WikiCreate Page Runner
<<protocol>>Repo Repo
<<protocol>>context
Domain
Enable Labs @mark_menard
# app/services/wiki_repository.rb class WikiRepository include Repo::UserMethods include Repo::WikiMethods include Repo::PageMethods include Repo::PermissionMethods end
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
Enable Labs @mark_menard
Domain
Enable Labs @mark_menard
Biz Objects
Enable Labs @mark_menard
Action Controller ::
Base
ActiveRecord :: Base
Page Controller
Application Controller
Page
WikiCreate Page Runner
<<protocol>>Repo Repo
<<protocol>>context
Domain
Enable Labs @mark_menard
Biz Model ActiveRecord :: Base
Simple Delegator
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
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
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
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
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
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
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
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
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
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
Enable Labs @mark_menard
Action Controller ::
Base
ActiveRecord :: Base
Page Controller
Application Controller
Some Model
Runner
Repo
Biz Model
<< wraps >>
Enable Labs @mark_menard
Action Controller ::
Base
ActiveRecord :: Base
Page Controller
Application Controller
Some Model
Runner
Repo
Biz Model
<< wraps >>
Enable Labs @mark_menard
More Lessons
Enable Labs @mark_menard
Why?
Enable Labs @mark_menard
Isolated Business Logic
Enable Labs @mark_menard
Incremental Approach
Enable Labs @mark_menard
Fast Tests
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
Enable Labs @mark_menard
Should we decouple?
Enable Labs @mark_menard
http://www.flickr.com/photos/dwortlehock/
Thanks for Everything Jim!
Enable Labs @mark_menard
Start Today
http://www.enablelabs.com/
866-895-8189
Enable Labs@mark_menard