Project

schizo

0.02
No commit activity in last 3 years
No release in over 3 years
There's a lot of open issues
DCI (data, context and interaction) for Ruby / Rails / ActiveRecord
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

>= 0
>= 0
>= 0
>= 0
 Project Readme

Schizo is a libary that aids in using DCI (data, context and interaction) in Ruby/Rails projects. It aims to overcome some of the shortcomings of using plain Object#extend, namely the issue that extending a role can permenantly alter a class.

Quickstart¶ ↑

Dive in…

class User < ActiveRecord::Base
  include Schizo::Data
end

module Poster
  extend Schizo::Role

  included do
    has_many :posts
  end

  def post_count_in_english
    "#{name} has #{posts.count} post(s)"
  end
end

user = User.find(1)
user.as(Poster) do |poster|
  poster.respond_to?(:posts) # => true
  user.respond_to?(:posts)   # => false

  poster.respond_to?(:post_count_in_english) # => true
  user.respond_to?(:post_count_in_english)   # => false

  poster.kind_of?(User)     # => true
  poster.instance_of?(User) # => true

  poster.class.name # => "User"
end

DCI¶ ↑

http://en.wikipedia.org/wiki/Data,_context_and_interaction

http://mikepackdev.com/blog_posts/24-the-right-way-to-code-dci-in-ruby

http://saturnflyer.com/blog/jim/2011/10/04/oop-dci-and-ruby-what-your-system-is-vs-what-your-system-does/

http://victorsavkin.com/post/13966712168/dci-in-ruby

http://andrzejonsoftware.blogspot.com/2011/02/dci-and-rails.html

The Problem¶ ↑

So what’s wrong with just using Object#extend? Nothing, until you want to avoid altering an instance’s class as a side effect of adorning the instance with a role… which happens often when using ActiveRecord.

Consider the following use of DCI and ActiveRecord with plain old Object#extend:

class User < ActiveRecord::Base
end

module Poster
  def self.extended(object)
    object.class.class_eval do
      has_many :posts
    end
  end

  def post_count_in_english
    "#{name} has #{posts.count} post(s)"
  end
end

user1 = User.find(1)
user1.extend(Poster)
user1.respond_to?(:posts) # Ok

user2 = User.find(2)
user2.respond_to?(:posts) # Oops, extending user1 ended up changing *all* users!

That goes against the core concept in DCI that your data should only be injected with behavior for a specific context.

The Magic¶ ↑

So how does Schizo work? It creates facade classes and facade objects that stand in for the classes and objects you really want. The facades try to quack as best they can like the real objects/classes.

This is easier to explain in an example (continuing from the Quickstart example):

user = User.find(1)
user.as(Poster) do |poster|
  poster.kind_of?(User)     # => true
  poster.instance_of?(User) # => true

  poster.class.name # => "User"
  poster.class      # => Schizo::Facades::User::Poster
end

Schizo::Facades::User::Poster inherits from User, that’s why poster.kind_of?(User) works natrually. poster.instance_of?(User) works because of the facade consciously trying to quack like User.

Facades and Objects¶ ↑

So knowing you’re working with a facade instead of the original object, some of the gotchas become obvious.

class Foo
  include Schizo::Data
  attr_reader :bar
  def initialize
    @bar = "low"
  end
end

module Baz
  extend Schizo::Role
  def set_bar(value)
    @bar = value
  end
end

foo = Foo.new
baz = foo.as(Baz)
baz.set_bar("high")
baz.bar # => "high"
foo.bar # => "low"

Makes perfect sense, right? But what about this…

foo = Foo.new
foo.as(Baz) do |baz|
  baz.set_bar("high")
end
foo.bar # => "high"

What?! Nah, it’s really simple. At the end of the code block, baz.actualize is called. All #actualize does is copy over the instances variables from the facade to the real object.

You can get the exact same affect by doing:

foo = Foo.new
baz = foo.as(Baz)
baz.set_bar("high")
baz.actualize
foo.bar # => "high"

Hmm, maybe #actualize should be renamed #converge… what do you think?

Multiple Roles and Nesting¶ ↑

You can adorn a data object with more than one role…

poster = User.new.as(Poster)
commenter = poster.as(Commenter) # Has all the methods of a Commenter AND Poster

Alternatively…

User.new.as(Poster) do |poster|
  poster.as(Commenter) do |commenter|
    # Has all the methods of a Commenter AND Poster
  end
end

ActiveSupport::Concern¶ ↑

You can use ActiveSupport::Concern instead of Schizo::Role

module Baz
  extend ActiveSupport::Concern
  def something; end
end

foo = Foo.new
baz = foo.as(Baz)
baz.something

Documentation¶ ↑

http://doc.stochasticbytes.com/schizo/index.html

Contact¶ ↑

@cjbottaro

Liscense¶ ↑

MIT or something