brassbound-dci 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.rdoc +78 -0
- data/Rakefile +1 -0
- data/brassbound-dci.gemspec +22 -0
- data/examples/money_transfer.rb +67 -0
- data/examples/wedding.rb +52 -0
- data/lib/brassbound/context.rb +163 -0
- data/lib/brassbound/util.rb +9 -0
- data/lib/brassbound/version.rb +3 -0
- data/lib/brassbound.rb +7 -0
- data/spec/context_spec.rb +90 -0
- data/spec/spec_helper.rb +2 -0
- metadata +72 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
= Brassbound
|
2
|
+
|
3
|
+
Brassbound is a simple but strict implementation of the Data, Context, and Interaction (DCI) paradigm for Ruby.
|
4
|
+
|
5
|
+
= Example
|
6
|
+
|
7
|
+
The canonical DCI example is the transfer of funds between a money source
|
8
|
+
and money sink. In this example, source and sink are both roles
|
9
|
+
that are attached to data objects, and the transfer of funds is orchestrated
|
10
|
+
by the context. Here's how the example looks using Brassbound.
|
11
|
+
|
12
|
+
require 'brassbound'
|
13
|
+
|
14
|
+
# Domain/data objects are plain old Ruby objects.
|
15
|
+
class Account
|
16
|
+
# Pretend to lookup accounts in a database.
|
17
|
+
def self.find(account_id)
|
18
|
+
case account_id
|
19
|
+
when 1
|
20
|
+
Account.new(account_id, 2000)
|
21
|
+
when 2
|
22
|
+
Account.new(account_id, 1000)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :id
|
27
|
+
attr_accessor :balance
|
28
|
+
|
29
|
+
def initialize(id, balance)
|
30
|
+
@id = id
|
31
|
+
@balance = balance
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Roles are plain old Ruby modules, and automatically have access to
|
36
|
+
# the invoking context.
|
37
|
+
module MoneySource
|
38
|
+
def transfer_out(amount)
|
39
|
+
puts("Transferring #{amount} from account #{self.id} to account #{context.money_sink.id}")
|
40
|
+
self.balance -= amount
|
41
|
+
puts("Source account new balance: #{self.balance}")
|
42
|
+
context.money_sink.transfer_in(amount)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
module MoneySink
|
47
|
+
def transfer_in(amount)
|
48
|
+
self.balance += amount
|
49
|
+
puts("Destination account new balance: #{self.balance}")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Contexts are Ruby classes that include the Brassbound::Context module.
|
54
|
+
# The common idiom is for the initialize method to create all of the
|
55
|
+
# necessary objects and declare how they are bound to roles.
|
56
|
+
# Then, within the scope of the execute method, the objects will have been
|
57
|
+
# bound to the declared roles, and can be accessed by the role name
|
58
|
+
# (convert to lower case with underscores by default).
|
59
|
+
class TransferFunds
|
60
|
+
include Brassbound::Context
|
61
|
+
|
62
|
+
def initialize(source_account_id, dest_account_id, amount)
|
63
|
+
@amount = amount
|
64
|
+
|
65
|
+
role MoneySource, Account.find(source_account_id)
|
66
|
+
role MoneySink, Account.find(dest_account_id)
|
67
|
+
end
|
68
|
+
|
69
|
+
def execute
|
70
|
+
# Here, money_source refers to the object bound to the MoneySource role
|
71
|
+
# in the initialize method.
|
72
|
+
money_source.transfer_out(@amount)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Now let's create and execute our context.
|
77
|
+
TransferFunds.new(1, 2, 100).call
|
78
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
$:.push File.expand_path("../lib", __FILE__)
|
4
|
+
require 'brassbound/version'
|
5
|
+
require 'rake'
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = "brassbound-dci"
|
9
|
+
s.version = Brassbound::VERSION
|
10
|
+
s.authors = ["Jason Voegele"]
|
11
|
+
s.email = ["jason@jvoegele.com"]
|
12
|
+
s.homepage = ""
|
13
|
+
s.summary = "Simple but strict DCI framework"
|
14
|
+
s.description = "Brassbound is a simple but strict implementation of the Data, Context, and Interaction (DCI) paradigm for Ruby."
|
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
|
+
s.add_development_dependency 'rspec', '~> 2.10.0'
|
22
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'brassbound'
|
2
|
+
|
3
|
+
# Domain/data objects are plain old Ruby objects.
|
4
|
+
class Account
|
5
|
+
# Pretend to lookup accounts in a database.
|
6
|
+
def self.find(account_id)
|
7
|
+
case account_id
|
8
|
+
when 1
|
9
|
+
Account.new(account_id, 2000)
|
10
|
+
when 2
|
11
|
+
Account.new(account_id, 1000)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :id
|
16
|
+
attr_accessor :balance
|
17
|
+
|
18
|
+
def initialize(id, balance)
|
19
|
+
@id = id
|
20
|
+
@balance = balance
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Roles are plain old Ruby modules, and automatically have access to
|
25
|
+
# the invoking context.
|
26
|
+
module MoneySource
|
27
|
+
def transfer_out(amount)
|
28
|
+
puts("Transferring #{amount} from account #{self.id} to account #{context.money_sink.id}")
|
29
|
+
self.balance -= amount
|
30
|
+
puts("Source account new balance: #{self.balance}")
|
31
|
+
context.money_sink.transfer_in(amount)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
module MoneySink
|
36
|
+
def transfer_in(amount)
|
37
|
+
self.balance += amount
|
38
|
+
puts("Destination account new balance: #{self.balance}")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Contexts are Ruby classes that include the Brassbound::Context module.
|
43
|
+
# The common idiom is for the initialize method to create all of the
|
44
|
+
# necessary objects and declare how they are bound to roles.
|
45
|
+
# Then, within the scope of the execute method, the objects will have been
|
46
|
+
# bound to the declared roles, and can be accessed by the role name
|
47
|
+
# (convert to lower case with underscores by default).
|
48
|
+
class TransferFunds
|
49
|
+
include Brassbound::Context
|
50
|
+
|
51
|
+
def initialize(source_account_id, dest_account_id, amount)
|
52
|
+
@amount = amount
|
53
|
+
|
54
|
+
role MoneySource, Account.find(source_account_id)
|
55
|
+
role MoneySink, Account.find(dest_account_id)
|
56
|
+
end
|
57
|
+
|
58
|
+
def execute
|
59
|
+
# Here, money_source refers to the object bound to the MoneySource role
|
60
|
+
# in the initialize method.
|
61
|
+
money_source.transfer_out(@amount)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Now let's create and execute our context.
|
66
|
+
TransferFunds.new(1, 2, 100).call
|
67
|
+
|
data/examples/wedding.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'brassbound'
|
2
|
+
|
3
|
+
class Person
|
4
|
+
attr_accessor :first_name, :last_name
|
5
|
+
|
6
|
+
def initialize(first_name, last_name)
|
7
|
+
@first_name = first_name
|
8
|
+
@last_name = last_name
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module WifeToBe
|
13
|
+
def marry(husband)
|
14
|
+
self.last_name = husband.last_name
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module HusbandToBe
|
19
|
+
def marry(wife)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module Minister
|
24
|
+
def administer_marraige
|
25
|
+
context.husband.marry(context.wife)
|
26
|
+
context.wife.marry(context.husband)
|
27
|
+
puts("I, #{first_name} #{last_name}, now pronounce you Mr. and Mrs. #{context.wife.last_name}")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# In a traditional wedding, a man and a woman are married by a minister.
|
32
|
+
class TraditionalWedding
|
33
|
+
include Brassbound::Context
|
34
|
+
|
35
|
+
def initialize(man_first_name, man_last_name,
|
36
|
+
woman_first_name, woman_last_name)
|
37
|
+
@man = Person.new(man_first_name, man_last_name)
|
38
|
+
@woman = Person.new(woman_first_name, woman_last_name)
|
39
|
+
@mark = Person.new('Mark', 'Schlafman')
|
40
|
+
|
41
|
+
role :husband, HusbandToBe, @man
|
42
|
+
role :wife, WifeToBe, @woman
|
43
|
+
role Minister, @mark
|
44
|
+
end
|
45
|
+
|
46
|
+
def execute
|
47
|
+
minister.administer_marraige
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
TraditionalWedding.new('Jason', 'Voegele', 'Jennifer', 'Bollinger').call
|
52
|
+
|
@@ -0,0 +1,163 @@
|
|
1
|
+
# This module represents the notion of the Context in the DCI paradigm.
|
2
|
+
#
|
3
|
+
# According to http://en.wikipedia.org/wiki/Data,_context_and_interaction
|
4
|
+
#
|
5
|
+
# The Context is the class (or its instance) whose code includes the roles
|
6
|
+
# for a given algorithm, scenario, or use case, as well as the code to map
|
7
|
+
# these roles into objects at run time and to enact the use case. Each role
|
8
|
+
# is bound to exactly one object during any given use case enactment; however,
|
9
|
+
# a single object may simultaneously play several roles. A context is
|
10
|
+
# instantiated at the beginning of the enactment of an algorithm, scenario,
|
11
|
+
# or use case. In summary, a Context comprises use cases and algorithms in
|
12
|
+
# which data objects are used through specific Roles.
|
13
|
+
#
|
14
|
+
# In Brassbound, context implementations are classes that include this module.
|
15
|
+
# Context implementations can use the #role method to bind roles to objects,
|
16
|
+
# and must provide an #execute method that enacts the use case behavior.
|
17
|
+
#
|
18
|
+
# Brassbound enforces the fact that each role is bound to exactly one object,
|
19
|
+
# since each role must have a unique name. However, any given object may
|
20
|
+
# simultaneously play several roles.
|
21
|
+
#
|
22
|
+
# Note that no special support is required for the role module or data object
|
23
|
+
# implementations; they are normal Ruby modules and objects, respectively.
|
24
|
+
# The context manages the necessary plumbing to bind roles to objects and to
|
25
|
+
# provide access to the context for the roles. In other words, role modules do
|
26
|
+
# not have to include any other modules or adhere to any particular conventions,
|
27
|
+
# and neither do the objects to which the roles are bound need to inherit from
|
28
|
+
# any other class or include any other modules.
|
29
|
+
module Brassbound::Context
|
30
|
+
|
31
|
+
require 'brassbound/util'
|
32
|
+
|
33
|
+
def self.current
|
34
|
+
Thread.current[:brassbound_context]
|
35
|
+
end
|
36
|
+
|
37
|
+
RoleMapping = Struct.new(:role_module, :data_object)
|
38
|
+
|
39
|
+
# A Hash containing all of the declared role mappings for this context.
|
40
|
+
# The keys of the Hash are the declared role names, while the values
|
41
|
+
# are the associated RoleMapping objects.
|
42
|
+
#
|
43
|
+
# Role mappings are declared using the #role method, which see for
|
44
|
+
# further details.
|
45
|
+
def roles
|
46
|
+
@declared_roles ||= Hash.new
|
47
|
+
end
|
48
|
+
|
49
|
+
# Declare a role mapping.
|
50
|
+
#
|
51
|
+
# This method accepts either two or three arguments. In the three argument
|
52
|
+
# form, the arguments are as follows:
|
53
|
+
#
|
54
|
+
# role_name:: The name of the role in this context.
|
55
|
+
# role_module:: The module that serves as the "methodful role".
|
56
|
+
# obj:: The data object to which the role_module will be attached.
|
57
|
+
#
|
58
|
+
# In the two argument form, the role_name is omitted and the role_name
|
59
|
+
# is derived from the name of the role_module by converting from
|
60
|
+
# CamelCaseName to underscore_name. For instance, if the role_name
|
61
|
+
# is not specified, then a role_module called MoneySource would be
|
62
|
+
# named money_source.
|
63
|
+
#
|
64
|
+
# This method adds the role mapping to the Hash returned by the #roles
|
65
|
+
# method. If the context is already executing when this method is
|
66
|
+
# invoked, it will call #apply_role to reify the role mapping
|
67
|
+
# immediately. Otherwise, all role mappings are applied when the
|
68
|
+
# context begins execution.
|
69
|
+
def role(*args)
|
70
|
+
case args.size
|
71
|
+
when 2
|
72
|
+
role_module = args[0]
|
73
|
+
obj = args[1]
|
74
|
+
role_name = Util.underscore(role_module.name).to_sym
|
75
|
+
when 3
|
76
|
+
role_name = args[0]
|
77
|
+
role_module = args[1]
|
78
|
+
obj = args[2]
|
79
|
+
else
|
80
|
+
raise ArgumentError
|
81
|
+
end
|
82
|
+
|
83
|
+
self.roles[role_name] = RoleMapping.new(role_module, obj)
|
84
|
+
|
85
|
+
if ::Brassbound::Context.current.equal?(self)
|
86
|
+
# If we are already in the execute method, apply the role mapping.
|
87
|
+
apply_role(role_name)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Apply the role identified by role_name to the associated object.
|
92
|
+
# This has several effects. First, the object is extended with the
|
93
|
+
# role module (i.e. the "methodful role" in DCI terminology), thus
|
94
|
+
# adding all of the role module's methods to the object. Second,
|
95
|
+
# the object has a :context method added to it, which provides
|
96
|
+
# the role implementation access to this context. Finally, a new
|
97
|
+
# method is added to this context object, which is named after the
|
98
|
+
# role_name and which returns the object which is mapped to that role.
|
99
|
+
def apply_role(role_name)
|
100
|
+
role_mapping = self.roles[role_name]
|
101
|
+
role_module, obj = role_mapping.role_module, role_mapping.data_object
|
102
|
+
obj.extend(role_module)
|
103
|
+
obj.instance_variable_set(:@__brassbound_context, self)
|
104
|
+
class << obj
|
105
|
+
def context
|
106
|
+
@__brassbound_context
|
107
|
+
end
|
108
|
+
end
|
109
|
+
self.singleton_class.send(:define_method, role_name) do
|
110
|
+
obj
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Undo (most of) the effects of #apply_role. This method will remove all of
|
115
|
+
# the methods that were added to the mapped data object by #apply role, but
|
116
|
+
# the fact that the object has been extended with the role module cannot be
|
117
|
+
# undone. Therefore, <tt>obj.kind_of?(role_module)</tt> will still be true,
|
118
|
+
# even though the methods defined in role_module will have been removed from
|
119
|
+
# the object.
|
120
|
+
def unapply_role(role_name)
|
121
|
+
role_mapping = self.roles[role_name]
|
122
|
+
role_module, obj = role_mapping.role_module, role_mapping.data_object
|
123
|
+
obj.instance_variable_set(:@__brassbound_context, nil)
|
124
|
+
class << obj
|
125
|
+
remove_method(:context)
|
126
|
+
end
|
127
|
+
role_module.instance_methods.each do |m|
|
128
|
+
obj.singleton_class.send(:undef_method, m)
|
129
|
+
end
|
130
|
+
self.singleton_class.send(:undef_method, role_name)
|
131
|
+
end
|
132
|
+
|
133
|
+
def undef_role(role_module, obj)
|
134
|
+
obj.instance_variable_set(:@__brassbound_context, nil)
|
135
|
+
class << obj
|
136
|
+
remove_method(:context)
|
137
|
+
end
|
138
|
+
role_module.instance_methods.each do |m|
|
139
|
+
obj.singleton_class.send(:undef_method, m)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Begin execution of this context. Note that context implementations must
|
144
|
+
# provide an #execute method, and should not override this #call method.
|
145
|
+
# This method will first set Context.current to self, apply all of the
|
146
|
+
# role mappings that have been declared with the #role method, and then
|
147
|
+
# call the #execute method.
|
148
|
+
#
|
149
|
+
# After the #execute method has returned, it will unapply all role mappings
|
150
|
+
# and set the current context back to its previous value.
|
151
|
+
def call(*args)
|
152
|
+
old_context = ::Brassbound::Context.current
|
153
|
+
Thread.current[:brassbound_context] = self
|
154
|
+
|
155
|
+
roles.each_key(&method(:apply_role))
|
156
|
+
begin
|
157
|
+
self.execute(*args)
|
158
|
+
ensure
|
159
|
+
roles.each_key(&method(:unapply_role))
|
160
|
+
Thread.current[:brassbound_context] = old_context
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
data/lib/brassbound.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
include Brassbound
|
3
|
+
|
4
|
+
class Account
|
5
|
+
def self.find(account_id)
|
6
|
+
case account_id
|
7
|
+
when 1
|
8
|
+
Account.new(200)
|
9
|
+
when 2
|
10
|
+
Account.new(100)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_accessor :balance
|
15
|
+
|
16
|
+
def initialize(balance)
|
17
|
+
@balance = balance
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module MoneySource
|
22
|
+
def transfer_out(amount)
|
23
|
+
self.balance -= amount
|
24
|
+
context.dest_account.transfer_in(amount)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
module MoneySink
|
29
|
+
def transfer_in(amount)
|
30
|
+
self.balance += amount
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class TransferFunds
|
35
|
+
include Context
|
36
|
+
|
37
|
+
def initialize(source_account_id, dest_account_id, amount)
|
38
|
+
@source_account_id = source_account_id
|
39
|
+
@dest_account_id = dest_account_id
|
40
|
+
@amount = amount
|
41
|
+
|
42
|
+
role :source_account, MoneySource, Account.find(@source_account_id)
|
43
|
+
role :dest_account, MoneySink, Account.find(@dest_account_id)
|
44
|
+
end
|
45
|
+
|
46
|
+
def execute
|
47
|
+
source_account.transfer_out(@amount)
|
48
|
+
# Allow spec to access the executing context for testing
|
49
|
+
yield self if block_given?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe Context do
|
54
|
+
include Context
|
55
|
+
|
56
|
+
let(:source_account_id) { 1 }
|
57
|
+
let(:dest_account_id) { 2 }
|
58
|
+
let(:source_account) { Account.find(source_account_id) }
|
59
|
+
let(:dest_account) { Account.find(dest_account_id) }
|
60
|
+
|
61
|
+
let(:transfer_funds_context) {
|
62
|
+
TransferFunds.new(source_account_id, dest_account_id, 50)
|
63
|
+
}
|
64
|
+
|
65
|
+
context "#role" do
|
66
|
+
it "declares a role mapping" do
|
67
|
+
role MoneySource, source_account
|
68
|
+
mapping = roles[:money_source]
|
69
|
+
mapping.role_module.should == MoneySource
|
70
|
+
mapping.data_object.should == source_account
|
71
|
+
|
72
|
+
role :money_sink, MoneySink, dest_account
|
73
|
+
mapping = roles[:money_sink]
|
74
|
+
mapping.role_module.should == MoneySink
|
75
|
+
mapping.data_object.should == dest_account
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context "#call" do
|
80
|
+
it "binds all roles to their mapped objects" do
|
81
|
+
transfer_funds_context.call do |ctx|
|
82
|
+
ctx.source_account.should == source_account
|
83
|
+
ctx.source_account.should be_kind_of(MoneySource)
|
84
|
+
ctx.dest_account.should == dest_account
|
85
|
+
ctx.dest_account.should be_kind_of(MoneySink)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: brassbound-dci
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.5.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jason Voegele
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-05-05 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &70190658979080 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.10.0
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70190658979080
|
25
|
+
description: Brassbound is a simple but strict implementation of the Data, Context,
|
26
|
+
and Interaction (DCI) paradigm for Ruby.
|
27
|
+
email:
|
28
|
+
- jason@jvoegele.com
|
29
|
+
executables: []
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files: []
|
32
|
+
files:
|
33
|
+
- .gitignore
|
34
|
+
- Gemfile
|
35
|
+
- README.rdoc
|
36
|
+
- Rakefile
|
37
|
+
- brassbound-dci.gemspec
|
38
|
+
- examples/money_transfer.rb
|
39
|
+
- examples/wedding.rb
|
40
|
+
- lib/brassbound.rb
|
41
|
+
- lib/brassbound/context.rb
|
42
|
+
- lib/brassbound/util.rb
|
43
|
+
- lib/brassbound/version.rb
|
44
|
+
- spec/context_spec.rb
|
45
|
+
- spec/spec_helper.rb
|
46
|
+
homepage: ''
|
47
|
+
licenses: []
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ! '>='
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ! '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
requirements: []
|
65
|
+
rubyforge_project:
|
66
|
+
rubygems_version: 1.8.15
|
67
|
+
signing_key:
|
68
|
+
specification_version: 3
|
69
|
+
summary: Simple but strict DCI framework
|
70
|
+
test_files:
|
71
|
+
- spec/context_spec.rb
|
72
|
+
- spec/spec_helper.rb
|