extensional 1.3.1
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/History.txt +4 -0
- data/LEGAL +5 -0
- data/LICENSE +22 -0
- data/README.md +76 -0
- data/lib/extensional.rb +45 -0
- data/lib/extensional/class.rb +68 -0
- data/lib/extensional/extent.rb +48 -0
- data/test/lib/extensional_test.rb +105 -0
- metadata +74 -0
data/History.txt
ADDED
data/LEGAL
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2010 Oregon Health & Sciences University Knight Cancer Institute
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
extensional: Class extent collector
|
2
|
+
===================================
|
3
|
+
|
4
|
+
**Git**: [http://github.com/caruby/extensional](http://github.com/caruby/extensional)
|
5
|
+
**Author**: OHSU Knight Cancer Institute
|
6
|
+
**Copyright**: 2010
|
7
|
+
**License**: MIT License
|
8
|
+
**Latest Version**: 1.3.1
|
9
|
+
**Release Date**: September 30th 2010
|
10
|
+
|
11
|
+
Synopsis
|
12
|
+
--------
|
13
|
+
|
14
|
+
The extensional gem adds the ability of a Class to collect its instances.
|
15
|
+
An extensional class is an Enumerable whose each method iterates over its instances.
|
16
|
+
An optional callback block enables associative access to instances by key.
|
17
|
+
|
18
|
+
Feature List
|
19
|
+
------------
|
20
|
+
|
21
|
+
1. Add instances to a Class extent.
|
22
|
+
|
23
|
+
2. Associative access by a designated key.
|
24
|
+
|
25
|
+
3. Custom unit definition.
|
26
|
+
|
27
|
+
4. Measurement parser.
|
28
|
+
|
29
|
+
Installing
|
30
|
+
----------
|
31
|
+
|
32
|
+
To install the extensional gem, use the following command:
|
33
|
+
|
34
|
+
$ gem install extensional
|
35
|
+
|
36
|
+
(Add `sudo` if you're installing under a POSIX system as root)
|
37
|
+
|
38
|
+
Alternatively, if you've checked the source out directly, you can call
|
39
|
+
`rake install` from the root project directory.
|
40
|
+
|
41
|
+
Usage
|
42
|
+
-----
|
43
|
+
|
44
|
+
An extensional class is an Enumerable whose <tt>each</tt> method iterates over its instances, e.g.:
|
45
|
+
|
46
|
+
class Person
|
47
|
+
make_extensional
|
48
|
+
end
|
49
|
+
Employee.new(20195) #=> Employee@2653
|
50
|
+
Employee.to_a #=> [Employee@2653]
|
51
|
+
|
52
|
+
An optional callback block adds associative access to instances by key using the class <tt>for</tt>
|
53
|
+
method, e.g.:
|
54
|
+
|
55
|
+
class Employee
|
56
|
+
make_extensional { |hash, emp| hash[emp.number] = emp }
|
57
|
+
...
|
58
|
+
end
|
59
|
+
Employee.for(20196) #=> nil
|
60
|
+
Employee.new(20196) #=> Employee@2654
|
61
|
+
Employee.for(20196) #=> Employee@2654
|
62
|
+
|
63
|
+
The Class.make_extensional RDoc includes additional examples.
|
64
|
+
|
65
|
+
Changelog
|
66
|
+
---------
|
67
|
+
|
68
|
+
- **September.30.10**: 2010.1 release
|
69
|
+
- Initial public release
|
70
|
+
|
71
|
+
Copyright
|
72
|
+
---------
|
73
|
+
|
74
|
+
extensional © 2010 by [Oregon Health & Sciences University](mailto:loneyf@ohsu.edu).
|
75
|
+
extensional is licensed under the MIT license. Please see the LICENSE and LEGAL
|
76
|
+
files for more information.
|
data/lib/extensional.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'set'
|
3
|
+
require 'extensional/class'
|
4
|
+
|
5
|
+
# An Extensional Class collects its instances.
|
6
|
+
# See Class#make_extensional for the usage model.
|
7
|
+
module Extensional
|
8
|
+
include Enumerable
|
9
|
+
|
10
|
+
# This Extensional implementation version.
|
11
|
+
VERSION = '1.3.1'
|
12
|
+
|
13
|
+
# Returns this Extensional class's Extent.
|
14
|
+
def extent
|
15
|
+
@extent
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns the instance with the given key arguments, or nil if no association is found.
|
19
|
+
#
|
20
|
+
# Raises NotImplementedError if the class extent does not implement associative access.
|
21
|
+
def for(*key)
|
22
|
+
unless @extent.associative? then
|
23
|
+
raise NotImplementedError.new("Associative access by key is not implemented for #{self}")
|
24
|
+
end
|
25
|
+
# extract the key argument from a key array with zero or one members;
|
26
|
+
# if key has more than one member, the key remains an array of all arguments
|
27
|
+
key = key.first if key.size == 1
|
28
|
+
@extent.find(key)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Adds an instance of this class to the class extent.
|
32
|
+
def <<(instance)
|
33
|
+
@extent << instance
|
34
|
+
end
|
35
|
+
|
36
|
+
# Calls block an each instance in this class's extent.
|
37
|
+
def each(&block)
|
38
|
+
@extent.each(&block)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Clears this class's extent.
|
42
|
+
def clear
|
43
|
+
@extent.clear
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'extensional/extent'
|
2
|
+
|
3
|
+
class Class
|
4
|
+
# Adds the ability of a Class to collect its instances. An extensional class is an Enumerable
|
5
|
+
# whose +each+ method iterates over its instances, e.g.:
|
6
|
+
# class Person
|
7
|
+
# make_extensional
|
8
|
+
# def initialize(ssn)
|
9
|
+
# @ssn = ssn
|
10
|
+
# Person << self
|
11
|
+
# end
|
12
|
+
# def to_s
|
13
|
+
# "#{self.class.name}{ssn=>#{@ssn}}"
|
14
|
+
# end
|
15
|
+
# ...
|
16
|
+
# end
|
17
|
+
# Person.new('555-55-5555')
|
18
|
+
# Person.join(', ') #=> Person{ssn=>555-55-5555}
|
19
|
+
#
|
20
|
+
# The optional callback block adds associative access to instances by key using the class #for method.
|
21
|
+
# The callback block is called when an instance is added to the extent. The block arguments include the
|
22
|
+
# key=>instance association hash and the new instance, e.g.:
|
23
|
+
# class Person
|
24
|
+
# make_extensional { |hash, person| hash[person.ssn] = person }
|
25
|
+
# ...
|
26
|
+
# end
|
27
|
+
# Person.for('555-55-5555') #=> nil
|
28
|
+
# Person.new('555-55-5555')
|
29
|
+
# Person.for('555-55-5555') #=> Person{ssn=>555-55-5555}
|
30
|
+
#
|
31
|
+
# The optional hash argument is the association hash. If the association hash is created with a block,
|
32
|
+
# then the instance is added to the hash on demand when the #for method is called in accordance with
|
33
|
+
# the standard Hash#new behavior, e.g.:
|
34
|
+
# class Person
|
35
|
+
# make_extensional(Hash.new { |hash, ssn| hash[snn] = new(ssn) })
|
36
|
+
# ...
|
37
|
+
# end
|
38
|
+
# Person.for('555-55-5555') #=> Person{ssn=>555-55-5555}
|
39
|
+
# Person.join(', ') #=> Person{ssn=>555-55-5555}
|
40
|
+
# Person.new('666-66-6666') # added to the extent but not the association since there is no callback block
|
41
|
+
# Person.map { |person| person.ssn }.sort #=> ["555-55-5555", "666-66-6666"]
|
42
|
+
# Person.for('666-66-6666') #=> nil
|
43
|
+
#
|
44
|
+
# The hash factory block above only adds an instance to the extent association if it is accessed using the
|
45
|
+
# #for class method. If an instance can be created both on demand and independently of the #for method,
|
46
|
+
# then specify both a factory block and a callback block, e.g.:
|
47
|
+
# class Person
|
48
|
+
# make_extensional(Hash.new { |hash, ssn| new(ssn) }) {|hash, person| hash[person.snn] = person }
|
49
|
+
# ...
|
50
|
+
# end
|
51
|
+
# Person.for('555-55-5555') #=> Person{ssn=>555-55-5555}
|
52
|
+
# Person.new('666-66-6666') # added to both the extent and the association
|
53
|
+
# Person.map { |person| person.ssn }.sort #=> ["555-55-5555", "666-66-6666"]
|
54
|
+
# Person.for('666-66-6666') #=> Person{ssn=>666-66-6666}
|
55
|
+
#
|
56
|
+
# Note that hash assignment is not needed in the hash initializer method, since there is a callback block
|
57
|
+
# assigns the hash key to the new instance.
|
58
|
+
#
|
59
|
+
# This latter make_extensional alternative is the most flexible strategy, since it provides for both
|
60
|
+
# iterative and associative access.
|
61
|
+
#
|
62
|
+
# If neither a callback block nor a hash is given, then associative access is disabled as described
|
63
|
+
# in the Extensional#for method.
|
64
|
+
def make_extensional(hash=nil, &block) # :yields: hash, instance
|
65
|
+
@extent = Extent.new(hash, &block)
|
66
|
+
extend(Extensional)
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
# An Extent is a thin Array wrapper with optional associative access.
|
5
|
+
class Extent
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
# The key=>instance association hash.
|
9
|
+
attr_reader :association
|
10
|
+
|
11
|
+
# Creates a new Extent. The block is called when an instance is added to the extent.
|
12
|
+
def initialize(hash=nil, &block) # :yields: hash, instance
|
13
|
+
@extent = Set.new
|
14
|
+
@association = hash
|
15
|
+
@association ||= {} if block_given?
|
16
|
+
@callback = block
|
17
|
+
end
|
18
|
+
|
19
|
+
# Adds the given obj to this extent.
|
20
|
+
def <<(obj)
|
21
|
+
@callback.call(@association, obj) if @callback
|
22
|
+
@extent << obj
|
23
|
+
end
|
24
|
+
|
25
|
+
# Calls the given block an each instance in this extent.
|
26
|
+
def each(&block)
|
27
|
+
@extent.each(&block)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns whether this Extent implements associative access by key.
|
31
|
+
def associative?
|
32
|
+
not @association.nil?
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns the instance for the given key.
|
36
|
+
#
|
37
|
+
# Raises NotImplementedError if this extent does not implement associative access.
|
38
|
+
def find(key)
|
39
|
+
raise NotImplementedError.new("Associative access by key is not implemented for this class extent") unless associative?
|
40
|
+
@association[key]
|
41
|
+
end
|
42
|
+
|
43
|
+
# Removes all instances from this Extent's extent and association.
|
44
|
+
def clear
|
45
|
+
@extent.clear
|
46
|
+
@association.clear if associative?
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
$:.unshift 'lib'
|
2
|
+
|
3
|
+
require 'extensional'
|
4
|
+
|
5
|
+
class Extended
|
6
|
+
make_extensional
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
Extended << self
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Associative
|
14
|
+
make_extensional { |hash, obj| hash[obj.label] = obj }
|
15
|
+
|
16
|
+
attr_reader :label
|
17
|
+
|
18
|
+
def initialize(label)
|
19
|
+
@label = label
|
20
|
+
Associative << self
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class CompoundKey
|
25
|
+
make_extensional { |hash, obj| hash[obj.key] = obj }
|
26
|
+
|
27
|
+
attr_reader :key
|
28
|
+
|
29
|
+
def initialize(x, y)
|
30
|
+
@key = [x, y]
|
31
|
+
CompoundKey << self
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class CreateOnAccess
|
36
|
+
make_extensional(Hash.new { |hash, label| hash[label] = CreateOnAccess.new(label) })
|
37
|
+
|
38
|
+
def initialize(label)
|
39
|
+
@label = label
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class AssociativeCreateOnAccess
|
44
|
+
make_extensional(Hash.new { |hash, label| AssociativeCreateOnAccess.new(label) }) { |hash, obj| hash[obj.label] = obj }
|
45
|
+
|
46
|
+
attr_reader :label
|
47
|
+
|
48
|
+
def initialize(label)
|
49
|
+
@label = label
|
50
|
+
AssociativeCreateOnAccess << self
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class A
|
55
|
+
make_extensional
|
56
|
+
def initialize
|
57
|
+
self.class << self
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class B < A
|
62
|
+
make_extensional
|
63
|
+
end
|
64
|
+
|
65
|
+
class C < A
|
66
|
+
make_extensional
|
67
|
+
end
|
68
|
+
|
69
|
+
class ExtensionalTest < Test::Unit::TestCase
|
70
|
+
def test_extent
|
71
|
+
assert_equal(Extended.new, Extended.to_a.first, "Instance not added to extension")
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_associative
|
75
|
+
obj = Associative.new(:test)
|
76
|
+
assert_same(obj, Associative.for(:test), "Instance not accessible by label")
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_compound_key
|
80
|
+
obj = CompoundKey.new(:x, :y)
|
81
|
+
assert_same(obj, CompoundKey.for(:x, :y), "Instance not accessible by compound key")
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_hash_factory
|
85
|
+
obj = CreateOnAccess.for(:test)
|
86
|
+
assert_not_nil(obj, "Instance not created")
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_associative_hash_factory
|
90
|
+
obj = AssociativeCreateOnAccess.for(:on_demand)
|
91
|
+
assert_not_nil(obj, "Instance not created")
|
92
|
+
assert_same(obj, AssociativeCreateOnAccess.for(:on_demand), "Instance created on demand not accessible by label")
|
93
|
+
obj = AssociativeCreateOnAccess.new(:by_new)
|
94
|
+
assert(AssociativeCreateOnAccess.extent.association.has_key?(:by_new), "Instance not accessible by label")
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_isolation
|
98
|
+
a = A.new
|
99
|
+
b = B.new
|
100
|
+
c = C.new
|
101
|
+
assert_equal([a], A.to_a, "Superclass extension incorrect")
|
102
|
+
assert_equal([b], B.to_a, "Subclass extension incorrect")
|
103
|
+
assert_equal([c], C.to_a, "Subclass extension incorrect")
|
104
|
+
end
|
105
|
+
end
|
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: extensional
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 25
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 3
|
9
|
+
- 1
|
10
|
+
version: 1.3.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- OHSU
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-09-30 00:00:00 -07:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: " The extensional gem adds the ability of a Class to collect its instances.\n An extensional class is an Enumerable whose each method iterates over its instances.\n An optional callback block enables associative access to instances by key.\n"
|
23
|
+
email: caruby.org@gmail.com
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files: []
|
29
|
+
|
30
|
+
files:
|
31
|
+
- lib/extensional/class.rb
|
32
|
+
- lib/extensional/extent.rb
|
33
|
+
- lib/extensional.rb
|
34
|
+
- test/lib/extensional_test.rb
|
35
|
+
- History.txt
|
36
|
+
- LEGAL
|
37
|
+
- LICENSE
|
38
|
+
- README.md
|
39
|
+
has_rdoc: extensional
|
40
|
+
homepage: http://github.com/caruby/extensional/
|
41
|
+
licenses: []
|
42
|
+
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options: []
|
45
|
+
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
hash: 3
|
54
|
+
segments:
|
55
|
+
- 0
|
56
|
+
version: "0"
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
hash: 3
|
63
|
+
segments:
|
64
|
+
- 0
|
65
|
+
version: "0"
|
66
|
+
requirements: []
|
67
|
+
|
68
|
+
rubyforge_project: caruby
|
69
|
+
rubygems_version: 1.3.7
|
70
|
+
signing_key:
|
71
|
+
specification_version: 3
|
72
|
+
summary: Collects Class instances.
|
73
|
+
test_files: []
|
74
|
+
|