schizo 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ tmp
6
+ doc
7
+ vendor
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.2
4
+ - 1.9.3
data/CHANGELOG ADDED
@@ -0,0 +1,5 @@
1
+ 0.1.1
2
+ * Updated gemspec info
3
+
4
+ 0.1.0
5
+ * Initial release
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in dci.gemspec
4
+ gemspec
data/README.rdoc ADDED
@@ -0,0 +1,175 @@
1
+ == Schizo {<img src="https://secure.travis-ci.org/cjbottaro/schizo.png" />}[http://travis-ci.org/cjbottaro/schizo]
2
+
3
+ Schizo is a libary that aids in using DCI (data, context and interaction) in Ruby/Rails projects. It aims
4
+ to overcome some of the shortcomings of using plain <tt>Object#extend</tt>, namely the issue that extending
5
+ a role can permenantly alter a class.
6
+
7
+ == Quickstart
8
+
9
+ Dive in...
10
+
11
+ class User < ActiveRecord::Base
12
+ include Schizo::Data
13
+ end
14
+
15
+ module Poster
16
+ extend Schizo::Role
17
+
18
+ extended do
19
+ has_many :posts
20
+ end
21
+
22
+ def post_count_in_english
23
+ "#{name} has #{posts.count} post(s)"
24
+ end
25
+ end
26
+
27
+ user = User.find(1)
28
+ user.as(Poster) do |poster|
29
+ poster.respond_to?(:posts) # => true
30
+ user.respond_to?(:posts) # => false
31
+
32
+ poster.respond_to?(:post_count_in_english) # => true
33
+ user.respond_to?(:post_count_in_english) # => false
34
+
35
+ poster.kind_of?(User) # => true
36
+ poster.instance_of?(User) # => true
37
+
38
+ poster.class.name # => "User"
39
+ end
40
+
41
+ == DCI
42
+
43
+ {\http://en.wikipedia.org/wiki/Data,_context_and_interaction}[http://en.wikipedia.org/wiki/Data,_context_and_interaction]
44
+
45
+ {\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]
46
+
47
+ {\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/]
48
+
49
+ {\http://victorsavkin.com/post/13966712168/dci-in-ruby}[http://victorsavkin.com/post/13966712168/dci-in-ruby]
50
+
51
+ {\http://andrzejonsoftware.blogspot.com/2011/02/dci-and-rails.html}[http://andrzejonsoftware.blogspot.com/2011/02/dci-and-rails.html]
52
+
53
+ == The Problem
54
+
55
+ So what's wrong with just using <tt>Object#extend</tt>? Nothing, until you want to alter an instance's class
56
+ as a side effect of adorning it with a role... which happens often when using ActiveRecord.
57
+
58
+ Consider the following use of DCI and ActiveRecord with plain old <tt>Object#extend</tt>:
59
+
60
+ class User < ActiveRecord::Base
61
+ end
62
+
63
+ module Poster
64
+ def self.extended(object)
65
+ object.class.class_eval do
66
+ has_many :posts
67
+ end
68
+ end
69
+
70
+ def post_count_in_english
71
+ "#{name} has #{posts.count} post(s)"
72
+ end
73
+ end
74
+
75
+ user1 = User.find(1)
76
+ user1.extend(Poster)
77
+ user1.respond_to?(:posts) # Ok
78
+
79
+ user2 = User.find(2)
80
+ user2.respond_to?(:posts) # Oops, extending user1 ended up changing *all* users!
81
+
82
+ That goes against the core concept in DCI that your data should only be injected with behavior for a specific
83
+ context.
84
+
85
+ == The Magic
86
+
87
+ So how does Schizo work? It creates <i>facade classes</i> and <i>facade objects</i> that stand in for the
88
+ classes and objects you really want. The facades try to quack as best they can like the real objects/classes.
89
+
90
+ This is easier to explain in an example (continuing from the Quickstart example):
91
+
92
+ user = User.find(1)
93
+ user.as(Poster) do |poster|
94
+ poster.kind_of?(User) # => true
95
+ poster.instance_of?(User) # => true
96
+
97
+ poster.class.name # => "User"
98
+ poster.class # => Schizo::Facades::User::Poster
99
+ end
100
+
101
+ <tt>Schizo::Facades::User::Poster</tt> inherits from +User+, that's why <tt>poster.kind_of?(User)</tt> works natrually.
102
+ <tt>poster.instance_of?(User)</tt> works because of the facade consciously trying to quack like +User+.
103
+
104
+ == Facades and Objects
105
+
106
+ So knowing you're working with a facade instead of the original object, some of the gotchas become obvious.
107
+
108
+ class Foo
109
+ include Schizo::Data
110
+ attr_reader :bar
111
+ def initialize
112
+ @bar = "low"
113
+ end
114
+ end
115
+
116
+ module Baz
117
+ extend Schizo::Role
118
+ def set_bar(value)
119
+ @bar = value
120
+ end
121
+ end
122
+
123
+ foo = Foo.new
124
+ baz = foo.as(Baz)
125
+ baz.set_bar("high")
126
+ baz.bar # => "high"
127
+ foo.bar # => "low"
128
+
129
+ Makes perfect sense, right? But what about this...
130
+
131
+ foo = Foo.new
132
+ foo.as(Baz) do |baz|
133
+ baz.set_bar("high")
134
+ end
135
+ foo.bar # => "high"
136
+
137
+ What?! Nah, it's really simple. At the end of the code block, +baz.actualize+ is called. All +#actualize+ does
138
+ is copy over the instances variables from the facade to the real object.
139
+
140
+ You can get the exact same affect by doing:
141
+
142
+ foo = Foo.new
143
+ baz = foo.as(Baz)
144
+ baz.set_bar("high")
145
+ baz.actualize
146
+ foo.bar # => "high"
147
+
148
+ Hmm, maybe +#actualize+ should be renamed +#converge+... what do you think?
149
+
150
+ == Multiple Roles and Nesting
151
+
152
+ You can adorn a data object with more than one role...
153
+
154
+ poster = User.new.as(Poster)
155
+ commenter = poster.as(Commenter) # Has all the methods of a Commenter AND Poster
156
+
157
+ Alternatively...
158
+
159
+ User.new.as(Poster) do |poster|
160
+ poster.as(Commenter) do |commenter|
161
+ # Has all the methods of a Commenter AND Poster
162
+ end
163
+ end
164
+
165
+ == Documentation
166
+
167
+ {\http://doc.stochasticbytes.com/schizo/index.html}[http://doc.stochasticbytes.com/schizo/index.html]
168
+
169
+ == Contact
170
+
171
+ {@cjbottaro}[http://twitter.com/cjbottaro]
172
+
173
+ == Liscense
174
+
175
+ MIT or something
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'active_record'
4
+ require 'yaml'
5
+ require 'logger'
6
+ require 'rspec/core/rake_task'
7
+
8
+ ENV["RAILS_ENV"] ||= "test"
9
+
10
+ task :default => :spec
11
+
12
+ desc "Run specs (default)"
13
+ RSpec::Core::RakeTask.new(:spec => :migrate) do |t|
14
+ t.rspec_opts = "--color -f d"
15
+ end
16
+
17
+ desc "Migrate the database through scripts in db/migrate. Target specific version with VERSION=x"
18
+ task :migrate => :environment do
19
+ ActiveRecord::Migrator.migrate('db/migrate', ENV["VERSION"] ? ENV["VERSION"].to_i : nil )
20
+ end
21
+
22
+ task :environment do
23
+ FileUtils.mkdir_p("tmp")
24
+ ActiveRecord::Base.establish_connection(YAML::load(File.open('db/database.yml')))
25
+ ActiveRecord::Base.logger = Logger.new(File.open('tmp/database.log', 'a'))
26
+ end
27
+
data/db/database.yml ADDED
@@ -0,0 +1,2 @@
1
+ adapter: sqlite3
2
+ database: "tmp/test.db"
@@ -0,0 +1,8 @@
1
+ class CreateUsers < ActiveRecord::Migration
2
+ def change
3
+ create_table :users do |t|
4
+ t.string :name
5
+ t.timestamps
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,9 @@
1
+ class CreatePosts < ActiveRecord::Migration
2
+ def change
3
+ create_table :posts do |t|
4
+ t.integer :user_id
5
+ t.string :title
6
+ t.timestamps
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,32 @@
1
+ module Schizo
2
+
3
+ # Include Data into a class to give it the ability to be adorned with a Role.
4
+ module Data
5
+
6
+ # Adorn a Data object with a Role.
7
+ #
8
+ # If a block is given, then a facade for _self_ is yielded to the block and the return value
9
+ # is _self_ with the facade's instance variables copied over to it.
10
+ # user.name = "callie"
11
+ # user.as(Poster) do |poster|
12
+ # poster.name = "coco"
13
+ # end
14
+ # user.name # => "coco"
15
+ #
16
+ # Without a block, a facade for _self_ is returned.
17
+ # user.name = "callie"
18
+ # poster = user.as(Poster)
19
+ # poster.name = "coco"
20
+ # user.name # => "callie"
21
+ def as(role, &block)
22
+ facade = Facade::ObjectBuilder.new(self, role).product
23
+ if block_given?
24
+ block.call(facade)
25
+ facade.actualize
26
+ else
27
+ facade
28
+ end
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,44 @@
1
+ module Schizo
2
+ # Rdoc won't let me :nodoc: this module. Nothing to see here.
3
+ module Facade
4
+
5
+ # This module is included in each Data object facade's class.
6
+ module Base
7
+
8
+ def self.included(mod) #:nodoc:
9
+ mod.extend(ClassMethods)
10
+ mod.class_eval <<-STR
11
+ attr_reader :#{DCI_INSTANCE_VARIABLE.to_s[1..-1]}
12
+ STR
13
+ end
14
+
15
+ module ClassMethods #:nodoc:
16
+
17
+ def name
18
+ superclass.name || "AnonClass#{object_id}"
19
+ end
20
+
21
+ end
22
+
23
+ def initialize(object, role) #:nodoc:
24
+ instance_variable_set(DCI_INSTANCE_VARIABLE, Struct.new(:object, :role).new(object, role))
25
+ end
26
+
27
+ def instance_of?(klass) #:nodoc:
28
+ # This is to get it working with nested facades. We need to traverse
29
+ # superclasses until we get to the first real (non facade) superclass.
30
+ my_superclass = self.class.superclass
31
+ while my_superclass.ancestors.include?(Base) and my_superclass
32
+ my_superclass = my_superclass.superclass
33
+ end
34
+ my_superclass == klass
35
+ end
36
+
37
+ # Copy a facade's instance variables to the object it's facading for.
38
+ def actualize
39
+ dci.object.tap{ |object| Schizo::Facade.copy_instance_variables(self, object) }
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,53 @@
1
+ module Schizo
2
+ module Facade #:nodoc:
3
+ class ClassBuilder #:nodoc:
4
+
5
+ attr_reader :base, :role
6
+
7
+ def initialize(base, role)
8
+ @base, @role = base, role
9
+ end
10
+
11
+ def product
12
+ @product ||= role_class
13
+ end
14
+
15
+ private
16
+
17
+ def module_name
18
+ base.name || "AnonClass#{base.object_id}"
19
+ end
20
+
21
+ def role_name
22
+ role.name || "AnonRole#{role.object_id}"
23
+ end
24
+
25
+ def container_module
26
+ @container_module ||= begin
27
+ if Schizo::Facades.const_defined?(module_name, false)
28
+ Schizo::Facades.const_get(module_name)
29
+ else
30
+ Schizo::Facades.const_set(module_name, Module.new)
31
+ end
32
+ end
33
+ end
34
+
35
+ def role_class
36
+ @role_class ||= begin
37
+ if container_module.const_defined?(role_name, false)
38
+ container_module.const_get(role_name)
39
+ else
40
+ container_module.const_set(role_name, build)
41
+ end
42
+ end
43
+ end
44
+
45
+ def build
46
+ Class.new(base){ include Base }.tap do |klass|
47
+ klass.class_eval(&role.extended_block) if role.extended_block
48
+ end
49
+ end
50
+
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,49 @@
1
+ module Schizo
2
+ module Facade #:nodoc:
3
+
4
+ DCI_INSTANCE_VARIABLE = :@dci
5
+
6
+ def self.copy_instance_variables(from, to)
7
+ from.instance_variables.each do |ivar_name|
8
+ ivar = from.instance_variable_get(ivar_name)
9
+ to.instance_variable_set(ivar_name, ivar) unless ivar_name == DCI_INSTANCE_VARIABLE
10
+ end
11
+ end
12
+
13
+ class ObjectBuilder #:nodoc:
14
+ attr_reader :object, :role
15
+
16
+ def initialize(object, role)
17
+ @object, @role = object, role
18
+ end
19
+
20
+ def product
21
+ @product ||= build
22
+ end
23
+
24
+ private
25
+
26
+ def facade_class
27
+ @facade_class ||= ClassBuilder.new(object.class, role).product
28
+ end
29
+
30
+ def build
31
+ facade_class.new(object, role).tap do |facade|
32
+
33
+ # This is to get nesting to work. Because each facade keeps a reference to the object
34
+ # it's facading for, we can traverse nested facades. Each time we find one, we extend
35
+ # its role into facade we're building.
36
+ previous_facade = object
37
+ while previous_facade.respond_to?(:dci)
38
+ facade.extend(previous_facade.dci.role)
39
+ previous_facade = previous_facade.dci.object
40
+ end
41
+
42
+ facade.extend(role)
43
+ Schizo::Facade.copy_instance_variables(object, facade)
44
+ end
45
+ end
46
+
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,9 @@
1
+ require "schizo/facade/base"
2
+ require "schizo/facade/class_builder"
3
+ require "schizo/facade/object_builder"
4
+
5
+ module Schizo
6
+ # This module solely exists to store facade classes.
7
+ module Facades #:nodoc:
8
+ end
9
+ end
@@ -0,0 +1,44 @@
1
+ module Schizo
2
+
3
+ # Extend a module with Role to make that module a _role_.
4
+ # module RoleA
5
+ # extend Schizo::Role
6
+ #
7
+ # def method_a
8
+ # end
9
+ #
10
+ # def method_b
11
+ # end
12
+ # end
13
+ module Role
14
+
15
+ # call-seq:
16
+ # extended{ ... }
17
+ #
18
+ # Call this method with a block and that block will be evaled by the Data object's class when
19
+ # the Data object is adorned with this role.
20
+ # module Poster
21
+ # extend Schizo::Role
22
+ #
23
+ # extended do
24
+ # has_many :posts
25
+ # end
26
+ # end
27
+ #
28
+ # User.new.as(Poster).do |poster|
29
+ # poster.posts.create :title => "My first post!"
30
+ # end
31
+ def extended(object = nil, &block)
32
+ if block_given?
33
+ @extended_block = block
34
+ else
35
+ super
36
+ end
37
+ end
38
+
39
+ def extended_block #:nodoc:
40
+ @extended_block
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,3 @@
1
+ module Schizo #:nodoc:
2
+ VERSION = "0.1.1"
3
+ end
data/lib/schizo.rb ADDED
@@ -0,0 +1,8 @@
1
+ require "schizo/data"
2
+ require "schizo/role"
3
+ require "schizo/version"
4
+ require "schizo/facade"
5
+
6
+ # The top level module; acts as a namespace.
7
+ module Schizo
8
+ end
data/schizo.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "schizo/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "schizo"
7
+ s.version = Schizo::VERSION
8
+ s.authors = ["Christopher J. Bottaro"]
9
+ s.email = ["cjbottaro@alumni.cs.utexas.edu"]
10
+ s.homepage = "https://github.com/cjbottaro/schizo"
11
+ s.summary = %q{DCI (data, context and interaction) for Ruby / Rails / ActiveRecord}
12
+ s.description = %q{DCI (data, context and interaction) for Ruby / Rails / ActiveRecord}
13
+
14
+ s.rubyforge_project = "schizo"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ s.add_development_dependency "rspec"
23
+ s.add_development_dependency "activerecord"
24
+ s.add_development_dependency "sqlite3"
25
+ s.add_development_dependency "rdoc"
26
+ s.add_development_dependency "pry"
27
+ s.add_development_dependency "rake"
28
+ # s.add_runtime_dependency "rest-client"
29
+ end
@@ -0,0 +1,44 @@
1
+ require "spec_helper"
2
+
3
+ describe "an ActiveRecord instance" do
4
+ context "adorned with a role" do
5
+
6
+ let(:role) do
7
+ Module.new do
8
+ extend Schizo::Role
9
+ def set_name(name)
10
+ self.name = name
11
+ end
12
+ end
13
+ end
14
+
15
+ let(:user){ User.new :name => "christopher" }
16
+
17
+ let(:adorned_user){ user.as(role) }
18
+
19
+ it "setting attributes should affect the original instance because they aren't duped" do
20
+ adorned_user.set_name("callie")
21
+ adorned_user.should be_name_changed
22
+ adorned_user.name.should == "callie"
23
+ user.name.should == "callie"
24
+ user.should be_name_changed
25
+ end
26
+
27
+ context "saving" do
28
+
29
+ before(:all){ adorned_user.save }
30
+
31
+ it "does not affect the original instance's status as a new record" do
32
+ adorned_user.should_not be_new_record
33
+ user.should be_new_record
34
+ end
35
+
36
+ it "unless #actualize is called" do
37
+ adorned_user.actualize
38
+ user.should_not be_new_record
39
+ end
40
+
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,51 @@
1
+ require "spec_helper"
2
+
3
+ describe "an ActiveRecord instance" do
4
+ context "extended with a role that has_many :posts" do
5
+
6
+ let(:user){ User.create! :name => "chris" }
7
+
8
+ let(:role) do
9
+ Module.new do
10
+ extend Schizo::Role
11
+ extended do
12
+ has_many :posts
13
+ end
14
+ end
15
+ end
16
+
17
+ let(:adorned_user){ user.as(role) }
18
+
19
+ it "responds to #posts" do
20
+ adorned_user.should respond_to(:posts)
21
+ user.should_not respond_to(:posts)
22
+ end
23
+
24
+ context "#posts" do
25
+
26
+ it "works with #<<" do
27
+ post = Post.new :title => "first"
28
+ post.should be_a_new_record
29
+ adorned_user.posts << post
30
+ post.should_not be_a_new_record
31
+ adorned_user.posts.count.should == 1
32
+ end
33
+
34
+ it "works with #build" do
35
+ post = adorned_user.posts.build :title => "second"
36
+ post.should be_a_new_record
37
+ adorned_user.save!
38
+ post.should_not be_a_new_record
39
+ adorned_user.posts.find(post.id).should == post
40
+ end
41
+
42
+ it "works with #create" do
43
+ post = adorned_user.posts.create :title => "third"
44
+ post.should_not be_a_new_record
45
+ adorned_user.posts.find(post.id).should == post
46
+ end
47
+
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,52 @@
1
+ require "spec_helper"
2
+ require "schizo/facade"
3
+
4
+ module Schizo
5
+ module Facade
6
+
7
+ describe ClassBuilder do
8
+
9
+ let(:base) do
10
+ Class.new do
11
+ def self.name; "Foo"; end
12
+ end
13
+ end
14
+
15
+ let(:role) do
16
+ Module.new do
17
+ extend Role
18
+ extended do
19
+ @test_var = 1
20
+ end
21
+ end
22
+ end
23
+
24
+ let(:builder) do
25
+ ClassBuilder.new(base, role)
26
+ end
27
+
28
+ context "#initialize" do
29
+
30
+ it "sets base and role" do
31
+ builder.base.should == base
32
+ builder.role.should == role
33
+ end
34
+
35
+ end
36
+
37
+ context "#product" do
38
+
39
+ it "returns a facade class" do
40
+ builder.product.tap do |facade|
41
+ facade.should be_a(Class)
42
+ facade.ancestors.should include(base)
43
+ facade.ancestors.should include(Base)
44
+ end
45
+ end
46
+
47
+ end
48
+
49
+ end
50
+
51
+ end
52
+ end
@@ -0,0 +1,56 @@
1
+ require "spec_helper"
2
+
3
+ module Schizo
4
+ module Facade
5
+
6
+ describe "a facade class" do
7
+
8
+ let(:base) do
9
+ Class.new do
10
+ def self.name; "Foo"; end
11
+ end
12
+ end
13
+
14
+ let(:role) do
15
+ Module.new do
16
+ extend Role
17
+ extended do
18
+ @test_var = 1
19
+ end
20
+ end
21
+ end
22
+
23
+ let(:builder) do
24
+ ClassBuilder.new(base, role)
25
+ end
26
+
27
+ let(:facade) do
28
+ builder.product
29
+ end
30
+
31
+ it "has the same #name as its superclass" do
32
+ facade.name.should == "Foo"
33
+ base.name.should == "Foo"
34
+ end
35
+
36
+ it "#initialize has arity == 2" do
37
+ base.instance_method(:initialize).arity.should < 2
38
+ facade.instance_method(:initialize).arity.should == 2
39
+ end
40
+
41
+ it "has class evaled the extended block of the role" do
42
+ facade.should be_instance_variable_defined(:@test_var)
43
+ end
44
+
45
+ it "is defined in Schizo::Facades" do
46
+ facade.should == Schizo::Facades.const_get(base.name).const_get("AnonRole#{role.object_id}")
47
+ end
48
+
49
+ it "is a singleton" do
50
+ facade.object_id.should == ClassBuilder.new(base, role).product.object_id
51
+ end
52
+
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,51 @@
1
+ require "spec_helper"
2
+
3
+ module Schizo
4
+ module Facade
5
+
6
+ describe(ObjectBuilder) do
7
+
8
+ let(:base) do
9
+ Class.new do
10
+ end
11
+ end
12
+
13
+ let(:role) do
14
+ Module.new do
15
+ extend Role
16
+ def bar(v)
17
+ @bar = v
18
+ end
19
+ end
20
+ end
21
+
22
+ let(:object) do
23
+ base.new
24
+ end
25
+
26
+ let(:builder) do
27
+ ObjectBuilder.new(object, role)
28
+ end
29
+
30
+ context "#initialize" do
31
+
32
+ it "sets object and role" do
33
+ builder.object.should == object
34
+ builder.role.should == role
35
+ end
36
+
37
+ end
38
+
39
+ context "#product" do
40
+
41
+ it "returns a facade object" do
42
+ builder.product.should be_a(base)
43
+ builder.product.should be_instance_of(base)
44
+ end
45
+
46
+ end
47
+
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,72 @@
1
+ require "spec_helper"
2
+
3
+ module Schizo
4
+ module Facade
5
+
6
+ describe "a facade object" do
7
+
8
+ let(:base) do
9
+ Class.new do
10
+ end
11
+ end
12
+
13
+ let(:role) do
14
+ Module.new do
15
+ extend Role
16
+ def bar(v)
17
+ @bar = v
18
+ end
19
+ end
20
+ end
21
+
22
+ let(:object) do
23
+ base.new
24
+ end
25
+
26
+ let(:builder) do
27
+ ObjectBuilder.new(object, role)
28
+ end
29
+
30
+ let(:facade) do
31
+ builder.product
32
+ end
33
+
34
+ it "is not the same as the original object" do
35
+ facade.should_not == object
36
+ end
37
+
38
+ it "responds to methods defined in its role" do
39
+ facade.should respond_to(:bar)
40
+ end
41
+
42
+ it "calling methods should not affect original object" do
43
+ facade.bar("test")
44
+ facade.instance_variable_get(:@bar).should == "test"
45
+ object.instance_variable_get(:@bar).should be_nil
46
+ end
47
+
48
+ context "#actualize" do
49
+
50
+ before(:all) do
51
+ facade.bar("blah")
52
+ facade.actualize
53
+ end
54
+
55
+ it "returns the original object" do
56
+ facade.actualize.should == object
57
+ end
58
+
59
+ it "sets instance variables in the original object" do
60
+ object.instance_variable_get(:@bar).should == "blah"
61
+ end
62
+
63
+ it "does not set any internal dci instance variables" do
64
+ object.instance_variable_defined?(Schizo::Facade::DCI_INSTANCE_VARIABLE).should be_false
65
+ end
66
+
67
+ end
68
+
69
+ end
70
+
71
+ end
72
+ end
@@ -0,0 +1,105 @@
1
+ require "spec_helper"
2
+
3
+ describe "a nested facade for ClassA with RoleA, RoleB and RoleC" do
4
+
5
+ let(:class_a) do
6
+ Class.new do
7
+ include Schizo::Data
8
+ attr_reader :foo, :bar, :baz
9
+ end
10
+ end
11
+
12
+ let(:role_a) do
13
+ Module.new do
14
+ extend Schizo::Role
15
+ def set_foo; @foo = "foo"; end
16
+ end
17
+ end
18
+
19
+ let(:role_b) do
20
+ Module.new do
21
+ extend Schizo::Role
22
+ def set_bar; @bar = "bar"; end
23
+ end
24
+ end
25
+
26
+ let(:role_c) do
27
+ Module.new do
28
+ extend Schizo::Role
29
+ def set_baz; @baz = "baz"; end
30
+ end
31
+ end
32
+
33
+ let(:object) do
34
+ class_a.new
35
+ end
36
+
37
+ let(:facade_a) do
38
+ object.as(role_a)
39
+ end
40
+
41
+ let(:facade_b) do
42
+ facade_a.as(role_b)
43
+ end
44
+
45
+ let(:facade_c) do
46
+ facade_b.as(role_c)
47
+ end
48
+
49
+ it "#instance_of?(ClassA) is true" do
50
+ facade_c.should be_instance_of(class_a)
51
+ end
52
+
53
+ it "#kind_of?(ClassA) is true" do
54
+ facade_c.should be_kind_of(class_a)
55
+ end
56
+
57
+ it "should have methods from RoleA, RoleB and RoleC" do
58
+ facade_a.should respond_to(:set_foo)
59
+ facade_a.should_not respond_to(:set_bar)
60
+ facade_a.should_not respond_to(:set_baz)
61
+
62
+ facade_b.should respond_to(:set_foo)
63
+ facade_b.should respond_to(:set_bar)
64
+ facade_a.should_not respond_to(:set_baz)
65
+
66
+ facade_c.should respond_to(:set_foo)
67
+ facade_c.should respond_to(:set_bar)
68
+ facade_c.should respond_to(:set_baz)
69
+ end
70
+
71
+ it "#actualize should walk up the chain" do
72
+ facade_c.set_foo
73
+ facade_c.set_bar
74
+ facade_c.set_baz
75
+
76
+ facade_c.foo.should == "foo"
77
+ facade_c.bar.should == "bar"
78
+ facade_c.baz.should == "baz"
79
+
80
+ facade_b.foo.should be_nil
81
+ facade_b.bar.should be_nil
82
+ facade_b.baz.should be_nil
83
+
84
+ facade_a.foo.should be_nil
85
+ facade_a.bar.should be_nil
86
+ facade_a.baz.should be_nil
87
+
88
+ facade_c.actualize
89
+
90
+ facade_b.foo.should == "foo"
91
+ facade_b.bar.should == "bar"
92
+ facade_b.baz.should == "baz"
93
+
94
+ facade_a.foo.should be_nil
95
+ facade_a.bar.should be_nil
96
+ facade_a.baz.should be_nil
97
+
98
+ facade_b.actualize
99
+
100
+ facade_a.foo.should == "foo"
101
+ facade_a.bar.should == "bar"
102
+ facade_a.baz.should == "baz"
103
+ end
104
+
105
+ end
@@ -0,0 +1,17 @@
1
+ require "schizo"
2
+ require "active_record"
3
+ require "pry"
4
+
5
+ ActiveRecord::Base.establish_connection(YAML::load(File.open('db/database.yml')))
6
+
7
+ class User < ActiveRecord::Base
8
+ include Schizo::Data
9
+ end
10
+
11
+ class Post < ActiveRecord::Base
12
+ include Schizo::Data
13
+ end
14
+
15
+ RSpec.configure do |config|
16
+ #config.mock_with :rr
17
+ end
metadata ADDED
@@ -0,0 +1,151 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: schizo
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Christopher J. Bottaro
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-02-09 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &70202402544160 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70202402544160
25
+ - !ruby/object:Gem::Dependency
26
+ name: activerecord
27
+ requirement: &70202402543320 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70202402543320
36
+ - !ruby/object:Gem::Dependency
37
+ name: sqlite3
38
+ requirement: &70202402542560 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70202402542560
47
+ - !ruby/object:Gem::Dependency
48
+ name: rdoc
49
+ requirement: &70202402541760 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70202402541760
58
+ - !ruby/object:Gem::Dependency
59
+ name: pry
60
+ requirement: &70202402706420 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *70202402706420
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: &70202402705740 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *70202402705740
80
+ description: DCI (data, context and interaction) for Ruby / Rails / ActiveRecord
81
+ email:
82
+ - cjbottaro@alumni.cs.utexas.edu
83
+ executables: []
84
+ extensions: []
85
+ extra_rdoc_files: []
86
+ files:
87
+ - .gitignore
88
+ - .travis.yml
89
+ - CHANGELOG
90
+ - Gemfile
91
+ - README.rdoc
92
+ - Rakefile
93
+ - db/database.yml
94
+ - db/migrate/001_create_users.rb
95
+ - db/migrate/002_create_posts.rb
96
+ - lib/schizo.rb
97
+ - lib/schizo/data.rb
98
+ - lib/schizo/facade.rb
99
+ - lib/schizo/facade/base.rb
100
+ - lib/schizo/facade/class_builder.rb
101
+ - lib/schizo/facade/object_builder.rb
102
+ - lib/schizo/role.rb
103
+ - lib/schizo/version.rb
104
+ - schizo.gemspec
105
+ - spec/active_record/attributes_spec.rb
106
+ - spec/active_record/has_many_spec.rb
107
+ - spec/facade/class_builder_spec.rb
108
+ - spec/facade/class_spec.rb
109
+ - spec/facade/object_builder_spec.rb
110
+ - spec/facade/object_spec.rb
111
+ - spec/nested_spec.rb
112
+ - spec/spec_helper.rb
113
+ homepage: https://github.com/cjbottaro/schizo
114
+ licenses: []
115
+ post_install_message:
116
+ rdoc_options: []
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ none: false
121
+ requirements:
122
+ - - ! '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ segments:
126
+ - 0
127
+ hash: -910995716218782308
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ segments:
135
+ - 0
136
+ hash: -910995716218782308
137
+ requirements: []
138
+ rubyforge_project: schizo
139
+ rubygems_version: 1.8.11
140
+ signing_key:
141
+ specification_version: 3
142
+ summary: DCI (data, context and interaction) for Ruby / Rails / ActiveRecord
143
+ test_files:
144
+ - spec/active_record/attributes_spec.rb
145
+ - spec/active_record/has_many_spec.rb
146
+ - spec/facade/class_builder_spec.rb
147
+ - spec/facade/class_spec.rb
148
+ - spec/facade/object_builder_spec.rb
149
+ - spec/facade/object_spec.rb
150
+ - spec/nested_spec.rb
151
+ - spec/spec_helper.rb