dci 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.ruby +43 -0
- data/.yardopts +7 -0
- data/COPYING.md +36 -0
- data/HISTORY.md +10 -0
- data/README.md +32 -0
- data/demo/account_example.md +74 -0
- data/demo/applique/dci.rb +1 -0
- data/lib/dci.rb +38 -0
- data/lib/dci/context.rb +89 -0
- data/lib/dci/object.rb +47 -0
- data/lib/dci/role.rb +53 -0
- metadata +82 -0
data/.ruby
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
---
|
2
|
+
source:
|
3
|
+
- Profile
|
4
|
+
authors:
|
5
|
+
- name: Thomas Sawyer
|
6
|
+
email: transfire@gmail.com
|
7
|
+
copyrights:
|
8
|
+
- holder: Thomas Sawyer
|
9
|
+
year: '2012'
|
10
|
+
license: BSD-2-Clause
|
11
|
+
replacements: []
|
12
|
+
alternatives: []
|
13
|
+
requirements:
|
14
|
+
- name: detroit
|
15
|
+
groups:
|
16
|
+
- build
|
17
|
+
development: true
|
18
|
+
- name: qed
|
19
|
+
groups:
|
20
|
+
- test
|
21
|
+
development: true
|
22
|
+
dependencies: []
|
23
|
+
conflicts: []
|
24
|
+
repositories:
|
25
|
+
- uri: git@github.com:rubyworks/dci.git
|
26
|
+
scm: git
|
27
|
+
name: upstream
|
28
|
+
resources:
|
29
|
+
home: https://rubyworks.github.com/dci
|
30
|
+
code: https://github.com/rubyworks/dci
|
31
|
+
mail: http://groups.google.com/groups/rubyworks-mailinglist
|
32
|
+
extra: {}
|
33
|
+
load_path:
|
34
|
+
- lib
|
35
|
+
revision: 0
|
36
|
+
version: 0.1.0
|
37
|
+
name: dci
|
38
|
+
title: DCI
|
39
|
+
summary: DCI for Ruby
|
40
|
+
created: '2011-03-04'
|
41
|
+
description: Faithful DCI framework for Ruby application development.
|
42
|
+
organization: Rubyworks
|
43
|
+
date: '2012-02-07'
|
data/.yardopts
ADDED
data/COPYING.md
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# COPYRIGHT
|
2
|
+
|
3
|
+
## NOTICES
|
4
|
+
|
5
|
+
### Assay
|
6
|
+
|
7
|
+
| Project | Assay |
|
8
|
+
|-----------|-----------------------------------|
|
9
|
+
| Copyright | (c) 2012 Rubyworks |
|
10
|
+
| License | (r) BSD-2-Clause |
|
11
|
+
| Website | http://rubyworks.github.com/assay |
|
12
|
+
|
13
|
+
## LICENSES
|
14
|
+
|
15
|
+
### BSD-2-Clause License
|
16
|
+
|
17
|
+
Redistribution and use in source and binary forms, with or without
|
18
|
+
modification, are permitted provided that the following conditions are met:
|
19
|
+
|
20
|
+
1. Redistributions of source code must retain the above copyright notice,
|
21
|
+
this list of conditions and the following disclaimer.
|
22
|
+
|
23
|
+
2. Redistributions in binary form must reproduce the above copyright
|
24
|
+
notice, this list of conditions and the following disclaimer in the
|
25
|
+
documentation and/or other materials provided with the distribution.
|
26
|
+
|
27
|
+
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
28
|
+
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
29
|
+
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
30
|
+
COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
31
|
+
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
32
|
+
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
33
|
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
34
|
+
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
35
|
+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
36
|
+
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/HISTORY.md
ADDED
data/README.md
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# DCI for Ruby
|
2
|
+
|
3
|
+
[Home](http://rubyworks.github.com/dci) /
|
4
|
+
[Code](http://github.com/rubyworks/dci) /
|
5
|
+
[Bugs](http://github.com/rubyworks/dci/issues) /
|
6
|
+
[Mail](http://groups.google.com/groups/rubyworks-mailinglist)
|
7
|
+
|
8
|
+
|
9
|
+
## Description
|
10
|
+
|
11
|
+
The DCI library for Ruby is a fairly faithful implementation of the DCI
|
12
|
+
concpets developed by Trygve Reenskaug, Reenskaug and James O. Coplien.
|
13
|
+
|
14
|
+
It define two reusable base classes, the Role and Context. The best way
|
15
|
+
to understand their usage is to look at the QED documentation provided
|
16
|
+
([for example](https://github.com/rubyworks/dci/blob/master/demo/account_example.md)).
|
17
|
+
|
18
|
+
|
19
|
+
## Installation
|
20
|
+
|
21
|
+
The ususal RubyGems install procedure:
|
22
|
+
|
23
|
+
$ gem install dci
|
24
|
+
|
25
|
+
|
26
|
+
## Copyrights
|
27
|
+
|
28
|
+
Copyright (c) 2012 Rubyworks. All rights reserved.
|
29
|
+
|
30
|
+
DCI for Ruby is distributable under the terms of **BSD-2-Clause** license.
|
31
|
+
|
32
|
+
See COPYING.md file for license details.
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# Account Balance Transfer
|
2
|
+
|
3
|
+
The Account Balance Transfre is the classic example of using DCI.
|
4
|
+
|
5
|
+
First we need our Data model. In the example that is the Account class.
|
6
|
+
To keep our example simple we will initialize new accounts with
|
7
|
+
a balance of $100. (We're generous like that.)
|
8
|
+
|
9
|
+
class Account
|
10
|
+
def initialize(account_id)
|
11
|
+
@account_id = account_id
|
12
|
+
@balance = 100
|
13
|
+
end
|
14
|
+
def account_id
|
15
|
+
@account_id
|
16
|
+
end
|
17
|
+
def available_balance
|
18
|
+
@balance
|
19
|
+
end
|
20
|
+
def increase_balance(amount)
|
21
|
+
@balance += amount
|
22
|
+
end
|
23
|
+
def decrease_balance(amount)
|
24
|
+
@balance -= amount
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
We set up two Roles, one role for withdrawing money from an account,
|
29
|
+
and one for depositing money into an account.
|
30
|
+
|
31
|
+
class Account::TransferWithdraw < Role
|
32
|
+
def transfer(amount)
|
33
|
+
decrease_balance(amount)
|
34
|
+
#log "Tranfered $#{amount} from account ##{account_id}."
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class Account::TransferDeposit < Role
|
39
|
+
def transfer(amount)
|
40
|
+
increase_balance(amount)
|
41
|
+
#log "Tranfered $#{amount} into account ##{account_id}."
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
Now we create a Context which will assign accounts to the roles
|
46
|
+
and used to perfomr the transfer.
|
47
|
+
|
48
|
+
# We can think of a context as setting a scene.
|
49
|
+
class Account::Transfer < Context
|
50
|
+
role :source_account => Account::TransferWithdraw
|
51
|
+
role :destination_account => Account::TransferDeposit
|
52
|
+
|
53
|
+
def initialize(source_account, destination_account)
|
54
|
+
self.source_account = source_account
|
55
|
+
self.destination_account = destination_account
|
56
|
+
end
|
57
|
+
|
58
|
+
def transfer(amount)
|
59
|
+
#log "Begin transfer."
|
60
|
+
roles.each{ |role| role.transfer(amount) }
|
61
|
+
#log "Transfer complete."
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
Let's give it a try.
|
66
|
+
|
67
|
+
acct1 = Account.new(000100)
|
68
|
+
acct2 = Account.new(000200)
|
69
|
+
|
70
|
+
Account::Transfer.new(acct1, acct2).transfer(50)
|
71
|
+
|
72
|
+
acct1.available_balance #=> 50
|
73
|
+
acct2.available_balance #=> 150
|
74
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'dci'
|
data/lib/dci.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# Data, Context and Interaction (DCI) is a paradigm used in computer software to
|
2
|
+
# program systems of communicating objects. Its goals are:
|
3
|
+
#
|
4
|
+
# * To improve the readability of object-oriented code by giving system behavior
|
5
|
+
# first-class status;
|
6
|
+
#
|
7
|
+
# * To cleanly separate code for rapidly changing system behavior (what the system does)
|
8
|
+
# from code for slowly changing domain knowledge (what the system is), instead
|
9
|
+
# of combining both in one class interface;
|
10
|
+
#
|
11
|
+
# * To help software developers reason about system-level state and behavior
|
12
|
+
# instead of only object state and behavior;
|
13
|
+
#
|
14
|
+
# * To support an object style of thinking that is close to peoples' mental
|
15
|
+
# models, rather than the class style of thinking that overshadowed object
|
16
|
+
# thinking early in the history of object-oriented programming languages.
|
17
|
+
#
|
18
|
+
# The paradigm separates the domain model (data) from use cases (context) and roles
|
19
|
+
# that objects play (interaction). DCI is complementary to model–view–controller (MVC).
|
20
|
+
# MVC as a pattern language is still used to separate the data and its processing from
|
21
|
+
# presentation.
|
22
|
+
#
|
23
|
+
# DCI was invented by Trygve Reenskaug, also the inventor of MVC. The current formulation
|
24
|
+
# of DCI is mostly the work of Reenskaug and James O. Coplien.
|
25
|
+
#
|
26
|
+
# acct1 = Account.new(10500)
|
27
|
+
# acct2 = Account.new(10010)
|
28
|
+
#
|
29
|
+
# Balance::Transfer.new(acct1, acct2).transfer(50)
|
30
|
+
#
|
31
|
+
module DCI
|
32
|
+
end
|
33
|
+
|
34
|
+
require 'dci/object' # data
|
35
|
+
require 'dci/context' # context
|
36
|
+
require 'dci/role' # interation
|
37
|
+
|
38
|
+
|
data/lib/dci/context.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
# Context - The Context is the class (or its instance) whose code includes the roles
|
2
|
+
# for a given algorithm, scenario, or use case, as well as the code to map these
|
3
|
+
# roles into objects at run time and to enact the use case. Each role is bound
|
4
|
+
# to exactly one object during any given use case enactment; however, a single
|
5
|
+
# object may simultaneously play several roles. A context is instantiated at the
|
6
|
+
# beginning of the enactment of an algorithm, scenario, or use case. In summary,
|
7
|
+
# a Context comprises use cases and algorithms in which data objects are used
|
8
|
+
# through specific Roles.
|
9
|
+
#
|
10
|
+
# Each context represents one or more use cases. A context object is instantiated
|
11
|
+
# for each enactment of a use case for which it is responsible. Its main job
|
12
|
+
# is to identify the objects that will participate in the use case and to
|
13
|
+
# assign them to play the Roles which carry out the use case through their
|
14
|
+
# responsibilities. A role may comprise methods, and each method is some small
|
15
|
+
# part of the logic of an algorithm implementing a use case. Role methods run
|
16
|
+
# in the context of an object that is selected by the context to play that role
|
17
|
+
# for the current use case enactment. The role-to-object bindings that take place
|
18
|
+
# in a context can be contrasted with the polymorphism of vernacular object-oriented
|
19
|
+
# programming. The overall business functionality is the sum of complex, dynamic
|
20
|
+
# networks of methods decentralized in multiple contexts and their roles.
|
21
|
+
#
|
22
|
+
# Each context is a scope that includes identifiers that correspond to its roles.
|
23
|
+
# Any role executing within that context can refer to the other roles in that
|
24
|
+
# context through these identifiers. These identifiers have come to be called
|
25
|
+
# methodless roles. At use case enactment time, each and every one of these
|
26
|
+
# identifiers becomes bound to an object playing the corresponding Role for
|
27
|
+
# this Context.
|
28
|
+
#
|
29
|
+
# An example of a context could be a wire transfer between two accounts,
|
30
|
+
# where data models (the banking accounts) are used through roles named
|
31
|
+
# SourceAccount and # DestinationAccount.
|
32
|
+
#
|
33
|
+
# class Account::Transfer < Context
|
34
|
+
# role :source_account => Account::TransferWithdraw
|
35
|
+
# role :destination_account => Account::TransferDeposit
|
36
|
+
#
|
37
|
+
# def initialize(source_account, destination_account)
|
38
|
+
# self.source_account = source_account
|
39
|
+
# self.destination_account = destination_account
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# def transfer(amount)
|
43
|
+
# roles.each{ |role| role.transfer(amount) }
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
class Context
|
48
|
+
|
49
|
+
# Define a role given the name the role will use in this context,
|
50
|
+
# and the role class that is to be played.
|
51
|
+
#
|
52
|
+
def self.role(name_to_role)
|
53
|
+
name_to_role.each do |name, role|
|
54
|
+
define_method("role_#{name}"){ role }
|
55
|
+
|
56
|
+
module_eval %{
|
57
|
+
def #{name}=(data)
|
58
|
+
@#{name} = role_#{name}.new(data)
|
59
|
+
end
|
60
|
+
|
61
|
+
def #{name}
|
62
|
+
@#{name}
|
63
|
+
end
|
64
|
+
}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# The default contructor can be used to assign roles via
|
69
|
+
# a settings hash.
|
70
|
+
#
|
71
|
+
def initialize(settings={})
|
72
|
+
settings.each do |k,v|
|
73
|
+
__send__("#{k}=", v)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns a list of all roles in the context.
|
78
|
+
#
|
79
|
+
# @todo Return value should probably be cached.
|
80
|
+
def roles
|
81
|
+
list = []
|
82
|
+
methods.each do |name|
|
83
|
+
next unless name.to_s.start_with?('role_')
|
84
|
+
list << __send__(name.to_s.sub('role_',''))
|
85
|
+
end
|
86
|
+
list
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
data/lib/dci/object.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# Data - The data are "what the system is." The data part of the DCI architecture
|
2
|
+
# is its (relatively) static data model with relations. The data design is usually
|
3
|
+
# coded up as conventional classes that represent the basic domain structure of
|
4
|
+
# the system. These classes are barely smart data, and they explicitly lack the
|
5
|
+
# functionality that is peculiar to support of any particular use case. These
|
6
|
+
# classes commonly encapsulate the physical storage of the data. These data
|
7
|
+
# implement an information structure that comes from the mental model of end
|
8
|
+
# users, domain experts, programmers, and other people in the system. They may
|
9
|
+
# correspond closely to the model objects of MVC.
|
10
|
+
#
|
11
|
+
# An example of a data object could be a bank account. Its interface would have
|
12
|
+
# basic operations for increasing and decreasing the balance and for inquiring
|
13
|
+
# about the current balance. The interface would likely not offer operations that
|
14
|
+
# involve transactions, or which in any way involve other objects or any user
|
15
|
+
# interaction. So, for example, while a bank account may offer a primitive for
|
16
|
+
# increasing the balance, it would have no method called deposit. Such operations
|
17
|
+
# belong instead in the interaction part of DCI.
|
18
|
+
#
|
19
|
+
# Data objects are instances of classes that might come from domain-driven design,
|
20
|
+
# and such classes might use subtyping relationships to organize domain data.
|
21
|
+
# Though it reduces to classes in the end, DCI reflects a computational model
|
22
|
+
# dominated by object thinking rather than class thinking. Therefore, when
|
23
|
+
# thinking "data" in DCI, it means thinking more about the instances at run time
|
24
|
+
# than about the classes from which they were instantiated.
|
25
|
+
#
|
26
|
+
# class Account
|
27
|
+
# def initialize(accountId)
|
28
|
+
# @account_id = accountId
|
29
|
+
# @balance = 0
|
30
|
+
# end
|
31
|
+
# def account_id
|
32
|
+
# @account_id
|
33
|
+
# end
|
34
|
+
# def available_balance
|
35
|
+
# @balance
|
36
|
+
# end
|
37
|
+
# def increase_balance(amount)
|
38
|
+
# @balance += amount
|
39
|
+
# end
|
40
|
+
# def decrease_balance(amount)
|
41
|
+
# @balance -= amount
|
42
|
+
# end
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# @todo Should objects track the roles in which they are presently particiapting?
|
46
|
+
class Object
|
47
|
+
end
|
data/lib/dci/role.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# Interaction - The Interaction is "what the system does." The Interaction
|
2
|
+
# is implemented as Roles which are played by objects at run time. These objects
|
3
|
+
# combine the state and methods of a Data (domain) object with methods (but no
|
4
|
+
# state, as Roles are stateless) from one or more Roles. In good DCI style,
|
5
|
+
# a Role addresses another object only in terms of its (methodless) Role. There
|
6
|
+
# is a special Role called `@self` which binds to the object playing the current
|
7
|
+
# Role. Code within a Role method may invoke a method on `@self` and thereby
|
8
|
+
# invoke a method of the Data part of the current object. One curious aspect
|
9
|
+
# of DCI is that these bindings are guaranteed to be in place only at run time
|
10
|
+
# (using a variety of approaches and conventions; C++ templates can be used to
|
11
|
+
# guarantee that the bindings will succeed). This means that Interactions—the
|
12
|
+
# Role methods—are generic. In fact, some DCI implementations use generics or
|
13
|
+
# templates for Roles.
|
14
|
+
#
|
15
|
+
# A Role is a stateless programming construct that corresponds to the end user's
|
16
|
+
# mental model of some entity in the system. A Role represents a collection of
|
17
|
+
# responsibilities. Whereas vernacular object-oriented programming speaks of
|
18
|
+
# objects or classes as the loci of responsibilities, DCI ascribes them to Roles.
|
19
|
+
# An object participating in a use case has responsibilities: those that it takes
|
20
|
+
# on as a result of playing a particular Role.
|
21
|
+
#
|
22
|
+
# In the money transfer use case, for example, the role methods in the
|
23
|
+
# SourceAccount and DestinationAccount enact the actual transfer.
|
24
|
+
#
|
25
|
+
# class Account::TransferWithdraw < Role
|
26
|
+
# def transfer(amount)
|
27
|
+
# decrease_balance(amount)
|
28
|
+
# log "Tranfered from account #{account_id} $#{amount}"
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# class Account::TransferDepoit < Role
|
33
|
+
# def transfer(amount)
|
34
|
+
# increase_balance(amount)
|
35
|
+
# log "Tranfered into account #{account_id} $#{amount}"
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
class Role
|
40
|
+
|
41
|
+
#
|
42
|
+
def initialize(player)
|
43
|
+
@self = player
|
44
|
+
end
|
45
|
+
|
46
|
+
#
|
47
|
+
# @todo Should use #public_send?
|
48
|
+
def method_missing(s, *a, &b)
|
49
|
+
@self.__send__(s, *a, &b)
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dci
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Thomas Sawyer
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-07 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: detroit
|
16
|
+
requirement: &12149120 !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: *12149120
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: qed
|
27
|
+
requirement: &12148360 !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: *12148360
|
36
|
+
description: Faithful DCI framework for Ruby application development.
|
37
|
+
email:
|
38
|
+
- transfire@gmail.com
|
39
|
+
executables: []
|
40
|
+
extensions: []
|
41
|
+
extra_rdoc_files:
|
42
|
+
- COPYING.md
|
43
|
+
- HISTORY.md
|
44
|
+
- README.md
|
45
|
+
files:
|
46
|
+
- .ruby
|
47
|
+
- .yardopts
|
48
|
+
- demo/account_example.md
|
49
|
+
- demo/applique/dci.rb
|
50
|
+
- lib/dci/context.rb
|
51
|
+
- lib/dci/object.rb
|
52
|
+
- lib/dci/role.rb
|
53
|
+
- lib/dci.rb
|
54
|
+
- COPYING.md
|
55
|
+
- HISTORY.md
|
56
|
+
- README.md
|
57
|
+
homepage: https://rubyworks.github.com/dci
|
58
|
+
licenses:
|
59
|
+
- BSD-2-Clause
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options: []
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
requirements: []
|
77
|
+
rubyforge_project:
|
78
|
+
rubygems_version: 1.8.11
|
79
|
+
signing_key:
|
80
|
+
specification_version: 3
|
81
|
+
summary: DCI for Ruby
|
82
|
+
test_files: []
|