key_struct 0.4.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
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
- define_key_struct(:attr_reader, keys)
4
+ fetch_key_struct(:reader, keys)
5
5
  end
6
6
 
7
7
  def self.accessor(*keys)
8
- define_key_struct(:attr_accessor, keys)
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
- def self.define_key_struct(access, keys)
18
-
19
- defaults = (Hash === keys.last) ? keys.pop : {}
20
- keys += defaults.keys
17
+ class Base
18
+ include Comparable
21
19
 
22
- Class.new.tap{ |klass| klass.class_eval do
23
- include Comparable
24
- send access, *keys
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
- define_method(:initialize) do |args={}|
30
- args = defaults.merge(args)
31
- keys.each do |key|
32
- instance_variable_set("@#{key}".to_sym, args.delete(key))
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
- define_method(:==) do |other|
38
- keys.all?{|key| other.respond_to?(key) and self.send(key) == other.send(key)}
39
- end
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
- define_method(:<=>) do |other|
42
- keys.each do |key|
43
- cmp = (self.send(key) <=> other.send(key))
44
- return cmp unless cmp == 0
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
- define_method(:to_hash) do
50
- Hash[*keys.map{ |key| [key, self.send(key)]}.flatten(1)]
51
- end
44
+ def to_hash
45
+ Hash[*self.class.keys.map{ |key| [key, self.send(key)]}.flatten(1)]
46
+ end
52
47
 
53
- define_method(:to_s) do
54
- "[#{self.class.name} #{keys.map{|key| "#{key}:#{self.send(key)}"}.join(' ')}]"
55
- end
48
+ def to_s
49
+ "[#{self.class.display_name} #{self.class.keys.map{|key| "#{key}:#{self.send(key)}"}.join(' ')}]"
50
+ end
56
51
 
57
- define_method(:inspect) do
58
- "<#{self.class.name}:0x#{self.object_id.to_s(16)} #{keys.map{|key| "#{key}:#{self.send(key).inspect}"}.join(' ')}>"
59
- end
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
 
@@ -1,3 +1,3 @@
1
1
  module KeyStruct
2
- VERSION = "0.4.0"
2
+ VERSION = "0.4.1"
3
3
  end
@@ -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
- it "returns hash using to_hash when value is array" do
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
- it "can handle a default that's an array" do
96
- expect { KeyStruct.send(method, :a => []) }.should_not raise_error
97
- end
126
+ context "when anonymous" do
98
127
 
99
- context "display as a string" do
128
+ before(:each) do
129
+ @klass = KeyStruct.send(method, :a => 3, :b => "hello")
130
+ end
100
131
 
101
- around(:each) do |example|
102
- PrintMe = @klass = KeyStruct.send(method, :a => 3, :b => "hello")
103
- example.run
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
- it "should be nice for :to_s" do
108
- @klass.new.to_s.should == "[PrintMe a:3 b:hello]"
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
- it "should be detailed for :inspect" do
112
- @klass.new.inspect.should match /<PrintMe:0x[0-9a-f]+ a:3 b:"hello">/
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.0
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: &70338235182700 !ruby/object:Gem::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: *70338235182700
24
+ version_requirements: *70230512662240
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: simplecov
27
- requirement: &70338235177160 !ruby/object:Gem::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: *70338235177160
35
+ version_requirements: *70230512661380
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: simplecov-gem-adapter
38
- requirement: &70338235172560 !ruby/object:Gem::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: *70338235172560
46
+ version_requirements: *70230512660520
47
47
  description: Defines KeyStruct analogous to Struct, but constructor takes keyword
48
48
  arguments
49
49
  email: