SkillAgentSearch skills...

Schizo

DCI (data, context and interaction) for Ruby / Rails / ActiveRecord

Install / Use

/learn @cjbottaro/Schizo
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

== Schizo {<img src="https://secure.travis-ci.org/cjbottaro/schizo.png" />}[http://travis-ci.org/cjbottaro/schizo]

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 <tt>Object#extend</tt>, 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://en.wikipedia.org/wiki/Data,_context_and_interaction]

{\http://mikepackdev.com/blog_posts/24-the-right-way-to-code-dci-in-ruby}[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://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://victorsavkin.com/post/13966712168/dci-in-ruby]

{\http://andrzejonsoftware.blogspot.com/2011/02/dci-and-rails.html}[http://andrzejonsoftware.blogspot.com/2011/02/dci-and-rails.html]

== The Problem

So what's wrong with just using <tt>Object#extend</tt>? 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 <tt>Object#extend</tt>:

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 <i>facade classes</i> and <i>facade objects</i> 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

<tt>Schizo::Facades::User::Poster</tt> inherits from +User+, that's why <tt>poster.kind_of?(User)</tt> works natrually. <tt>poster.instance_of?(User)</tt> 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 <tt>ActiveSupport::Concern</tt> instead of <tt>Schizo::Role</tt>

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}[http://doc.stochasticbytes.com/schizo/index.html]

== Contact

{@cjbottaro}[http://twitter.com/cjbottaro]

== Liscense

MIT or something

View on GitHub
GitHub Stars69
CategoryDevelopment
Updated2y ago
Forks4

Languages

Ruby

Security Score

65/100

Audited on Dec 8, 2023

No findings