key_struct 0.4.0 → 0.4.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/README.rdoc +1 -0
- data/lib/key_struct/key_struct.rb +74 -36
- data/lib/key_struct/version.rb +1 -1
- data/spec/key_struct_spec.rb +63 -19
- metadata +7 -7
data/README.rdoc
CHANGED
@@ -134,6 +134,7 @@ Requires ruby >= 1.9.2. (Has been tested on MRI 1.9.2 and MRI 1.9.3)
|
|
134
134
|
|
135
135
|
Release Notes:
|
136
136
|
|
137
|
+
* 0.4.1 - Cache anonymous classes to avoid TypeError: superclass mismatch. Nicer strings for anonymous classes. Internals change: use base class rather than metaprogramming.
|
137
138
|
* 0.4.0 - Introduce class introspection
|
138
139
|
* 0.3.1 - Bug fix: to_hash when a value is an array. Was raising ArgumentError for Hash
|
139
140
|
* 0.3.0 - Introduced to_s and inspect
|
@@ -1,11 +1,11 @@
|
|
1
1
|
module KeyStruct
|
2
2
|
|
3
3
|
def self.reader(*keys)
|
4
|
-
|
4
|
+
fetch_key_struct(:reader, keys)
|
5
5
|
end
|
6
6
|
|
7
7
|
def self.accessor(*keys)
|
8
|
-
|
8
|
+
fetch_key_struct(:accessor, keys)
|
9
9
|
end
|
10
10
|
|
11
11
|
instance_eval do
|
@@ -14,51 +14,89 @@ module KeyStruct
|
|
14
14
|
|
15
15
|
private
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
defaults = (Hash === keys.last) ? keys.pop : {}
|
20
|
-
keys += defaults.keys
|
17
|
+
class Base
|
18
|
+
include Comparable
|
21
19
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
define_singleton_method(:keys) { keys }
|
27
|
-
define_singleton_method(:defaults) { defaults }
|
20
|
+
class << self
|
21
|
+
attr_reader :keys, :defaults, :access
|
22
|
+
end
|
28
23
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
end
|
34
|
-
raise ArgumentError, "Invalid argument(s): #{args.keys.map(&:inspect).join(' ')} -- KeyStruct accepts #{keys.map(&:inspect).join(' ')}" if args.any?
|
24
|
+
def initialize(args={})
|
25
|
+
args = self.class.defaults.merge(args)
|
26
|
+
self.class.keys.each do |key|
|
27
|
+
instance_variable_set("@#{key}".to_sym, args.delete(key))
|
35
28
|
end
|
29
|
+
raise ArgumentError, "Invalid argument(s): #{args.keys.map(&:inspect).join(' ')} -- KeyStruct accepts #{self.class.keys.map(&:inspect).join(' ')}" if args.any?
|
30
|
+
end
|
36
31
|
|
37
|
-
|
38
|
-
|
39
|
-
|
32
|
+
def ==(other)
|
33
|
+
self.class.keys.all?{|key| other.respond_to?(key) and self.send(key) == other.send(key)}
|
34
|
+
end
|
40
35
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
end
|
46
|
-
0
|
36
|
+
def <=>(other)
|
37
|
+
self.class.keys.each do |key|
|
38
|
+
cmp = (self.send(key) <=> other.send(key))
|
39
|
+
return cmp unless cmp == 0
|
47
40
|
end
|
41
|
+
0
|
42
|
+
end
|
48
43
|
|
49
|
-
|
50
|
-
|
51
|
-
|
44
|
+
def to_hash
|
45
|
+
Hash[*self.class.keys.map{ |key| [key, self.send(key)]}.flatten(1)]
|
46
|
+
end
|
52
47
|
|
53
|
-
|
54
|
-
|
55
|
-
|
48
|
+
def to_s
|
49
|
+
"[#{self.class.display_name} #{self.class.keys.map{|key| "#{key}:#{self.send(key)}"}.join(' ')}]"
|
50
|
+
end
|
56
51
|
|
57
|
-
|
58
|
-
|
59
|
-
|
52
|
+
def inspect
|
53
|
+
"<#{self.class.display_name}:0x#{self.object_id.to_s(16)} #{self.class.keys.map{|key| "#{key}:#{self.send(key).inspect}"}.join(' ')}>"
|
54
|
+
end
|
60
55
|
|
56
|
+
def self.display_name
|
57
|
+
self.name || "KeyStruct.#{access}"
|
61
58
|
end
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
# for anonymous superclasses, such as
|
63
|
+
#
|
64
|
+
# class Foo < KeyStruct[:a, :b]
|
65
|
+
# end
|
66
|
+
#
|
67
|
+
# we want to be sure that if the code gets re-executed (e.g. the file
|
68
|
+
# gets loaded twice) the superclass will be the same object otherwise
|
69
|
+
# ruby will raise a TypeError: superclass mismatch. So keep a cache of
|
70
|
+
# anonymous KeyStructs
|
71
|
+
#
|
72
|
+
# But don't reuse the class if it has a name, i.e. if it was assigned to
|
73
|
+
# a constant. If somebody does
|
74
|
+
#
|
75
|
+
# Foo = KeyStruct[:a, :b]
|
76
|
+
# Bar = KeyStruct[:a, :b]
|
77
|
+
#
|
78
|
+
# they should get different class definitions, in particular because the
|
79
|
+
# classname is used in #to_s and #inspect
|
80
|
+
#
|
81
|
+
def self.fetch_key_struct(access, keys)
|
82
|
+
@cache ||= {}
|
83
|
+
signature = [access, keys]
|
84
|
+
@cache.delete(signature) if @cache[signature] and @cache[signature].name
|
85
|
+
@cache[signature] ||= define_key_struct(access, keys)
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.define_key_struct(access, keys)
|
89
|
+
keys = keys.dup
|
90
|
+
defaults = (Hash === keys.last) ? keys.pop.dup : {}
|
91
|
+
keys += defaults.keys
|
92
|
+
|
93
|
+
Class.new(Base).tap{ |klass|
|
94
|
+
klass.class_eval do
|
95
|
+
@keys = keys
|
96
|
+
@defaults = defaults
|
97
|
+
@access = access
|
98
|
+
send "attr_#{access}", *keys
|
99
|
+
end
|
62
100
|
}
|
63
101
|
end
|
64
102
|
|
data/lib/key_struct/version.rb
CHANGED
data/spec/key_struct_spec.rb
CHANGED
@@ -51,6 +51,42 @@ shared_examples "a keystruct" do |method|
|
|
51
51
|
|
52
52
|
end
|
53
53
|
|
54
|
+
context "definition" do
|
55
|
+
it "can handle a default that's an array" do
|
56
|
+
expect { KeyStruct.send(method, :a => []) }.should_not raise_error
|
57
|
+
end
|
58
|
+
|
59
|
+
it "reuses existing anonymous class definition" do
|
60
|
+
k = KeyStruct.send(method, :a, :b => 3)
|
61
|
+
j = KeyStruct.send(method, :a, :b => 3)
|
62
|
+
k.should equal j
|
63
|
+
end
|
64
|
+
|
65
|
+
it "does not reuse non-anonymous class definition" do
|
66
|
+
begin
|
67
|
+
K = KeyStruct.send(method, :a, :b => 3)
|
68
|
+
j = KeyStruct.send(method, :a, :b => 3)
|
69
|
+
K.should_not equal j
|
70
|
+
ensure
|
71
|
+
Object.send(:remove_const, :K)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
context "returning hash" do
|
78
|
+
|
79
|
+
it "returns hash using to_hash" do
|
80
|
+
KeyStruct.send(method, :a => 3, :b => 4).new.to_hash.should == {:a => 3, :b => 4}
|
81
|
+
end
|
82
|
+
|
83
|
+
it "returns hash using to_hash when value is array" do
|
84
|
+
KeyStruct.send(method, :a => 3, :b => [[1,2], [3,4]]).new.to_hash.should == {:a => 3, :b => [[1,2],[3,4]]}
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
|
54
90
|
context "comparison" do
|
55
91
|
|
56
92
|
before(:each) do
|
@@ -84,35 +120,43 @@ shared_examples "a keystruct" do |method|
|
|
84
120
|
|
85
121
|
end
|
86
122
|
|
87
|
-
it "returns hash using to_hash" do
|
88
|
-
KeyStruct.send(method, :a => 3, :b => 4).new.to_hash.should == {:a => 3, :b => 4}
|
89
|
-
end
|
90
123
|
|
91
|
-
|
92
|
-
KeyStruct.send(method, :a => 3, :b => [[1,2], [3,4]]).new.to_hash.should == {:a => 3, :b => [[1,2],[3,4]]}
|
93
|
-
end
|
124
|
+
context "string display" do
|
94
125
|
|
95
|
-
|
96
|
-
expect { KeyStruct.send(method, :a => []) }.should_not raise_error
|
97
|
-
end
|
126
|
+
context "when anonymous" do
|
98
127
|
|
99
|
-
|
128
|
+
before(:each) do
|
129
|
+
@klass = KeyStruct.send(method, :a => 3, :b => "hello")
|
130
|
+
end
|
100
131
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
Object.send(:remove_const, :PrintMe)
|
105
|
-
end
|
132
|
+
it "should be nice for :to_s" do
|
133
|
+
@klass.new.to_s.should == "[KeyStruct.#{method} a:3 b:hello]"
|
134
|
+
end
|
106
135
|
|
107
|
-
|
108
|
-
|
136
|
+
it "should be detailed for :inspect" do
|
137
|
+
@klass.new.inspect.should match /<KeyStruct.#{method}:0x[0-9a-f]+ a:3 b:"hello">/
|
138
|
+
end
|
109
139
|
end
|
110
140
|
|
111
|
-
|
112
|
-
|
141
|
+
context "when named" do
|
142
|
+
around(:each) do |example|
|
143
|
+
PrintMe = @klass = KeyStruct.send(method, :a => 3, :b => "hello")
|
144
|
+
example.run
|
145
|
+
Object.send(:remove_const, :PrintMe)
|
146
|
+
end
|
147
|
+
|
148
|
+
it "should be nice for :to_s" do
|
149
|
+
@klass.new.to_s.should == "[PrintMe a:3 b:hello]"
|
150
|
+
end
|
151
|
+
|
152
|
+
it "should be detailed for :inspect" do
|
153
|
+
@klass.new.inspect.should match /<PrintMe:0x[0-9a-f]+ a:3 b:"hello">/
|
154
|
+
end
|
113
155
|
end
|
156
|
+
|
114
157
|
end
|
115
158
|
|
159
|
+
|
116
160
|
end
|
117
161
|
|
118
162
|
describe "KeyStruct" do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: key_struct
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -13,7 +13,7 @@ date: 2012-05-15 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
16
|
-
requirement: &
|
16
|
+
requirement: &70230512662240 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70230512662240
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: simplecov
|
27
|
-
requirement: &
|
27
|
+
requirement: &70230512661380 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70230512661380
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: simplecov-gem-adapter
|
38
|
-
requirement: &
|
38
|
+
requirement: &70230512660520 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,7 +43,7 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70230512660520
|
47
47
|
description: Defines KeyStruct analogous to Struct, but constructor takes keyword
|
48
48
|
arguments
|
49
49
|
email:
|