blather 0.4.1 → 0.4.2
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 +16 -1
- data/examples/echo.rb +1 -1
- data/examples/ping.rb +11 -0
- data/examples/pong.rb +6 -0
- data/lib/blather/client.rb +5 -3
- data/lib/blather/client/client.rb +150 -51
- data/lib/blather/client/dsl.rb +24 -0
- data/lib/blather/client/dsl/pubsub.rb +16 -16
- data/lib/blather/errors.rb +14 -3
- data/lib/blather/jid.rb +14 -22
- data/lib/blather/stanza.rb +0 -16
- data/lib/blather/stanza/iq.rb +46 -5
- data/lib/blather/stanza/message.rb +141 -9
- data/lib/blather/stanza/presence.rb +49 -5
- data/lib/blather/stanza/presence/status.rb +68 -8
- data/lib/blather/stream/client.rb +6 -0
- data/lib/blather/stream/features.rb +1 -1
- data/spec/blather/client/client_spec.rb +131 -0
- data/spec/blather/client/dsl_spec.rb +20 -0
- data/spec/blather/stanza/message_spec.rb +17 -6
- data/spec/blather/stream/client_spec.rb +20 -0
- data/spec/blather/stream/component_spec.rb +1 -1
- metadata +4 -2
data/lib/blather/errors.rb
CHANGED
@@ -2,16 +2,27 @@ module Blather
|
|
2
2
|
# Main error class
|
3
3
|
class BlatherError < StandardError
|
4
4
|
class_inheritable_array :handler_heirarchy
|
5
|
-
|
6
5
|
self.handler_heirarchy ||= []
|
7
|
-
self.handler_heirarchy << :error
|
8
6
|
|
7
|
+
@@handler_list = []
|
8
|
+
|
9
|
+
##
|
10
|
+
# Register the class's handler
|
9
11
|
def self.register(handler)
|
12
|
+
@@handler_list << handler
|
10
13
|
self.handler_heirarchy.unshift handler
|
11
14
|
end
|
12
15
|
|
16
|
+
##
|
17
|
+
# The list of registered handlers
|
18
|
+
def self.handler_list
|
19
|
+
@@handler_list
|
20
|
+
end
|
21
|
+
|
22
|
+
register :error
|
23
|
+
|
13
24
|
# HACK!! until I can refactor the entire Error object model
|
14
|
-
def id
|
25
|
+
def id # :nodoc:
|
15
26
|
nil
|
16
27
|
end
|
17
28
|
end
|
data/lib/blather/jid.rb
CHANGED
@@ -7,29 +7,23 @@ module Blather
|
|
7
7
|
|
8
8
|
PATTERN = /^(?:([^@]*)@)??([^@\/]*)(?:\/(.*?))?$/.freeze
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
##
|
15
|
-
# Get the JID's domain
|
16
|
-
attr_reader :domain
|
10
|
+
attr_reader :node,
|
11
|
+
:domain,
|
12
|
+
:resource
|
17
13
|
|
18
14
|
##
|
19
|
-
#
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
#
|
24
|
-
#
|
15
|
+
# Create a new JID. If called as new('a@b/c'), parse the string and split (node, domain, resource).
|
16
|
+
# * +node+ - can be any of the following:
|
17
|
+
# * a string representing the JID ("node@domain.tld/resource")
|
18
|
+
# * a JID. in which case nothing will be done and the original JID will be passed back
|
19
|
+
# * a string representing the node
|
20
|
+
# * +domain+ - the domain of the JID
|
21
|
+
# * +resource+ - the resource the connection should be bound to
|
25
22
|
def self.new(node, domain = nil, resource = nil)
|
26
23
|
node.is_a?(JID) ? node : super
|
27
24
|
end
|
28
25
|
|
29
|
-
|
30
|
-
# Create a new JID. If called as new('a@b/c'), parse the string and
|
31
|
-
# split (node, domain, resource)
|
32
|
-
def initialize(node, domain = nil, resource = nil)
|
26
|
+
def initialize(node, domain = nil, resource = nil) # :nodoc:
|
33
27
|
@resource = resource
|
34
28
|
@domain = domain
|
35
29
|
@node = node
|
@@ -62,14 +56,12 @@ module Blather
|
|
62
56
|
|
63
57
|
##
|
64
58
|
# Returns a new JID with resource removed.
|
65
|
-
# return:: [JID]
|
66
59
|
def stripped
|
67
|
-
|
60
|
+
dup.strip!
|
68
61
|
end
|
69
62
|
|
70
63
|
##
|
71
64
|
# Removes the resource (sets it to nil)
|
72
|
-
# return:: [JID] self
|
73
65
|
def strip!
|
74
66
|
@resource = nil
|
75
67
|
self
|
@@ -80,8 +72,8 @@ module Blather
|
|
80
72
|
# helpful for sorting etc.
|
81
73
|
#
|
82
74
|
# String representations are compared, see JID#to_s
|
83
|
-
def <=>(
|
84
|
-
to_s <=>
|
75
|
+
def <=>(other)
|
76
|
+
to_s <=> other.to_s
|
85
77
|
end
|
86
78
|
alias_method :eql?, :==
|
87
79
|
|
data/lib/blather/stanza.rb
CHANGED
@@ -34,22 +34,6 @@ module Blather
|
|
34
34
|
'blather%04x' % @@last_id
|
35
35
|
end
|
36
36
|
|
37
|
-
##
|
38
|
-
# Creates a new stanza with the same name as the node
|
39
|
-
# then inherits all the node's attributes and properties
|
40
|
-
def self.import(node)
|
41
|
-
self.new(node.element_name).inherit(node)
|
42
|
-
end
|
43
|
-
|
44
|
-
##
|
45
|
-
# Automatically set the stanza's ID
|
46
|
-
# and attach it to a document so XPath searching works
|
47
|
-
def self.new(name = nil)
|
48
|
-
node = super
|
49
|
-
node.name = name.to_s if name
|
50
|
-
node
|
51
|
-
end
|
52
|
-
|
53
37
|
##
|
54
38
|
# Helper method to generate stanza guard methods
|
55
39
|
#
|
data/lib/blather/stanza/iq.rb
CHANGED
@@ -1,14 +1,50 @@
|
|
1
1
|
module Blather
|
2
2
|
class Stanza
|
3
3
|
|
4
|
-
|
5
|
-
#
|
4
|
+
# = Iq Stanza
|
5
|
+
#
|
6
|
+
# Info/Query, or IQ, is a request-response mechanism, similar in some ways to HTTP. The semantics of IQ enable an entity
|
7
|
+
# to make a request of, and receive a response from, another entity. The data content of the request and response is
|
8
|
+
# defined by the namespace declaration of a direct child element of the IQ element, and the interaction is tracked by the
|
9
|
+
# requesting entity through use of the 'id' attribute. Thus, IQ interactions follow a common pattern of structured data
|
10
|
+
# exchange such as get/result or set/result (although an error may be returned in reply to a request if appropriate).
|
11
|
+
#
|
12
|
+
# == ID Attribute
|
13
|
+
#
|
14
|
+
# Iq Stanzas require the ID attribute be set. Blather will handle this automatically when a new Iq is created.
|
15
|
+
#
|
16
|
+
# == Type Attribute
|
17
|
+
#
|
18
|
+
# * +:get+ -- The stanza is a request for information or requirements.
|
19
|
+
# * +:set+ -- The stanza provides required data, sets new values, or replaces existing values.
|
20
|
+
# * +:result+ -- The stanza is a response to a successful get or set request.
|
21
|
+
# * +:error+ -- An error has occurred regarding processing or delivery of a previously-sent get or set (see Stanza Errors).
|
22
|
+
#
|
23
|
+
# Blather provides a helper for each possible type:
|
24
|
+
#
|
25
|
+
# Iq#get?
|
26
|
+
# Iq#set?
|
27
|
+
# Iq#result?
|
28
|
+
# Iq#error?
|
29
|
+
#
|
30
|
+
# Blather treats the +type+ attribute like a normal ruby object attribute providing a getter and setter.
|
31
|
+
# The default +type+ is +get+.
|
32
|
+
#
|
33
|
+
# iq = Iq.new
|
34
|
+
# iq.type # => :get
|
35
|
+
# iq.get? # => true
|
36
|
+
# iq.type = :set
|
37
|
+
# iq.set? # => true
|
38
|
+
# iq.get? # => false
|
39
|
+
#
|
40
|
+
# iq.type = :invalid # => RuntimeError
|
41
|
+
#
|
6
42
|
class Iq < Stanza
|
7
|
-
VALID_TYPES = [:get, :set, :result, :error]
|
43
|
+
VALID_TYPES = [:get, :set, :result, :error] # :nodoc:
|
8
44
|
|
9
45
|
register :iq
|
10
46
|
|
11
|
-
def self.import(node)
|
47
|
+
def self.import(node) # :nodoc:
|
12
48
|
klass = nil
|
13
49
|
node.children.each { |e| break if klass = class_from_registration(e.element_name, (e.namespace.href if e.namespace)) }
|
14
50
|
|
@@ -19,6 +55,11 @@ class Stanza
|
|
19
55
|
end
|
20
56
|
end
|
21
57
|
|
58
|
+
##
|
59
|
+
# Create a new Iq
|
60
|
+
# * +type+ - the type of stanza (:get, :set, :result, :error)
|
61
|
+
# * +to+ - the JID of the inteded recipient
|
62
|
+
# * +id+ - the stanza's ID. Leaving this nil will set the ID to the next unique number
|
22
63
|
def self.new(type = nil, to = nil, id = nil)
|
23
64
|
node = super :iq
|
24
65
|
node.type = type || :get
|
@@ -31,7 +72,7 @@ class Stanza
|
|
31
72
|
|
32
73
|
##
|
33
74
|
# Ensures type is :get, :set, :result or :error
|
34
|
-
def type=(type)
|
75
|
+
def type=(type) # :nodoc:
|
35
76
|
raise ArgumentError, "Invalid Type (#{type}), use: #{VALID_TYPES*' '}" if type && !VALID_TYPES.include?(type.to_sym)
|
36
77
|
super
|
37
78
|
end
|
@@ -1,14 +1,136 @@
|
|
1
1
|
module Blather
|
2
2
|
class Stanza
|
3
3
|
|
4
|
-
|
5
|
-
#
|
4
|
+
# Exchanging messages is a basic use of XMPP and occurs when a user generates a message stanza
|
5
|
+
# that is addressed to another entity. The sender's server is responsible for delivering the
|
6
|
+
# message to the intended recipient (if the recipient is on the same local server) or for routing
|
7
|
+
# the message to the recipient's server (if the recipient is on a remote server). Thus a message
|
8
|
+
# stanza is used to "push" information to another entity.
|
9
|
+
#
|
10
|
+
# == To Attribute
|
11
|
+
#
|
12
|
+
# An instant messaging client specifies an intended recipient for a message by providing the JID
|
13
|
+
# of an entity other than the sender in the +to+ attribute of the Message stanza. If the message
|
14
|
+
# is being sent outside the context of any existing chat session or received message, the value
|
15
|
+
# of the +to+ address SHOULD be of the form "user@domain" rather than of the form "user@domain/resource".
|
16
|
+
#
|
17
|
+
# msg = Message.new 'user@domain.tld/resource'
|
18
|
+
# msg.to == 'user@domain.tld/resource'
|
19
|
+
#
|
20
|
+
# msg.to = 'another-user@some-domain.tld/resource'
|
21
|
+
# msg.to == 'another-user@some-domain.tld/resource'
|
22
|
+
#
|
23
|
+
# The +to+ attribute on a Message stanza works like any regular ruby object attribute
|
24
|
+
#
|
25
|
+
# == Type Attribute
|
26
|
+
#
|
27
|
+
# Common uses of the message stanza in instant messaging applications include: single messages;
|
28
|
+
# messages sent in the context of a one-to-one chat session; messages sent in the context of a
|
29
|
+
# multi-user chat room; alerts, notifications, or other information to which no reply is expected;
|
30
|
+
# and errors. These uses are differentiated via the +type+ attribute. If included, the +type+
|
31
|
+
# attribute MUST have one of the following values:
|
32
|
+
#
|
33
|
+
# * +:chat+ -- The message is sent in the context of a one-to-one chat session. Typically a receiving
|
34
|
+
# client will present message of type +chat+ in an interface that enables one-to-one chat between
|
35
|
+
# the two parties, including an appropriate conversation history.
|
36
|
+
# * +:error+ -- The message is generated by an entity that experiences an error in processing a message
|
37
|
+
# received from another entity. A client that receives a message of type +error+ SHOULD present an
|
38
|
+
# appropriate interface informing the sender of the nature of the error.
|
39
|
+
# * +:groupchat+ -- The message is sent in the context of a multi-user chat environment (similar to that
|
40
|
+
# of [IRC]). Typically a receiving client will present a message of type +groupchat+ in an interface
|
41
|
+
# that enables many-to-many chat between the parties, including a roster of parties in the chatroom
|
42
|
+
# and an appropriate conversation history.
|
43
|
+
# * +:headline+ -- The message provides an alert, a notification, or other information to which no reply
|
44
|
+
# is expected (e.g., news headlines, sports updates, near-real-time market data, and syndicated content).
|
45
|
+
# Because no reply to the message is expected, typically a receiving client will present a message of
|
46
|
+
# type "headline" in an interface that appropriately differentiates the message from standalone messages,
|
47
|
+
# chat messages, or groupchat messages (e.g., by not providing the recipient with the ability to reply).
|
48
|
+
# * +:normal+ -- The message is a standalone message that is sent outside the context of a one-to-one
|
49
|
+
# conversation or groupchat, and to which it is expected that the recipient will reply. Typically a receiving
|
50
|
+
# client will present a message of type +normal+ in an interface that enables the recipient to reply, but
|
51
|
+
# without a conversation history. The default value of the +type+ attribute is +normal+.
|
52
|
+
#
|
53
|
+
# Blather provides a helper for each possible type:
|
54
|
+
#
|
55
|
+
# Message#chat?
|
56
|
+
# Message#error?
|
57
|
+
# Message#groupchat?
|
58
|
+
# Message#headline?
|
59
|
+
# Message#normal?
|
60
|
+
#
|
61
|
+
# Blather treats the +type+ attribute like a normal ruby object attribute providing a getter and setter.
|
62
|
+
# The default +type+ is +chat+.
|
63
|
+
#
|
64
|
+
# msg = Message.new
|
65
|
+
# msg.type # => :chat
|
66
|
+
# msg.chat? # => true
|
67
|
+
# msg.type = :normal
|
68
|
+
# msg.normal? # => true
|
69
|
+
# msg.chat? # => false
|
70
|
+
#
|
71
|
+
# msg.type = :invalid # => RuntimeError
|
72
|
+
#
|
73
|
+
# == Body Element
|
74
|
+
#
|
75
|
+
# The +body+ element contains human-readable XML character data that specifies the textual contents of the message;
|
76
|
+
# this child element is normally included but is optional.
|
77
|
+
#
|
78
|
+
# Blather provides an attribute-like syntax for Message +body+ elements.
|
79
|
+
#
|
80
|
+
# msg = Message.new 'user@domain.tld', 'message body'
|
81
|
+
# msg.body # => 'message body'
|
82
|
+
#
|
83
|
+
# msg.body = 'other message'
|
84
|
+
# msg.body # => 'other message'
|
85
|
+
#
|
86
|
+
# == Subject Element
|
87
|
+
#
|
88
|
+
# The +subject+ element contains human-readable XML character data that specifies the topic of the message.
|
89
|
+
#
|
90
|
+
# Blather provides an attribute-like syntax for Message +subject+ elements.
|
91
|
+
#
|
92
|
+
# msg = Message.new 'user@domain.tld', 'message subject'
|
93
|
+
# msg.subject # => 'message subject'
|
94
|
+
#
|
95
|
+
# msg.subject = 'other subject'
|
96
|
+
# msg.subject # => 'other subject'
|
97
|
+
#
|
98
|
+
# == Thread Element
|
99
|
+
#
|
100
|
+
# The primary use of the XMPP +thread+ element is to uniquely identify a conversation thread or "chat session"
|
101
|
+
# between two entities instantiated by Message stanzas of type +chat+. However, the XMPP thread element can
|
102
|
+
# also be used to uniquely identify an analogous thread between two entities instantiated by Message stanzas
|
103
|
+
# of type +headline+ or +normal+, or among multiple entities in the context of a multi-user chat room instantiated
|
104
|
+
# by Message stanzas of type +groupchat+. It MAY also be used for Message stanzas not related to a human
|
105
|
+
# conversation, such as a game session or an interaction between plugins. The +thread+ element is not used to
|
106
|
+
# identify individual messages, only conversations or messagingg sessions. The inclusion of the +thread+ element
|
107
|
+
# is optional.
|
108
|
+
#
|
109
|
+
# The value of the +thread+ element is not human-readable and MUST be treated as opaque by entities; no semantic
|
110
|
+
# meaning can be derived from it, and only exact comparisons can be made against it. The value of the +thread+
|
111
|
+
# element MUST be a universally unique identifier (UUID) as described in [UUID].
|
112
|
+
#
|
113
|
+
# The +thread+ element MAY possess a 'parent' attribute that identifies another thread of which the current
|
114
|
+
# thread is an offshoot or child; the value of the 'parent' must conform to the syntax of the +thread+ element itself.
|
115
|
+
#
|
116
|
+
# Blather provides an attribute-like syntax for Message +thread+ elements.
|
117
|
+
#
|
118
|
+
# msg = Message.new
|
119
|
+
# msg.thread = '12345'
|
120
|
+
# msg.thread # => '12345'
|
121
|
+
#
|
122
|
+
# Parent threads can be set using a hash:
|
123
|
+
#
|
124
|
+
# msg.thread = {'parent-id' => 'thread-id'}
|
125
|
+
# msg.thread # => 'thread-id'
|
126
|
+
# msg.parent_thread # => 'parent-id'
|
127
|
+
#
|
6
128
|
class Message < Stanza
|
7
|
-
VALID_TYPES = [:chat, :error, :groupchat, :headline, :normal]
|
129
|
+
VALID_TYPES = [:chat, :error, :groupchat, :headline, :normal] # :nodoc:
|
8
130
|
|
9
131
|
register :message
|
10
132
|
|
11
|
-
def self.import(node)
|
133
|
+
def self.import(node) # :nodoc:
|
12
134
|
klass = nil
|
13
135
|
node.children.each { |e| break if klass = class_from_registration(e.element_name, (e.namespace.href if e.namespace)) }
|
14
136
|
|
@@ -20,7 +142,7 @@ class Stanza
|
|
20
142
|
end
|
21
143
|
|
22
144
|
def self.new(to = nil, body = nil, type = :chat)
|
23
|
-
node = super
|
145
|
+
node = super :message
|
24
146
|
node.to = to
|
25
147
|
node.type = type
|
26
148
|
node.body = body
|
@@ -29,16 +151,26 @@ class Stanza
|
|
29
151
|
|
30
152
|
attribute_helpers_for :type, VALID_TYPES
|
31
153
|
|
32
|
-
|
33
|
-
# Ensures type is :chat, :error, :groupchat, :headline or :normal
|
34
|
-
def type=(type)
|
154
|
+
def type=(type) # :nodoc:
|
35
155
|
raise ArgumentError, "Invalid Type (#{type}), use: #{VALID_TYPES*' '}" if type && !VALID_TYPES.include?(type.to_sym)
|
36
156
|
super
|
37
157
|
end
|
38
158
|
|
39
159
|
content_attr_accessor :body
|
40
160
|
content_attr_accessor :subject
|
41
|
-
|
161
|
+
|
162
|
+
content_attr_reader :thread
|
163
|
+
|
164
|
+
def parent_thread # :nodoc:
|
165
|
+
n = find_first('thread')
|
166
|
+
n[:parent] if n
|
167
|
+
end
|
168
|
+
|
169
|
+
def thread=(thread) # :nodoc:
|
170
|
+
parent, thread = thread.to_a.flatten if thread.is_a?(Hash)
|
171
|
+
set_content_for :thread, thread
|
172
|
+
find_first('thread')[:parent] = parent
|
173
|
+
end
|
42
174
|
end
|
43
175
|
|
44
176
|
end #Stanza
|
@@ -1,10 +1,54 @@
|
|
1
1
|
module Blather
|
2
2
|
class Stanza
|
3
3
|
|
4
|
-
|
5
|
-
#
|
4
|
+
# = Presence Stanza
|
5
|
+
#
|
6
|
+
# Within Blather most of the interaction with Presence stanzas will be through one of its child classes: Status or Subscription.
|
7
|
+
#
|
8
|
+
# Presence stanzas are used to express an entity's current network availability (offline or online, along with
|
9
|
+
# various sub-states of the latter and optional user-defined descriptive text), and to notify other entities of
|
10
|
+
# that availability. Presence stanzas are also used to negotiate and manage subscriptions to the presence of other entities.
|
11
|
+
#
|
12
|
+
# == Type Attribute
|
13
|
+
#
|
14
|
+
# The +type+ attribute of a presence stanza is optional. A presence stanza that does not possess a +type+ attribute
|
15
|
+
# is used to signal to the server that the sender is online and available for communication. If included, the +type+
|
16
|
+
# attribute specifies a lack of availability, a request to manage a subscription to another entity's presence, a
|
17
|
+
# request for another entity's current presence, or an error related to a previously-sent presence stanza. If included,
|
18
|
+
# the +type+ attribute must have one of the following values:
|
19
|
+
#
|
20
|
+
# * +:unavailable+ -- Signals that the entity is no longer available for communication
|
21
|
+
# * +:subscribe+ -- The sender wishes to subscribe to the recipient's presence.
|
22
|
+
# * +:subscribed+ -- The sender has allowed the recipient to receive their presence.
|
23
|
+
# * +:unsubscribe+ -- The sender is unsubscribing from another entity's presence.
|
24
|
+
# * +:unsubscribed+ -- The subscription request has been denied or a previously-granted subscription has been cancelled.
|
25
|
+
# * +:probe+ -- A request for an entity's current presence; should be generated only by a server on behalf of a user.
|
26
|
+
# * +:error+ -- An error has occurred regarding processing or delivery of a previously-sent presence stanza.
|
27
|
+
#
|
28
|
+
# Blather provides a helper for each possible type:
|
29
|
+
#
|
30
|
+
# Presence#unavailabe?
|
31
|
+
# Presence#unavailable?
|
32
|
+
# Presence#subscribe?
|
33
|
+
# Presence#subscribed?
|
34
|
+
# Presence#unsubscribe?
|
35
|
+
# Presence#unsubscribed?
|
36
|
+
# Presence#probe?
|
37
|
+
# Presence#error?
|
38
|
+
#
|
39
|
+
# Blather treats the +type+ attribute like a normal ruby object attribute providing a getter and setter.
|
40
|
+
# The default +type+ is nil.
|
41
|
+
#
|
42
|
+
# presence = Presence.new
|
43
|
+
# presence.type # => nil
|
44
|
+
# presence.type = :unavailable
|
45
|
+
# presence.unavailable? # => true
|
46
|
+
# presence.error? # => false
|
47
|
+
#
|
48
|
+
# presence.type = :invalid # => RuntimeError
|
49
|
+
#
|
6
50
|
class Presence < Stanza
|
7
|
-
VALID_TYPES = [:unavailable, :subscribe, :subscribed, :unsubscribe, :unsubscribed, :probe, :error]
|
51
|
+
VALID_TYPES = [:unavailable, :subscribe, :subscribed, :unsubscribe, :unsubscribed, :probe, :error] # :nodoc:
|
8
52
|
|
9
53
|
register :presence
|
10
54
|
|
@@ -13,7 +57,7 @@ class Stanza
|
|
13
57
|
# either a Status or Subscription object is created based
|
14
58
|
# on the type attribute.
|
15
59
|
# If neither is found it instantiates a Presence object
|
16
|
-
def self.import(node)
|
60
|
+
def self.import(node) # :nodoc:
|
17
61
|
klass = case node['type']
|
18
62
|
when nil, 'unavailable' then Status
|
19
63
|
when /subscribe/ then Subscription
|
@@ -32,7 +76,7 @@ class Stanza
|
|
32
76
|
|
33
77
|
##
|
34
78
|
# Ensures type is one of :unavailable, :subscribe, :subscribed, :unsubscribe, :unsubscribed, :probe or :error
|
35
|
-
def type=(type)
|
79
|
+
def type=(type) # :nodoc:
|
36
80
|
raise ArgumentError, "Invalid Type (#{type}), use: #{VALID_TYPES*' '}" if type && !VALID_TYPES.include?(type.to_sym)
|
37
81
|
super
|
38
82
|
end
|