sendgrid-ruby 5.1.0 → 5.2.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.
@@ -2,9 +2,11 @@ This documentation provides examples for specific use cases. Please [open an iss
2
2
 
3
3
  # Table of Contents
4
4
 
5
- * [Transactional Templates](#transactional_templates)
5
+ * [Transactional Templates](#transactional-templates)
6
+ * [How to Setup a Domain Whitelabel](#domain-whitelabel)
7
+ * [How to View Email Statistics](#email-statistics)
6
8
 
7
- <a name="transactional_templates"></a>
9
+ <a name="transactional-templates"></a>
8
10
  # Transactional Templates
9
11
 
10
12
  For this example, we assume you have created a [transactional template](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). Following is the template content we used for testing.
@@ -110,4 +112,32 @@ end
110
112
  puts response.status_code
111
113
  puts response.body
112
114
  puts response.headers
113
- ```
115
+ ```
116
+
117
+ ## Adding Attachments
118
+
119
+ ```ruby
120
+ attachment = Attachment.new
121
+ attachment.content = Base64.strict_encode64(File.open(fpath, 'rb').read)
122
+ attachment.type = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
123
+ attachment.filename = fname
124
+ attachment.disposition = 'attachment'
125
+ attachment.content_id = 'Reports Sheet'
126
+ mail.add_attachment(attachment)
127
+
128
+ ```
129
+ Attachments must be base64 encoded, using Base64's strict_encode64 where no line feeds are added.
130
+
131
+ <a name="domain-whitelabel"></a>
132
+ # How to Setup a Domain Whitelabel
133
+
134
+ You can find documentation for how to setup a domain whitelabel via the UI [here](https://sendgrid.com/docs/Classroom/Basics/Whitelabel/setup_domain_whitelabel.html) and via API [here](https://github.com/sendgrid/sendgrid-ruby/blob/master/USAGE.md#whitelabel).
135
+
136
+ Find more information about all of SendGrid's whitelabeling related documentation [here](https://sendgrid.com/docs/Classroom/Basics/Whitelabel/index.html).
137
+
138
+ <a name="email-statistics"></a>
139
+ # How to View Email Statistics
140
+
141
+ You can find documentation for how to view your email statistics via the UI [here](https://app.sendgrid.com/statistics) and via API [here](https://github.com/sendgrid/sendgrid-ruby/blob/master/USAGE.md#stats).
142
+
143
+ Alternatively, we can post events to a URL of your choice via our [Event Webhook](https://sendgrid.com/docs/API_Reference/Webhooks/event.html) about events that occur as SendGrid processes your email.
@@ -0,0 +1,42 @@
1
+ require 'sendgrid-ruby'
2
+ require 'date'
3
+
4
+ include SendGrid
5
+
6
+ sg_client = SendGrid::API.new(api_key: ENV['SENDGRID_API_KEY']).client
7
+ stats = SendGrid::EmailStats.new(sendgrid_client: sg_client)
8
+
9
+ # Fetch stats by day, between 2 dates
10
+ from = Date.new(2017, 10, 01)
11
+ to = Date.new(2017, 10, 12)
12
+
13
+ email_stats = stats.by_day(from, to)
14
+
15
+ email_stats.metrics
16
+
17
+ if !email_stats.error?
18
+ email_stats.metrics.each do |metric|
19
+ puts "Date - #{metric.date}"
20
+ puts "Number of Requests - #{metric.requests}"
21
+ puts "Bounces - #{metric.bounces}"
22
+ puts "Opens - #{metric.opens}"
23
+ puts "Clicks - #{metric.clicks}"
24
+ end
25
+ end
26
+
27
+ # Fetch stats by week, between 2 dates for a category
28
+ from = Date.new(2017, 10, 01)
29
+ to = Date.new(2017, 10, 12)
30
+ category = 'abcd'
31
+
32
+ email_stats = stats.by_week(from, to, category)
33
+
34
+ if !email_stats.error?
35
+ email_stats.metrics.each do |metric|
36
+ puts "Date - #{metric.date}"
37
+ puts "Number of Requests - #{metric.requests}"
38
+ puts "Bounces - #{metric.bounces}"
39
+ puts "Opens - #{metric.opens}"
40
+ puts "Clicks - #{metric.clicks}"
41
+ end
42
+ end
@@ -22,3 +22,6 @@ require_relative 'sendgrid/helpers/mail/subscription_tracking'
22
22
  require_relative 'sendgrid/helpers/mail/substitution'
23
23
  require_relative 'sendgrid/helpers/mail/tracking_settings'
24
24
  require_relative 'sendgrid/helpers/settings/settings'
25
+ require_relative 'sendgrid/helpers/stats/email_stats'
26
+ require_relative 'sendgrid/helpers/stats/stats_response'
27
+ require_relative 'sendgrid/helpers/stats/metrics'
@@ -3,8 +3,12 @@ require 'json'
3
3
  module SendGrid
4
4
  class Email
5
5
  def initialize(email: nil, name: nil)
6
- @email = email
7
- @name = name
6
+ if name
7
+ @email = email
8
+ @name = name
9
+ else
10
+ @email, @name = split_email(email)
11
+ end
8
12
  end
9
13
 
10
14
  def email=(email)
@@ -23,6 +27,11 @@ module SendGrid
23
27
  @name
24
28
  end
25
29
 
30
+ def split_email(email)
31
+ split = /(?:(?<address>.+)\s)?<?(?<email>.+@[^>]+)>?/.match(email)
32
+ return split[:email], split[:address]
33
+ end
34
+
26
35
  def to_json(*)
27
36
  {
28
37
  'email' => self.email,
@@ -1,4 +1,4 @@
1
- **This module allows you to quickly and easily build a Settings object for retreiving or updating you SendGrid Settings.**
1
+ **This module allows you to quickly and easily build a Settings object for retrieving or updating you SendGrid Settings.**
2
2
 
3
3
  # Quick Start
4
4
 
@@ -11,4 +11,4 @@ ruby examples/helpers/settings/example.rb
11
11
  ## Usage
12
12
 
13
13
  - See the [example](https://github.com/sendgrid/sendgrid-ruby/tree/master/examples/helpers/settings) for a complete working example.
14
- - [Documentation](https://sendgrid.com/docs/API_Reference/Web_API_v3/Settings/index.html)
14
+ - [Documentation](https://sendgrid.com/docs/API_Reference/Web_API_v3/Settings/index.html)
@@ -0,0 +1,46 @@
1
+ require 'json'
2
+
3
+ module SendGrid
4
+ class EmailStats
5
+ def initialize(args)
6
+ @sendgrid_client = args[:sendgrid_client]
7
+ end
8
+
9
+ def by_day(start_date, end_date, categories = nil, subusers = nil)
10
+ get('day', start_date, end_date, categories, subusers)
11
+ end
12
+
13
+ def by_week(start_date, end_date, categories = nil, subusers = nil)
14
+ get('week', start_date, end_date, categories, subusers)
15
+ end
16
+
17
+ def by_month(start_date, end_date, categories = nil, subusers = nil)
18
+ get('month', start_date, end_date, categories, subusers)
19
+ end
20
+
21
+ def get(aggregated_by, start_date, end_date, categories = nil, subusers = nil)
22
+ params = query_params(aggregated_by, start_date, end_date, categories, subusers)
23
+
24
+ response_body = @sendgrid_client.stats.get(query_params: params).body
25
+ build_response(response_body)
26
+ end
27
+
28
+ private
29
+
30
+ def query_params(aggregated_by, start_date, end_date, categories, subusers)
31
+ params = {
32
+ aggregated_by: aggregated_by,
33
+ start_date: start_date,
34
+ end_date: end_date
35
+ }
36
+ params.merge(categories: categories) if categories
37
+ params.merge(subusers: subusers) if subusers
38
+ params
39
+ end
40
+
41
+ def build_response(response_body)
42
+ response_json = JSON.parse(response_body)
43
+ StatsResponse.new(response_json)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,35 @@
1
+ require 'json'
2
+
3
+ module SendGrid
4
+ class Metrics
5
+ attr_reader :blocks, :bounce_drops,
6
+ :bounces, :clicks, :deferred, :delivered,
7
+ :invalid_emails, :opens, :processed, :requests,
8
+ :spam_report_drops, :spam_reports, :unique_clicks,
9
+ :unique_opens, :unsubscribe_drops, :unsubscribes
10
+
11
+ def initialize(args={})
12
+ @date = args['date']
13
+ @blocks = args['blocks']
14
+ @bounce_drops = args['bounce_drops']
15
+ @bounces = args['bounces']
16
+ @clicks = args['clicks']
17
+ @deferred = args['deferred']
18
+ @delivered = args['delivered']
19
+ @invalid_emails = args['invalid_emails']
20
+ @opens = args['opens']
21
+ @processed = args['processed']
22
+ @requests = args['requests']
23
+ @spam_report_drops = args['spam_report_drops']
24
+ @spam_reports = args['spam_reports']
25
+ @unique_clicks = args['unique_clicks']
26
+ @unique_opens = args['unique_opens']
27
+ @unsubscribe_drops = args['unsubscribe_drops']
28
+ @unsubscribes = args['unsubscribes']
29
+ end
30
+
31
+ def date
32
+ Date.parse(@date)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,31 @@
1
+ require 'json'
2
+
3
+ module SendGrid
4
+ class StatsResponse
5
+ def initialize(args)
6
+ @errors = args['errors'] if args.is_a? Hash
7
+ @stats = args if args.is_a? Array
8
+ end
9
+
10
+ def errors
11
+ @errors.map do |error|
12
+ error['message']
13
+ end
14
+ end
15
+
16
+ def error?
17
+ !@errors.nil?
18
+ end
19
+
20
+ def metrics
21
+ @stats.flat_map do |stat|
22
+ starting_date = stat['date']
23
+ all_stats_for_date = stat['stats']
24
+
25
+ metrics = all_stats_for_date.map do |metric|
26
+ Metrics.new(metric['metrics'].merge('date' => starting_date))
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,3 +1,3 @@
1
1
  module SendGrid
2
- VERSION = '5.1.0'
2
+ VERSION = '5.2.0'
3
3
  end
@@ -16,9 +16,14 @@ to = SendGrid::Email.new('test@example.com', 'Example User')
16
16
  subject = 'Sending with SendGrid is Fun'
17
17
  plain_text_content = 'and easy to do anywhere, even with Ruby'
18
18
  html_content = '<strong>and easy to do anywhere, even with Ruby</strong>'
19
- msg = SendGrid::Mail.create_single_email(from, to, subject, plain_text_content, html_content)
19
+ msg = SendGrid::Mail.create(from: from,
20
+ tos: to,
21
+ subject: subject,
22
+ plain_text_content: plain_text_content,
23
+ html_content: html_content,
24
+ substitutions: {})
20
25
 
21
- client = SendGrid::ClientFactory.new(api_key: ENV['SENDGRID_API_KEY'])
26
+ client = SendGrid::Client.new(api_key: ENV['SENDGRID_API_KEY'])
22
27
 
23
28
  begin
24
29
  response = client.send_email(msg)
@@ -38,7 +43,7 @@ The following code assumes you are storing the API key in an [environment variab
38
43
  require 'sendgrid-ruby'
39
44
 
40
45
  from = SendGrid::Email.new('test@example.com', 'Example User')
41
- tos = [
46
+ tos = [
42
47
  SendGrid::Email.new('test1@example.com', 'Example User1'),
43
48
  SendGrid::Email.new('test2@example.com', 'Example User2'),
44
49
  SendGrid::Email.new('test3@example.com', 'Example User3')
@@ -46,13 +51,14 @@ tos = [
46
51
  subject = 'Sending with SendGrid is Fun'
47
52
  plain_text_content = 'and easy to do anywhere, even with Ruby'
48
53
  html_content = '<strong>and easy to do anywhere, even with Ruby</strong>'
49
- msg = SendGrid::Mail.create_single_email_to_multiple_recipients(from,
50
- tos,
51
- subject,
52
- plain_text_content,
53
- html_content)
54
+ msg = SendGrid::Mail.create(from: from,
55
+ tos: tos,
56
+ subject: subject,
57
+ plain_text_content: plain_text_content,
58
+ html_content: html_content,
59
+ substitutions: {})
54
60
 
55
- client = SendGrid::ClientFactory.new(api_key: ENV['SENDGRID_API_KEY'])
61
+ client = SendGrid::Client.new(api_key: ENV['SENDGRID_API_KEY'])
56
62
 
57
63
  begin
58
64
  response = client.send_email(msg)
@@ -72,7 +78,7 @@ The following code assumes you are storing the API key in an [environment variab
72
78
  require 'sendgrid-ruby'
73
79
 
74
80
  from = SendGrid::Email.new('test@example.com', 'Example User')
75
- tos = [
81
+ tos = [
76
82
  SendGrid::Email.new('test1@example.com', 'Example User1'),
77
83
  SendGrid::Email.new('test2@example.com', 'Example User2'),
78
84
  SendGrid::Email.new('test3@example.com', 'Example User3')
@@ -92,14 +98,14 @@ values = [
92
98
  substitutions = {
93
99
  '-name1-' => values
94
100
  }
95
- msg = SendGrid::Mail.create_multiple_emails_to_multiple_recipients(from,
96
- tos,
97
- subjects,
98
- plain_text_content,
99
- html_content,
100
- substitutions)
101
+ msg = SendGrid::Mail.create(from: from,
102
+ tos: tos,
103
+ subject: subjects,
104
+ plain_text_content: plain_text_content,
105
+ html_content: html_content,
106
+ substitutions: substitutions)
101
107
 
102
- client = SendGrid::ClientFactory.new(api_key: ENV['SENDGRID_API_KEY'])
108
+ client = SendGrid::Client.new(api_key: ENV['SENDGRID_API_KEY'])
103
109
 
104
110
  begin
105
111
  response = client.send_email(msg)
@@ -116,33 +122,33 @@ puts response.headers
116
122
  The following code assumes you are storing the API key in an [environment variable (recommended)](https://github.com/sendgrid/sendgrid-ruby/blob/master/TROUBLESHOOTING.md#environment-variables-and-your-sendgrid-api-key). If you don't have your key stored in an environment variable, you can assign it directly to `api_key` for testing purposes.
117
123
 
118
124
  ```ruby
119
- client = SendGrid::ClientFactory.new(api_key: ENV['SENDGRID_API_KEY'])
125
+ client = SendGrid::Client.new(api_key: ENV['SENDGRID_API_KEY'])
120
126
 
121
127
  from = SendGrid::Email.new('test@example.com', 'Example User')
122
128
  to = SendGrid::Email.new('test@example.com', 'Example User')
123
129
  subject = 'Sending with SendGrid is Fun'
124
130
  plain_text_content = 'and easy to do anywhere, even with Ruby'
125
131
  html_content = '<strong>and easy to do anywhere, even with Ruby</strong>'
126
- msg = SendGrid::SendGridMessage.new(from, to, subject, plain_text_content, html_content)
132
+ msg = SendGrid::Message.new(from, to, subject, plain_text_content, html_content)
127
133
 
128
134
  # For a detailed description of each of these settings, please see the [documentation](https://sendgrid.com/docs/API_Reference/api_v3.html).
129
135
 
130
136
  msg.add_to(SendGrid::Email.new('test1@example.com', 'Example User1'))
131
- to_emails = [
137
+ to_emails = [
132
138
  SendGrid::Email.new('test2@example.com', 'Example User2'),
133
139
  SendGrid::Email.new('test3@example.com', 'Example User3')
134
140
  ];
135
141
  msg.add_tos(to_emails)
136
142
 
137
143
  msg.add_cc(SendGrid::Email.new('test4@example.com', 'Example User4'))
138
- cc_emails = [
144
+ cc_emails = [
139
145
  SendGrid::Email.new('test5@example.com', 'Example User5'),
140
146
  SendGrid::Email.new('test6@example.com', 'Example User6')
141
147
  ];
142
148
  msg.add_ccs(cc_emails)
143
149
 
144
150
  msg.add_bcc(SendGrid::Email.new('test7@example.com', 'Example User7'))
145
- bcc_emails = [
151
+ bcc_emails = [
146
152
  SendGrid::Email.new('test8@example.com', 'Example User8'),
147
153
  SendGrid::Email.new('test9@example.com', 'Example User9')
148
154
  ];
@@ -179,21 +185,21 @@ msg.set_subject('this subject overrides the Global Subject on the default Person
179
185
  # If you need to add more [Personalizations](https://sendgrid.com/docs/Classroom/Send/v3_Mail_Send/personalizations.html), here is an example of adding another Personalization by passing in a personalization index.
180
186
 
181
187
  msg.add_to(SendGrid::Email.new('test10@example.com', 'Example User10'), 1)
182
- to_emails = [
188
+ to_emails = [
183
189
  SendGrid::Email.new('test11@example.com', 'Example User11'),
184
190
  SendGrid::Email.new('test12@example.com', 'Example User12')
185
191
  ];
186
192
  msg.add_tos(to_emails, 1)
187
193
 
188
194
  msg.add_cc(SendGrid::Email.new('test13@example.com', 'Example User13'), 1)
189
- cc_emails = [
195
+ cc_emails = [
190
196
  SendGrid::Email.new('test14@example.com', 'Example User14'),
191
197
  SendGrid::Email.new('test15@example.com', 'Example User15')
192
198
  ];
193
199
  msg.add_ccs(cc_emails, 1)
194
200
 
195
201
  msg.add_bcc(SendGrid::Email.new('test16@example.com', 'Example User16'), 1)
196
- bcc_emails = [
202
+ bcc_emails = [
197
203
  SendGrid::Email.new('test17@example.com', 'Example User17'),
198
204
  SendGrid::Email.new('test18@example.com', 'Example User18')
199
205
  ];
@@ -292,16 +298,18 @@ puts response.headers
292
298
  The following code assumes you are storing the API key in an [environment variable (recommended)](https://github.com/sendgrid/sendgrid-ruby/blob/master/TROUBLESHOOTING.md#environment-variables-and-your-sendgrid-api-key). If you don't have your key stored in an environment variable, you can assign it directly to `api_key` for testing purposes.
293
299
 
294
300
  ```ruby
295
- client = SendGrid::ClientFactory.new(api_key: ENV['SENDGRID_API_KEY'])
301
+ client = SendGrid::Client.new(api_key: ENV['SENDGRID_API_KEY'])
296
302
 
297
303
  from = SendGrid::Email.new('test@example.com', 'Example User')
298
304
  to = SendGrid::Email.new('test@example.com', 'Example User')
299
305
  subject = 'Sending with SendGrid is Fun'
300
306
  plain_text_content = 'and easy to do anywhere, even with Ruby'
301
307
  html_content = '<strong>and easy to do anywhere, even with Ruby</strong>'
302
- msg = SendGrid::SendGridMessage.new(from, to, subject, plain_text_content, html_content)
308
+ msg = SendGrid::Message.new(from, to, subject, plain_text_content, html_content)
309
+ bytes = File.read('/path/to/the/attachment.pdf')
310
+ encoded = Base64.encode64(bytes)
303
311
  msg.add_attachment('balance_001.pdf',
304
- 'base64 encoded content',
312
+ encoded,
305
313
  'application/pdf',
306
314
  'attachment',
307
315
  'Balance Sheet')
@@ -355,14 +363,14 @@ I hope you are having a great day in -city- :)
355
363
  ```
356
364
 
357
365
  ```ruby
358
- client = SendGrid::ClientFactory.new(api_key: ENV['SENDGRID_API_KEY'])
366
+ client = SendGrid::Client.new(api_key: ENV['SENDGRID_API_KEY'])
359
367
 
360
368
  from = SendGrid::Email.new('test@example.com', 'Example User')
361
369
  to = SendGrid::Email.new('test@example.com', 'Example User')
362
370
  subject = 'Sending with SendGrid is Fun'
363
371
  plain_text_content = 'and easy to do anywhere, even with Ruby'
364
372
  html_content = '<strong>and easy to do anywhere, even with Ruby</strong>'
365
- msg = SendGrid::SendGridMessage.new(from, to, subject, plain_text_content, html_content)
373
+ msg = SendGrid::Message.new(from, to, subject, plain_text_content, html_content)
366
374
 
367
375
  substitutions = [
368
376
  '-name-' => 'Example User',
@@ -0,0 +1,112 @@
1
+ require 'spec_helper'
2
+
3
+ describe SendGrid::EmailStats do
4
+ let(:sg_client) { SendGrid::API.new(api_key: 'abcd').client }
5
+ let(:stats) { SendGrid::EmailStats.new(sendgrid_client: sg_client) }
6
+ let(:sg_response) { double('SendGrid::Response') }
7
+
8
+ let(:sample_response) do
9
+ [{
10
+ "date" => "2017-10-01",
11
+ "stats" => [
12
+ {"metrics" =>
13
+ {
14
+ "blocks" => 101,
15
+ "bounce_drops" => 102,
16
+ "bounces" => 103,
17
+ "clicks" => 104,
18
+ "deferred" => 105,
19
+ "delivered" => 106,
20
+ "invalid_emails" => 107,
21
+ "opens" => 108,
22
+ "processed" => 109,
23
+ "requests" => 110,
24
+ "spam_report_drops" => 111,
25
+ "spam_reports" => 112,
26
+ "unique_clicks" => 113,
27
+ "unique_opens" => 114,
28
+ "unsubscribe_drops" => 115,
29
+ "unsubscribes" => 116
30
+ }
31
+ }
32
+ ]
33
+ }]
34
+ end
35
+
36
+ let(:error_response) do
37
+ {
38
+ "errors" => [
39
+ {
40
+ "message" => "end_date should be a YYYY-MM-DD formatted date"
41
+ }
42
+ ]
43
+ }
44
+ end
45
+
46
+ describe '.new' do
47
+ it 'initializes with SendGrid::Client' do
48
+ expect(stats).to be_a SendGrid::EmailStats
49
+ end
50
+ end
51
+
52
+ describe 'successful response' do
53
+ before do
54
+ allow_any_instance_of(SendGrid::Response).to receive(:body) { sample_response.to_json }
55
+ end
56
+
57
+ describe '#by_day' do
58
+ it 'fetches data aggregated by day' do
59
+ day_stats = stats.by_day('2017-10-01', '2017-10-02')
60
+ day_metrics = day_stats.metrics.first
61
+
62
+ expect(day_metrics).to be_a SendGrid::Metrics
63
+ expect(day_metrics.date.to_s).to eq('2017-10-01')
64
+ expect(day_metrics.requests).to eq(110)
65
+ expect(day_metrics.clicks).to eq(104)
66
+ expect(day_metrics.bounces).to eq(103)
67
+ expect(day_metrics.opens).to eq(108)
68
+ end
69
+ end
70
+
71
+ describe '#by_week' do
72
+ it 'fetches data aggregated by week' do
73
+ day_stats = stats.by_week('2017-10-01', '2017-10-12')
74
+ day_metrics = day_stats.metrics.first
75
+
76
+ expect(day_metrics).to be_a SendGrid::Metrics
77
+ expect(day_metrics.date.to_s).to eq('2017-10-01')
78
+ expect(day_metrics.requests).to eq(110)
79
+ expect(day_metrics.clicks).to eq(104)
80
+ expect(day_metrics.bounces).to eq(103)
81
+ expect(day_metrics.opens).to eq(108)
82
+ end
83
+ end
84
+
85
+ describe '#by_month' do
86
+ it 'fetches data aggregated by month' do
87
+ day_stats = stats.by_month('2017-10-01', '2017-11-01')
88
+ day_metrics = day_stats.metrics.first
89
+
90
+ expect(day_metrics).to be_a SendGrid::Metrics
91
+ expect(day_metrics.date.to_s).to eq('2017-10-01')
92
+ expect(day_metrics.requests).to eq(110)
93
+ expect(day_metrics.clicks).to eq(104)
94
+ expect(day_metrics.bounces).to eq(103)
95
+ expect(day_metrics.opens).to eq(108)
96
+ end
97
+ end
98
+ end
99
+
100
+ describe 'error response' do
101
+ before do
102
+ allow_any_instance_of(SendGrid::Response).to receive(:body) { error_response.to_json }
103
+ end
104
+
105
+ it 'fetches data aggregated by month' do
106
+ day_stats = stats.by_month('2017-10-01', '2017-10-02')
107
+
108
+ expect(day_stats.errors).to include('end_date should be a YYYY-MM-DD formatted date')
109
+ expect(day_stats.error?).to be_truthy
110
+ end
111
+ end
112
+ end