classy 1.1.1 → 1.2.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/README.rdoc +7 -2
- data/VERSION +1 -1
- data/lib/classy/aliasable.rb +106 -43
- data/lib/classy/subclass_aware.rb +11 -18
- data/lib/classy/templatable.rb +5 -0
- data/spec/aliasable_spec.rb +34 -4
- data/spec/subclass_aware_spec.rb +3 -6
- metadata +27 -9
data/README.rdoc
CHANGED
@@ -11,6 +11,10 @@ sub-subclasses, etc), and Aliasable lets you refer to classes via symbols
|
|
11
11
|
class Parent
|
12
12
|
extend Aliasable
|
13
13
|
extend SubclassAware
|
14
|
+
extend Templatable
|
15
|
+
|
16
|
+
templatable_attr :awesomeness, :temperature
|
17
|
+
awesomeness :fairly
|
14
18
|
|
15
19
|
aka :pop
|
16
20
|
end
|
@@ -23,8 +27,9 @@ sub-subclasses, etc), and Aliasable lets you refer to classes via symbols
|
|
23
27
|
aka :kid2
|
24
28
|
end
|
25
29
|
|
26
|
-
Parent.find(:kid1)
|
27
|
-
Parent.subclasses
|
30
|
+
Parent.find(:kid1) # => ChildA
|
31
|
+
Parent.subclasses # => [ ChildA, ChildB ]
|
32
|
+
Parent.new.awesomeness # => :fairly
|
28
33
|
|
29
34
|
More extensive documentation and example code can be found in the RDoc for each
|
30
35
|
module (available online at http://rdoc.info/projects/djspinmonkey/classy) or
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.2.0
|
data/lib/classy/aliasable.rb
CHANGED
@@ -4,17 +4,10 @@
|
|
4
4
|
# a given class hierarchy. Possible uses for this include friendlier DSLs or
|
5
5
|
# additional layers of dynamic abstraction when specifying classes.
|
6
6
|
#
|
7
|
-
# Note: As mentioned, this module keeps its identity map in a class variable,
|
8
|
-
# @@classy_aliases, on the extending class. This could concievably lead to
|
9
|
-
# namespace conflicts and strange bugs in the unlikely event that this variable
|
10
|
-
# is used for anything else. Later versions may implement a hash of identity
|
11
|
-
# maps as a class variable on the Aliasable module itself, but for reasons of
|
12
|
-
# complexity and performance, that has not been done at this time.
|
13
|
-
#
|
14
7
|
# ==Example
|
15
8
|
#
|
16
9
|
# class ParentClass
|
17
|
-
#
|
10
|
+
# include Aliasable
|
18
11
|
# aka :pop
|
19
12
|
# end
|
20
13
|
#
|
@@ -25,54 +18,124 @@
|
|
25
18
|
# Parent.find(:pop) # => ParentClass
|
26
19
|
# Parent.find(:kid) # => AliasedSubclass
|
27
20
|
#
|
28
|
-
#
|
21
|
+
# It is also possible to include Aliasable from a model, which will then track
|
22
|
+
# aliases of classes which include that module.
|
23
|
+
#
|
24
|
+
# == Example
|
25
|
+
#
|
26
|
+
# module Meta
|
27
|
+
# include Aliasable
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# class AliasedClass
|
31
|
+
# include Meta
|
32
|
+
#
|
33
|
+
# aka :klass
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# Meta.find(:klass) # => AliasedClass
|
37
|
+
#
|
38
|
+
# More complex usage examples can be found in the spec file.
|
39
|
+
#
|
40
|
+
# NOTE: This defines a class variable, @@classy_aliases, on any class or module
|
41
|
+
# that includes Aliasable (or any class that includes a module including
|
42
|
+
# Aliasable).
|
43
|
+
#
|
44
|
+
# ANOTHER NOTE: As always, if you define your own .included methods, be sure to
|
45
|
+
# call super.
|
29
46
|
#
|
30
47
|
module Aliasable
|
31
|
-
|
32
|
-
def self.extended (klass) #:nodoc:
|
33
|
-
klass.class_exec do
|
34
|
-
class_variable_set(:@@classy_aliases, {})
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
# When passed a class, just returns it. When passed a symbol that is an
|
39
|
-
# alias for a class, returns that class.
|
48
|
+
# Handle a module or class including Aliasable.
|
40
49
|
#
|
41
|
-
#
|
42
|
-
# ParentClass.find(:kid) # => AliasedSubclass
|
50
|
+
# :nodoc:
|
43
51
|
#
|
44
|
-
def
|
45
|
-
|
46
|
-
|
52
|
+
def self.included( mod )
|
53
|
+
mod.extend ControllingClassMethods
|
54
|
+
mod.extend UniversalClassMethods
|
55
|
+
mod.extend AliasingClassMethods if mod.kind_of? Class # If mod is a Class, the aliased classes get the class methods via inheritance.
|
56
|
+
mod.send :class_variable_set, :@@classy_aliases, Hash.new
|
57
|
+
super
|
47
58
|
end
|
48
59
|
|
49
|
-
#
|
60
|
+
# Methods for the class or module that directly includes Aliasable.
|
50
61
|
#
|
51
|
-
|
52
|
-
|
62
|
+
module ControllingClassMethods
|
63
|
+
# Handle a class including a module that has included Aliasable. Since the
|
64
|
+
# contolling module has extended this module, this method ends up being
|
65
|
+
# called when the controlling module is included.
|
66
|
+
#
|
67
|
+
# As a minor side effect, an instance method named #included ends up on any
|
68
|
+
# class that directly includes Aliasable. If you know an elegant way to
|
69
|
+
# avoid this, I welcome pull requests. :-)
|
70
|
+
#
|
71
|
+
# :nodoc:
|
72
|
+
#
|
73
|
+
def included( klass )
|
74
|
+
klass.extend AliasingClassMethods
|
75
|
+
klass.extend UniversalClassMethods
|
76
|
+
|
77
|
+
# Hoo boy. We need to set the @@classy_aliases class variable in the
|
78
|
+
# including class to point to the same actual hash object that the
|
79
|
+
# @@classy_aliases variable on the controlling module points to. When
|
80
|
+
# everything is class based, this is done automatically, since
|
81
|
+
# sub-classes share class variables.
|
82
|
+
#
|
83
|
+
klass.send(:class_variable_set, :@@classy_aliases, self.send(:class_variable_get, :@@classy_aliases))
|
84
|
+
|
85
|
+
super
|
86
|
+
end
|
87
|
+
|
88
|
+
# When passed a class, just returns it. When passed a symbol that is an
|
89
|
+
# alias for a class, returns that class.
|
90
|
+
#
|
91
|
+
# ParentClass.find(AliasedSubclass) # => AliasedSubclass
|
92
|
+
# ParentClass.find(:kid) # => AliasedSubclass
|
93
|
+
#
|
94
|
+
def find( nick )
|
95
|
+
return nick if nick.kind_of? Class
|
96
|
+
aliases[nick]
|
97
|
+
end
|
98
|
+
|
99
|
+
# Forget all known aliases. Mainly useful for testing purposes.
|
100
|
+
#
|
101
|
+
def forget_aliases
|
102
|
+
aliases.clear
|
103
|
+
end
|
104
|
+
|
53
105
|
end
|
54
106
|
|
55
|
-
#
|
56
|
-
# by.
|
57
|
-
#
|
58
|
-
# class AnotherClass
|
59
|
-
# aka :kid2, :chunky_bacon
|
60
|
-
# ...
|
61
|
-
# end
|
107
|
+
# Methods for the classes that get aliased.
|
62
108
|
#
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
109
|
+
module AliasingClassMethods
|
110
|
+
# Specifies a symbol (or several) that a given framework might be known
|
111
|
+
# by.
|
112
|
+
#
|
113
|
+
# class AnotherClass
|
114
|
+
# aka :kid2, :chunky_bacon
|
115
|
+
# ...
|
116
|
+
# end
|
117
|
+
#
|
118
|
+
def aka( *nicks )
|
119
|
+
nicks.each do |nick|
|
120
|
+
raise ArgumentError, "Called aka with an alias that is already taken." if aliases.include? nick
|
121
|
+
aliases[nick] = self
|
122
|
+
end
|
67
123
|
end
|
68
124
|
end
|
69
125
|
|
70
|
-
#
|
126
|
+
# Methods for both the controlling class/module and the aliased classes.
|
71
127
|
#
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
128
|
+
module UniversalClassMethods
|
129
|
+
# Return a hash of known aliases to Class objects.
|
130
|
+
#
|
131
|
+
# DANGER DANGER: This is the actual hash used internally by Aliasable, not a
|
132
|
+
# dup. If you mess with it, you might asplode things.
|
133
|
+
#
|
134
|
+
# ParentClass.aliases # => { :pop => ParentClass, :kid => AliasedSubclass, :kid2 => AnotherClass, :chunky_bacon => AnotherClass }
|
135
|
+
# ParentClass.aliases[:thing] = "BOOM" # This will end in tears.
|
136
|
+
#
|
137
|
+
def aliases
|
138
|
+
send :class_variable_get, :@@classy_aliases
|
139
|
+
end
|
76
140
|
end
|
77
|
-
|
78
141
|
end
|
@@ -28,23 +28,19 @@ require 'set'
|
|
28
28
|
#
|
29
29
|
# == Warning
|
30
30
|
#
|
31
|
-
# This module defines an inherited() class method
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
# class ChildC < Parent
|
37
|
-
#
|
38
|
-
# class << self; alias :old_inherited :inherited end
|
39
|
-
# def self.inherited(sub)
|
40
|
-
# old_inherited(sub)
|
41
|
-
# # ...your inherited() code...
|
42
|
-
# end
|
31
|
+
# This module defines an inherited() class method. If the extending class
|
32
|
+
# defines its own inherited() method without calling super, this inherited()
|
33
|
+
# method is lost and subclass tracking will break. In order to work around
|
34
|
+
# this, make sure your inherited() method calls super. Like this:
|
43
35
|
#
|
36
|
+
# class YourAwesomeClass
|
37
|
+
#
|
38
|
+
# def self.inherited
|
39
|
+
# # ...your awesome logic
|
40
|
+
# super # <-- This is important.
|
44
41
|
# end
|
45
42
|
#
|
46
|
-
#
|
47
|
-
# in future versions of this module, this work around will not be necessary.
|
43
|
+
# end
|
48
44
|
#
|
49
45
|
module SubclassAware
|
50
46
|
|
@@ -57,12 +53,9 @@ module SubclassAware
|
|
57
53
|
# Add the inheriting class to the list of subclasses. Not intended to be
|
58
54
|
# called directly.
|
59
55
|
#
|
60
|
-
# TODO: Find a way for self.inherited on the extended class not to blow
|
61
|
-
# this away without requiring a bunch of alias chain hoops to be jumped
|
62
|
-
# through, as described above.
|
63
|
-
#
|
64
56
|
def inherited(sub)
|
65
57
|
class_exec { class_variable_get(:@@classy_subclasses).add sub }
|
58
|
+
super
|
66
59
|
end
|
67
60
|
|
68
61
|
# Return an array of all known subclasses (and sub-subclasses, etc) of this
|
data/lib/classy/templatable.rb
CHANGED
@@ -24,6 +24,8 @@
|
|
24
24
|
# # Instances can override the defaults.
|
25
25
|
# doodad.awesomeness = nil
|
26
26
|
# doodad.temperature = :cool
|
27
|
+
# doodad.awesomeness # => nil
|
28
|
+
# doodad.temperature # => :cool
|
27
29
|
#
|
28
30
|
# == Note
|
29
31
|
#
|
@@ -37,6 +39,9 @@ module Templatable
|
|
37
39
|
# (Required to distinguish between variables explicitly set to nil and those
|
38
40
|
# left as nil by default.)
|
39
41
|
#
|
42
|
+
# @private
|
43
|
+
# :nodoc:
|
44
|
+
#
|
40
45
|
@@ever_been_set = Hash.new { |hash, key| hash[key] = {} }
|
41
46
|
|
42
47
|
# Defines one or more templatable attrs, which will add instance methods
|
data/spec/aliasable_spec.rb
CHANGED
@@ -2,11 +2,10 @@ require File.join(File.dirname(__FILE__), 'spec_helper')
|
|
2
2
|
|
3
3
|
describe "Aliasable" do
|
4
4
|
|
5
|
-
# TODO: It would be better to tear these down and rebuild them before each
|
6
|
-
# test, since some of the tests add or remove aliases.
|
7
5
|
before :all do
|
6
|
+
Base.forget_aliases if Object.const_defined? "Base"
|
8
7
|
class Base
|
9
|
-
|
8
|
+
include Aliasable
|
10
9
|
aka :base
|
11
10
|
end
|
12
11
|
|
@@ -18,6 +17,21 @@ describe "Aliasable" do
|
|
18
17
|
aka :childB
|
19
18
|
end
|
20
19
|
|
20
|
+
Meta.forget_aliases if Object.const_defined? "Meta"
|
21
|
+
module Meta
|
22
|
+
include Aliasable
|
23
|
+
end
|
24
|
+
|
25
|
+
class IncluderA
|
26
|
+
include Meta
|
27
|
+
aka :incA
|
28
|
+
end
|
29
|
+
|
30
|
+
class IncluderB
|
31
|
+
include Meta
|
32
|
+
aka :incB
|
33
|
+
end
|
34
|
+
|
21
35
|
end
|
22
36
|
|
23
37
|
describe ".aliases" do
|
@@ -29,6 +43,13 @@ describe "Aliasable" do
|
|
29
43
|
:childB => ChildB
|
30
44
|
})
|
31
45
|
end
|
46
|
+
|
47
|
+
it "should work on modules as well as classes" do
|
48
|
+
Meta.aliases.should eql({
|
49
|
+
:incA => IncluderA,
|
50
|
+
:incB => IncluderB
|
51
|
+
})
|
52
|
+
end
|
32
53
|
end
|
33
54
|
|
34
55
|
describe ".aka" do
|
@@ -46,11 +67,15 @@ describe "Aliasable" do
|
|
46
67
|
Base.find(:first_child).should equal ChildA
|
47
68
|
Base.find(:childB).should equal ChildB
|
48
69
|
end
|
70
|
+
|
71
|
+
it "should work on modules as well as bases" do
|
72
|
+
Meta.find(:incA).should equal IncluderA
|
73
|
+
end
|
49
74
|
end
|
50
75
|
|
51
76
|
it "should keep aliases of different class hierarchies separate" do
|
52
77
|
class AnotherBase
|
53
|
-
|
78
|
+
include Aliasable
|
54
79
|
end
|
55
80
|
|
56
81
|
lambda {
|
@@ -76,6 +101,11 @@ describe "Aliasable" do
|
|
76
101
|
Base.forget_aliases
|
77
102
|
Base.aliases.should be_empty
|
78
103
|
end
|
104
|
+
|
105
|
+
it "should work for modules as well as classes" do
|
106
|
+
Meta.forget_aliases
|
107
|
+
Meta.aliases.should be_empty
|
108
|
+
end
|
79
109
|
end
|
80
110
|
|
81
111
|
end
|
data/spec/subclass_aware_spec.rb
CHANGED
@@ -8,13 +8,10 @@ describe "SubclassAware" do
|
|
8
8
|
class ParentA
|
9
9
|
extend SubclassAware
|
10
10
|
|
11
|
-
#
|
12
|
-
# define your own self.inherited to keep from blowing away the one
|
13
|
-
# SubclassAware sets up. There's a todo in subclass_aware.rb about this.
|
14
|
-
class << self; alias :old_inherited :inherited end
|
11
|
+
# Make sure you call super in any self.inherited() methods!
|
15
12
|
def self.inherited(sub)
|
16
|
-
|
17
|
-
#
|
13
|
+
# ...your stuff...
|
14
|
+
super # <-- This is important
|
18
15
|
end
|
19
16
|
end
|
20
17
|
|
metadata
CHANGED
@@ -1,7 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: classy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
hash: 31
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 2
|
9
|
+
- 0
|
10
|
+
version: 1.2.0
|
5
11
|
platform: ruby
|
6
12
|
authors:
|
7
13
|
- John Hyland
|
@@ -9,19 +15,25 @@ autorequire:
|
|
9
15
|
bindir: bin
|
10
16
|
cert_chain: []
|
11
17
|
|
12
|
-
date:
|
18
|
+
date: 2011-10-27 00:00:00 -04:00
|
13
19
|
default_executable:
|
14
20
|
dependencies:
|
15
21
|
- !ruby/object:Gem::Dependency
|
16
22
|
name: rspec
|
17
|
-
|
18
|
-
|
19
|
-
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
20
26
|
requirements:
|
21
27
|
- - ">="
|
22
28
|
- !ruby/object:Gem::Version
|
29
|
+
hash: 13
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 2
|
33
|
+
- 9
|
23
34
|
version: 1.2.9
|
24
|
-
|
35
|
+
type: :development
|
36
|
+
version_requirements: *id001
|
25
37
|
description: Classy is a collection of metaprogramming-heavy modules which you can extend in order to give various capabilities to your Ruby classes. For example, SubclassAware lets a class know about all of its subclasses (and sub-subclasses, etc), and Aliasable lets you refer to classes via symbols (useful for creating friendly DSLs).
|
26
38
|
email: github@djspinmonkey.com
|
27
39
|
executables: []
|
@@ -57,21 +69,27 @@ rdoc_options:
|
|
57
69
|
require_paths:
|
58
70
|
- lib
|
59
71
|
required_ruby_version: !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
60
73
|
requirements:
|
61
74
|
- - ">="
|
62
75
|
- !ruby/object:Gem::Version
|
76
|
+
hash: 3
|
77
|
+
segments:
|
78
|
+
- 0
|
63
79
|
version: "0"
|
64
|
-
version:
|
65
80
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
66
82
|
requirements:
|
67
83
|
- - ">="
|
68
84
|
- !ruby/object:Gem::Version
|
85
|
+
hash: 3
|
86
|
+
segments:
|
87
|
+
- 0
|
69
88
|
version: "0"
|
70
|
-
version:
|
71
89
|
requirements: []
|
72
90
|
|
73
91
|
rubyforge_project:
|
74
|
-
rubygems_version: 1.
|
92
|
+
rubygems_version: 1.6.2
|
75
93
|
signing_key:
|
76
94
|
specification_version: 3
|
77
95
|
summary: A collection of modules to enhance the capabilities of Ruby classes in various ways.
|