classy 1.1.1 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|