twilio-ruby 3.12.3 → 3.13.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.
- checksums.yaml +8 -8
- data/CHANGES.md +12 -0
- data/README.md +14 -14
- data/Rakefile +2 -2
- data/docs/faq.rst +3 -3
- data/docs/getting-started.rst +17 -12
- data/docs/usage/accounts.rst +6 -6
- data/docs/usage/applications.rst +5 -5
- data/docs/usage/basics.rst +1 -1
- data/docs/usage/caller-ids.rst +4 -2
- data/docs/usage/conferences.rst +1 -1
- data/docs/usage/errors.rst +3 -3
- data/docs/usage/messages.rst +24 -16
- data/docs/usage/phone-calls.rst +10 -8
- data/docs/usage/phone-numbers.rst +15 -11
- data/docs/usage/sip.rst +9 -8
- data/docs/usage/twiml.rst +2 -2
- data/examples/examples.rb +44 -20
- data/lib/rack/twilio_webhook_authentication.rb +5 -1
- data/lib/twilio-ruby/rest/calls.rb +4 -4
- data/lib/twilio-ruby/rest/client.rb +25 -23
- data/lib/twilio-ruby/rest/conferences/participants.rb +2 -2
- data/lib/twilio-ruby/rest/incoming_phone_numbers.rb +1 -1
- data/lib/twilio-ruby/rest/instance_resource.rb +9 -5
- data/lib/twilio-ruby/rest/list_resource.rb +18 -10
- data/lib/twilio-ruby/rest/outgoing_caller_ids.rb +1 -1
- data/lib/twilio-ruby/rest/queues/members.rb +1 -1
- data/lib/twilio-ruby/rest/sip.rb +1 -3
- data/lib/twilio-ruby/rest/utils.rb +11 -3
- data/lib/twilio-ruby/util/capability.rb +4 -4
- data/lib/twilio-ruby/version.rb +1 -1
- data/spec/rack/twilio_webhook_authentication_spec.rb +18 -6
- data/spec/rest/account_spec.rb +24 -8
- data/spec/rest/call_spec.rb +6 -2
- data/spec/rest/client_spec.rb +37 -14
- data/spec/rest/conference_spec.rb +3 -1
- data/spec/rest/instance_resource_spec.rb +1 -1
- data/spec/rest/numbers_spec.rb +18 -6
- data/spec/rest/queue_spec.rb +3 -1
- data/spec/rest/recording_spec.rb +3 -1
- data/spec/util/url_encode_spec.rb +1 -1
- metadata +1 -1
data/docs/usage/sip.rst
CHANGED
@@ -27,9 +27,9 @@ under sip.twilio.com.
|
|
27
27
|
@client = Twilio::REST::Client.new account_sid, auth_token
|
28
28
|
|
29
29
|
@domain = @client.sip.domains.create(
|
30
|
-
|
31
|
-
|
32
|
-
|
30
|
+
friendly_name: "The Office Domain",
|
31
|
+
voice_url: "http://example.com/voice"
|
32
|
+
domain_name: "dunder-mifflin-scranton.sip.twilio.com"
|
33
33
|
)
|
34
34
|
puts @domain.sid
|
35
35
|
|
@@ -51,7 +51,7 @@ to individual ip addresses. To do this, you'll first need to create an
|
|
51
51
|
@client = Twilio::REST::Client.new account_sid, auth_token
|
52
52
|
|
53
53
|
@ip_acl = @client.sip.ip_access_control_lists.create(
|
54
|
-
|
54
|
+
friendly_name: "The Office IpAccessControlList"
|
55
55
|
)
|
56
56
|
puts @ip_acl.sid
|
57
57
|
|
@@ -71,10 +71,10 @@ Now it's time to add an :class:`IpAddress` to your new :class:`IpAccessControlLi
|
|
71
71
|
@client = Twilio::REST::Client.new account_sid, auth_token
|
72
72
|
|
73
73
|
@ip_address = @client.sip.ip_access_control_lists.get(
|
74
|
-
|
74
|
+
"AL456", # IpAccessControlList sid
|
75
75
|
).ip_addresses.create(
|
76
|
-
|
77
|
-
|
76
|
+
friendly_name: "Dwights's Computer",
|
77
|
+
ip_address: "192.168.1.42"
|
78
78
|
)
|
79
79
|
puts @ip_address.sid
|
80
80
|
|
@@ -97,7 +97,8 @@ associate them. To do this, create an :class:`IpAccessControlListMapping`.
|
|
97
97
|
@ip_acl_mapping = @client.sip.domains.get(
|
98
98
|
"SD456", # SIP Domain sid
|
99
99
|
).ip_access_control_list_mappings.create(
|
100
|
-
|
100
|
+
ip_access_control_list_sid: "AL789"
|
101
|
+
)
|
101
102
|
|
102
103
|
puts @ip_acl_mapping.sid
|
103
104
|
|
data/docs/usage/twiml.rst
CHANGED
@@ -35,7 +35,7 @@ All attributes are keyword arguments.
|
|
35
35
|
require 'twilio-ruby'
|
36
36
|
|
37
37
|
Twilio::TwiML::Response.new do |r|
|
38
|
-
r.Play "https://api.twilio.com/cowbell.mp3", :
|
38
|
+
r.Play "https://api.twilio.com/cowbell.mp3", loop: 5
|
39
39
|
end.text
|
40
40
|
|
41
41
|
.. code-block:: xml
|
@@ -53,7 +53,7 @@ Any example of nesting nouns in verbs
|
|
53
53
|
|
54
54
|
Twilio::TwiML::Response.new do |r|
|
55
55
|
r.Say "hello"
|
56
|
-
r.Gather :
|
56
|
+
r.Gather finishOnKey: => 4 do |g|
|
57
57
|
g.Say "world"
|
58
58
|
end
|
59
59
|
end.text
|
data/examples/examples.rb
CHANGED
@@ -8,7 +8,7 @@
|
|
8
8
|
################ ACCOUNTS ################
|
9
9
|
|
10
10
|
# shortcut to grab your account object (account_sid is inferred from the client's auth credentials)
|
11
|
-
@account = @client.account
|
11
|
+
@account = @client.account
|
12
12
|
|
13
13
|
# list your (sub)accounts
|
14
14
|
@client.accounts.list
|
@@ -19,12 +19,16 @@
|
|
19
19
|
puts @account.friendly_name
|
20
20
|
|
21
21
|
# update an account's friendly name
|
22
|
-
@client.accounts.get(@account_sid).update(:
|
22
|
+
@client.accounts.get(@account_sid).update(friendly_name: 'A Fabulous Friendly Name')
|
23
23
|
|
24
24
|
################ CALLS ################
|
25
25
|
|
26
26
|
# print a list of calls (all parameters optional)
|
27
|
-
@account.calls.list(
|
27
|
+
@account.calls.list(
|
28
|
+
page: 0,
|
29
|
+
page_size: 1000,
|
30
|
+
start_time: '2010-09-01'
|
31
|
+
).each do |call|
|
28
32
|
puts call.sid
|
29
33
|
end
|
30
34
|
|
@@ -34,18 +38,23 @@ end
|
|
34
38
|
end
|
35
39
|
|
36
40
|
# make a new outgoing call. returns a call object just like calls.get
|
37
|
-
@call = @account.calls.create(
|
41
|
+
@call = @account.calls.create(
|
42
|
+
from: '+14159341234',
|
43
|
+
to: '+18004567890',
|
44
|
+
url: 'http://example.com/call-handler'
|
45
|
+
)
|
38
46
|
|
39
47
|
# cancel the call if not already in progress
|
40
|
-
@account.calls.get(@call.sid).update(
|
48
|
+
@account.calls.get(@call.sid).update(status: 'canceled')
|
41
49
|
# or equivalently
|
42
|
-
@call.update(
|
50
|
+
@call.update(status: 'canceled')
|
43
51
|
# or simply
|
44
52
|
@call.cancel
|
45
53
|
|
46
54
|
# redirect and then terminate a call
|
47
|
-
@account.calls.get('CA386025c9bf5d6052a1d1ea42b4d16662')
|
48
|
-
@
|
55
|
+
@call = @account.calls.get('CA386025c9bf5d6052a1d1ea42b4d16662')
|
56
|
+
@call.update(url: 'http://example.com/call-redirect')
|
57
|
+
@call.update(status: 'completed')
|
49
58
|
# or, use the aliases...
|
50
59
|
@call.redirect_to('http://example.com/call-redirect')
|
51
60
|
@call.hangup
|
@@ -53,7 +62,7 @@ end
|
|
53
62
|
################ SMS MESSAGES ################
|
54
63
|
|
55
64
|
# print a list of messages
|
56
|
-
@account.messages.list(
|
65
|
+
@account.messages.list(date_sent: '2010-09-01').each do |message|
|
57
66
|
puts message.body
|
58
67
|
end
|
59
68
|
|
@@ -61,10 +70,18 @@ end
|
|
61
70
|
puts @account.messages.get('SMXXXXXXXX').body
|
62
71
|
|
63
72
|
# send an sms
|
64
|
-
@account.messages.create(
|
73
|
+
@account.messages.create(
|
74
|
+
from: '+14159341234',
|
75
|
+
to: '+16105557069',
|
76
|
+
body: 'Hey there!'
|
77
|
+
)
|
65
78
|
|
66
79
|
# send an mms
|
67
|
-
@account.messages.create(
|
80
|
+
@account.messages.create(
|
81
|
+
from: '+14159341234',
|
82
|
+
to: '+16105557069',
|
83
|
+
media_urls: 'http://example.com/media.png'
|
84
|
+
)
|
68
85
|
|
69
86
|
################ PHONE NUMBERS ################
|
70
87
|
|
@@ -72,22 +89,28 @@ puts @account.messages.get('SMXXXXXXXX').body
|
|
72
89
|
@account.available_phone_numbers.list
|
73
90
|
|
74
91
|
# print some available numbers
|
75
|
-
@numbers = @account.available_phone_numbers.get('US').local.list(
|
76
|
-
|
92
|
+
@numbers = @account.available_phone_numbers.get('US').local.list(
|
93
|
+
contains: 'AWESOME'
|
94
|
+
)
|
95
|
+
@numbers.each { |num| puts num.phone_number }
|
77
96
|
|
78
97
|
# buy the first one
|
79
|
-
@account.incoming_phone_numbers.create(:
|
98
|
+
@account.incoming_phone_numbers.create(phone_number: @numbers[0].phone_number)
|
80
99
|
|
81
100
|
# update an existing phone number's voice url
|
82
|
-
@account.incoming_phone_numbers.get('PNdba508c5616a7f5e141789f44f022cc3')
|
101
|
+
number = @account.incoming_phone_numbers.get('PNdba508c5616a7f5e141789f44f022cc3')
|
102
|
+
number.update(voice_url: 'http://example.com/voice')
|
83
103
|
|
84
104
|
# decommission an existing phone number
|
85
|
-
numbers = @account.incoming_phone_numbers.list(
|
105
|
+
numbers = @account.incoming_phone_numbers.list(
|
106
|
+
friendly_name: 'A Fabulous Friendly Name'
|
107
|
+
)
|
86
108
|
numbers[0].delete
|
87
109
|
################ CONFERENCES ################
|
88
110
|
|
89
111
|
# get a particular conference's participants object and stash it
|
90
|
-
|
112
|
+
conference = @account.conferences.get('CFbbe46ff1274e283f7e3ac1df0072ab39')
|
113
|
+
@participants = conference.participants
|
91
114
|
|
92
115
|
# list participants
|
93
116
|
@participants.list.each do |p|
|
@@ -95,17 +118,18 @@ numbers[0].delete
|
|
95
118
|
end
|
96
119
|
|
97
120
|
# update a conference participant
|
98
|
-
@participants.get('CA386025c9bf5d6052a1d1ea42b4d16662').update(
|
121
|
+
@participants.get('CA386025c9bf5d6052a1d1ea42b4d16662').update(muted: 'true')
|
99
122
|
# or an easier way
|
100
123
|
@participants.get('CA386025c9bf5d6052a1d1ea42b4d16662').mute
|
101
124
|
|
102
125
|
# and, since we're lazy loading, this would only incur one http request
|
103
|
-
@account.conferences.get('CFbbe46ff1274e283f7e3ac1df0072ab39').participants
|
126
|
+
@account.conferences.get('CFbbe46ff1274e283f7e3ac1df0072ab39').participants
|
127
|
+
.get('CA386025c9bf5d6052a1d1ea42b4d16662').update(muted: 'true')
|
104
128
|
|
105
129
|
################ QUEUES ###################
|
106
130
|
|
107
131
|
# create a new queue
|
108
|
-
@queue = @account.queues.create(:
|
132
|
+
@queue = @account.queues.create(friendly_name: 'MyQueue', max_size: 50)
|
109
133
|
|
110
134
|
# get a list of queues for this account
|
111
135
|
@queues = @account.queues.list
|
@@ -33,7 +33,11 @@ module Rack
|
|
33
33
|
if validator.validate(original_url, params, signature)
|
34
34
|
@app.call(env)
|
35
35
|
else
|
36
|
-
[
|
36
|
+
[
|
37
|
+
403,
|
38
|
+
{'Content-Type' => 'text/plain'},
|
39
|
+
["Twilio Request Validation Failed."]
|
40
|
+
]
|
37
41
|
end
|
38
42
|
end
|
39
43
|
end
|
@@ -7,7 +7,7 @@ module Twilio
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def make(from, to, url)
|
10
|
-
create :
|
10
|
+
create from: from, to: to, url: url
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
@@ -18,15 +18,15 @@ module Twilio
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def redirect_to(url)
|
21
|
-
update :
|
21
|
+
update url: url
|
22
22
|
end
|
23
23
|
|
24
24
|
def cancel
|
25
|
-
update :
|
25
|
+
update status: 'canceled'
|
26
26
|
end
|
27
27
|
|
28
28
|
def hangup
|
29
|
-
update :
|
29
|
+
update status: 'completed'
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
@@ -63,21 +63,23 @@ module Twilio
|
|
63
63
|
HTTP_HEADERS = {
|
64
64
|
'Accept' => 'application/json',
|
65
65
|
'Accept-Charset' => 'utf-8',
|
66
|
-
'User-Agent' => "twilio-ruby/#{Twilio::VERSION}
|
66
|
+
'User-Agent' => "twilio-ruby/#{Twilio::VERSION}" \
|
67
|
+
" (#{engine}/#{RUBY_PLATFORM}" \
|
68
|
+
" #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL})"
|
67
69
|
}
|
68
70
|
|
69
71
|
DEFAULTS = {
|
70
|
-
:
|
71
|
-
:
|
72
|
-
:
|
73
|
-
:
|
74
|
-
:
|
75
|
-
:
|
76
|
-
:
|
77
|
-
:
|
78
|
-
:
|
79
|
-
:
|
80
|
-
:
|
72
|
+
host: 'api.twilio.com',
|
73
|
+
port: 443,
|
74
|
+
use_ssl: true,
|
75
|
+
ssl_verify_peer: true,
|
76
|
+
ssl_ca_file: File.dirname(__FILE__) + '/../../../conf/cacert.pem',
|
77
|
+
timeout: 30,
|
78
|
+
proxy_addr: nil,
|
79
|
+
proxy_port: nil,
|
80
|
+
proxy_user: nil,
|
81
|
+
proxy_pass: nil,
|
82
|
+
retry_limit: 1
|
81
83
|
}
|
82
84
|
|
83
85
|
attr_reader :account_sid, :account, :accounts, :last_request,
|
@@ -91,36 +93,36 @@ module Twilio
|
|
91
93
|
# hash of connection configuration options. the following keys are
|
92
94
|
# supported:
|
93
95
|
#
|
94
|
-
# === <tt
|
96
|
+
# === <tt>host: 'api.twilio.com'</tt>
|
95
97
|
#
|
96
98
|
# The domain to which you'd like the client to make HTTP requests. Useful
|
97
99
|
# for testing. Defaults to 'api.twilio.com'.
|
98
100
|
#
|
99
|
-
# === <tt
|
101
|
+
# === <tt>port: 443</tt>
|
100
102
|
#
|
101
103
|
# The port on which to connect to the above domain. Defaults to 443 and
|
102
104
|
# should be left that way except in testing environments.
|
103
105
|
#
|
104
|
-
# === <tt
|
106
|
+
# === <tt>use_ssl: true</tt>
|
105
107
|
#
|
106
108
|
# Declare whether ssl should be used for connections to the above domain.
|
107
109
|
# Defaults to true and should be left alone except when testing.
|
108
110
|
#
|
109
|
-
# === <tt
|
111
|
+
# === <tt>ssl_verify_peer: true</tt>
|
110
112
|
#
|
111
113
|
# Declare whether to verify the host's ssl cert when setting up the
|
112
114
|
# connection to the above domain. Defaults to true, but can be turned off
|
113
115
|
# to avoid ssl certificate verification failures in environments without
|
114
116
|
# the necessary ca certificates.
|
115
117
|
#
|
116
|
-
# === <tt
|
118
|
+
# === <tt>ssl_ca_file: '/path/to/ca/file'</tt>
|
117
119
|
#
|
118
120
|
# Specify the path to the certificate authority bundle you'd like to use
|
119
121
|
# to verify Twilio's SSL certificate on each request. If not specified, a
|
120
122
|
# certificate bundle extraced from Firefox is packaged with the gem and
|
121
123
|
# used by default.
|
122
124
|
#
|
123
|
-
# === <tt
|
125
|
+
# === <tt>timeout: 30</tt>
|
124
126
|
#
|
125
127
|
# Set the time in seconds to wait before timing out the HTTP request.
|
126
128
|
# Defaults to 30 seconds. If you aren't fetching giant pages of call or
|
@@ -128,24 +130,24 @@ module Twilio
|
|
128
130
|
# lower. In paricular if you are sending SMS you can set this to 1 second
|
129
131
|
# or less and swallow the exception if you don't care about the response.
|
130
132
|
#
|
131
|
-
# === <tt
|
133
|
+
# === <tt>proxy_addr: 'proxy.host.domain'</tt>
|
132
134
|
#
|
133
135
|
# The domain of a proxy through which you'd like the client to make HTTP
|
134
136
|
# requests. Defaults to nil.
|
135
137
|
#
|
136
|
-
# === <tt
|
138
|
+
# === <tt>proxy_port: 3128</tt>
|
137
139
|
#
|
138
140
|
# The port on which to connect to the above proxy. Defaults to nil.
|
139
141
|
#
|
140
|
-
# === <tt
|
142
|
+
# === <tt>proxy_user: 'username'</tt>
|
141
143
|
#
|
142
144
|
# The user name to use for authentication with the proxy. Defaults to nil.
|
143
145
|
#
|
144
|
-
# === <tt
|
146
|
+
# === <tt>proxy_pass: 'password'</tt>
|
145
147
|
#
|
146
148
|
# The password to use for authentication with the proxy. Defaults to nil.
|
147
149
|
#
|
148
|
-
# === <tt
|
150
|
+
# === <tt>retry_limit: 1</tt>
|
149
151
|
#
|
150
152
|
# The number of times to retry a request that has failed before throwing
|
151
153
|
# an exception. Defaults to one.
|
@@ -32,7 +32,7 @@ module Twilio
|
|
32
32
|
# to handle the update. For example, to update the +VoiceUrl+ of a Twilio
|
33
33
|
# Application you could write:
|
34
34
|
#
|
35
|
-
# @app.update :
|
35
|
+
# @app.update voice_url: 'http://my.other.app.com/handle_voice'
|
36
36
|
#
|
37
37
|
# After returning, the object will contain the most recent state of the
|
38
38
|
# instance resource, including the newly updated properties.
|
@@ -77,23 +77,27 @@ module Twilio
|
|
77
77
|
hash.each do |p,v|
|
78
78
|
property = detwilify p
|
79
79
|
unless ['client', 'updated'].include? property
|
80
|
-
eigenclass.send :define_method, property.to_sym, &lambda {v}
|
80
|
+
eigenclass.send :define_method, property.to_sym, &lambda { v }
|
81
81
|
end
|
82
82
|
end
|
83
83
|
@updated = !hash.keys.empty?
|
84
84
|
end
|
85
85
|
|
86
86
|
def resource(*resources)
|
87
|
-
custom_resource_names = {:
|
87
|
+
custom_resource_names = { sms: 'SMS', sip: 'SIP' }
|
88
88
|
resources.each do |r|
|
89
89
|
resource = twilify r
|
90
90
|
relative_path = custom_resource_names.fetch(r, resource)
|
91
91
|
path = "#{@path}/#{relative_path}"
|
92
|
-
enclosing_module = @submodule == nil
|
92
|
+
enclosing_module = if @submodule == nil
|
93
|
+
Twilio::REST
|
94
|
+
else
|
95
|
+
Twilio::REST.const_get(@submodule)
|
96
|
+
end
|
93
97
|
resource_class = enclosing_module.const_get resource
|
94
98
|
instance_variable_set("@#{r}", resource_class.new(path, @client))
|
95
99
|
end
|
96
|
-
self.class.instance_eval {attr_reader *resources}
|
100
|
+
self.class.instance_eval { attr_reader *resources }
|
97
101
|
end
|
98
102
|
|
99
103
|
end
|
@@ -5,9 +5,9 @@ module Twilio
|
|
5
5
|
|
6
6
|
def initialize(path, client)
|
7
7
|
custom_names = {
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
'Media' => 'MediaInstance',
|
9
|
+
'IpAddresses' => 'IpAddress',
|
10
|
+
'Feedback' => 'FeedbackInstance'
|
11
11
|
}
|
12
12
|
@path, @client = path, client
|
13
13
|
resource_name = self.class.name.split('::')[-1]
|
@@ -16,7 +16,11 @@ module Twilio
|
|
16
16
|
# The next line grabs the enclosing module. Necessary for resources
|
17
17
|
# contained in their own submodule like /SMS/Messages
|
18
18
|
parent_module = self.class.to_s.split('::')[-2]
|
19
|
-
full_module_path = parent_module ==
|
19
|
+
full_module_path = if parent_module == "REST"
|
20
|
+
Twilio::REST
|
21
|
+
else
|
22
|
+
Twilio::REST.const_get parent_module
|
23
|
+
end
|
20
24
|
|
21
25
|
@instance_class = full_module_path.const_get instance_name
|
22
26
|
@list_key, @instance_id_key = detwilify(resource_name), 'sid'
|
@@ -49,7 +53,7 @@ module Twilio
|
|
49
53
|
client, list_class = @client, self.class
|
50
54
|
resource_list.instance_eval do
|
51
55
|
eigenclass = class << self; self; end
|
52
|
-
eigenclass.send :define_method, :total, &lambda {response['total']}
|
56
|
+
eigenclass.send :define_method, :total, &lambda { response['total'] }
|
53
57
|
eigenclass.send :define_method, :next_page, &lambda {
|
54
58
|
if response['next_page_uri']
|
55
59
|
list_class.new(response['next_page_uri'], client).list({}, true)
|
@@ -70,7 +74,7 @@ module Twilio
|
|
70
74
|
# +total+ attribute as well.
|
71
75
|
def total
|
72
76
|
raise "Can't get a resource total without a REST Client" unless @client
|
73
|
-
@client.get(@path, :
|
77
|
+
@client.get(@path, page_size: 1)['total']
|
74
78
|
end
|
75
79
|
|
76
80
|
##
|
@@ -98,18 +102,22 @@ module Twilio
|
|
98
102
|
|
99
103
|
def resource(*resources)
|
100
104
|
custom_resource_names = {
|
101
|
-
:
|
102
|
-
:
|
105
|
+
sms: 'SMS',
|
106
|
+
sip: 'SIP'
|
103
107
|
}
|
104
108
|
resources.each do |r|
|
105
109
|
resource = twilify r
|
106
110
|
relative_path = custom_resource_names.fetch(r, resource)
|
107
111
|
path = "#{@path}/#{relative_path}"
|
108
|
-
enclosing_module = @submodule == nil
|
112
|
+
enclosing_module = if @submodule == nil
|
113
|
+
Twilio::REST
|
114
|
+
else
|
115
|
+
Twilio::REST.const_get(@submodule)
|
116
|
+
end
|
109
117
|
resource_class = enclosing_module.const_get resource
|
110
118
|
instance_variable_set("@#{r}", resource_class.new(path, @client))
|
111
119
|
end
|
112
|
-
self.class.instance_eval {attr_reader *resources}
|
120
|
+
self.class.instance_eval { attr_reader *resources }
|
113
121
|
end
|
114
122
|
end
|
115
123
|
end
|