extensional 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ === 1.3.1 / 2010-09-30
2
+
3
+ * Initial public release
4
+
data/LEGAL ADDED
@@ -0,0 +1,5 @@
1
+ LEGAL NOTICE INFORMATION
2
+ ------------------------
3
+
4
+ All the files in this distribution are covered under either the MIT
5
+ license (see the file LICENSE).
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.
@@ -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 &copy; 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.
@@ -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
+