action_smser 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.md +191 -0
  3. data/Rakefile +39 -0
  4. data/app/assets/javascripts/action_smser/delivery_reports.js +2 -0
  5. data/app/assets/stylesheets/action_smser/application.css +7 -0
  6. data/app/assets/stylesheets/action_smser/delivery_reports.css +4 -0
  7. data/app/assets/stylesheets/scaffold.css +56 -0
  8. data/app/controllers/action_smser/application_controller.rb +4 -0
  9. data/app/controllers/action_smser/delivery_reports_controller.rb +63 -0
  10. data/app/models/action_smser/delivery_report.rb +30 -0
  11. data/app/views/action_smser/delivery_reports/index.html.erb +143 -0
  12. data/app/views/layouts/action_smser/delivery_reports.html.erb +56 -0
  13. data/config/routes.rb +7 -0
  14. data/db/migrate/20120102215215_create_action_smser_delivery_reports.rb +19 -0
  15. data/lib/action_smser.rb +24 -0
  16. data/lib/action_smser/base.rb +133 -0
  17. data/lib/action_smser/delivery_methods/simple_http.rb +70 -0
  18. data/lib/action_smser/delivery_methods/test_array.rb +30 -0
  19. data/lib/action_smser/engine.rb +5 -0
  20. data/lib/action_smser/version.rb +3 -0
  21. data/lib/tasks/action_smser_tasks.rake +4 -0
  22. data/test/action_smser_test.rb +7 -0
  23. data/test/dummy/Rakefile +7 -0
  24. data/test/dummy/app/assets/javascripts/application.js +7 -0
  25. data/test/dummy/app/assets/stylesheets/application.css +7 -0
  26. data/test/dummy/app/controllers/application_controller.rb +3 -0
  27. data/test/dummy/app/helpers/application_helper.rb +2 -0
  28. data/test/dummy/app/mailers/hello_smser.rb +9 -0
  29. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  30. data/test/dummy/config.ru +4 -0
  31. data/test/dummy/config/application.rb +51 -0
  32. data/test/dummy/config/boot.rb +10 -0
  33. data/test/dummy/config/database.yml +26 -0
  34. data/test/dummy/config/environment.rb +5 -0
  35. data/test/dummy/config/environments/development.rb +30 -0
  36. data/test/dummy/config/environments/production.rb +60 -0
  37. data/test/dummy/config/environments/test.rb +39 -0
  38. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  39. data/test/dummy/config/initializers/git_ignored_passwords.rb +10 -0
  40. data/test/dummy/config/initializers/inflections.rb +10 -0
  41. data/test/dummy/config/initializers/mime_types.rb +5 -0
  42. data/test/dummy/config/initializers/secret_token.rb +7 -0
  43. data/test/dummy/config/initializers/session_store.rb +8 -0
  44. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  45. data/test/dummy/config/locales/en.yml +5 -0
  46. data/test/dummy/config/routes.rb +4 -0
  47. data/test/dummy/db/development.sqlite3 +0 -0
  48. data/test/dummy/db/schema.rb +28 -0
  49. data/test/dummy/db/test.sqlite3 +0 -0
  50. data/test/dummy/log/development.log +540 -0
  51. data/test/dummy/log/test.log +18246 -0
  52. data/test/dummy/public/404.html +26 -0
  53. data/test/dummy/public/422.html +26 -0
  54. data/test/dummy/public/500.html +26 -0
  55. data/test/dummy/public/favicon.ico +0 -0
  56. data/test/dummy/script/rails +6 -0
  57. data/test/functional/action_smser/delivery_reports_controller_test.rb +118 -0
  58. data/test/integration/navigation_test.rb +11 -0
  59. data/test/test_helper.rb +25 -0
  60. data/test/unit/action_smser/base_test.rb +83 -0
  61. data/test/unit/action_smser/delivery_methods/simple_http_test.rb +52 -0
  62. data/test/unit/action_smser/delivery_methods/test_array_test.rb +42 -0
  63. data/test/unit/action_smser/delivery_report_test.rb +54 -0
  64. metadata +212 -0
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2012 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,191 @@
1
+ # ActionSmser
2
+
3
+ Simple way to use SMS (Short Message Service) in the same way as ActionMailer (ActionSmser == ActionMailer).
4
+ Includes also delivery reports and easy way to add custom gateways. See examples below.
5
+
6
+ [<img src="https://secure.travis-ci.org/holli/action_smser.png" />](http://travis-ci.org/holli/action_smser)
7
+
8
+ # Install
9
+
10
+
11
+ ## Common
12
+
13
+ ```
14
+ Gemfile ->
15
+
16
+ gem 'action_smser'
17
+
18
+ # To use delivery reports
19
+ bundle exec rake railties:install:migrations FROM=ActionSmser
20
+ rake db:migrate
21
+ ```
22
+
23
+ ## SMS sending basic
24
+
25
+ **Initializing**
26
+
27
+ ```
28
+ in /config/initializers/active_smser.rb
29
+
30
+ if Rails.env.development? || Rails.env.production?
31
+
32
+ ActionSmser.delivery_options[:delivery_method] = :simple_http
33
+ ActionSmser.delivery_options[:simple_http] = {
34
+ :server => 'server_to_use', :username => 'username', :password => 'password',
35
+ :use_ssl => true
36
+ }
37
+
38
+ # ActionSmser.delivery_options[:save_delivery_reports] = true
39
+ end
40
+
41
+ ```
42
+
43
+ **Mailer classes**
44
+
45
+ ```
46
+ in /app/mailers/test_sms.rb
47
+
48
+ class TestSms < ActionSmser::Base
49
+ def hello_user(to, from, user)
50
+ str = "Hello #{user}."
51
+ sms(:to => to, :from => from, :body => str)
52
+ end
53
+ end
54
+ ```
55
+
56
+ **Sending sms**
57
+
58
+ ```
59
+ E.g. from console or controller
60
+
61
+ sms = TestSms.hello_user('358407573855', '358407573855', "Olli")
62
+ sms.deliver
63
+ ```
64
+
65
+ ## Delivery methods
66
+
67
+ Optional delivery methods can be used by creating classes under "ActionSmser::DeliveryMethods" and referring
68
+ them as downcase infos. See example of :simple_http at
69
+ https://github.com/holli/action_smser/blob/master/lib/action_smser/delivery_methods/simple_http.rb
70
+
71
+ ## Delivery reports
72
+
73
+ Gem handles collecting and analysing of delivery reports. This enables you to make sure that your gateway works.
74
+
75
+ Delivery reports and a summary can be seen at http://localhost.inv:3000/action_smser/delivery_reports/ .
76
+ Summary includes info about the amount of sent sms, delivery times, types of sms, etc.
77
+
78
+ Parsers and access infos can be implemented by creating a class with admin_access and process_delivery_report methods.
79
+ See example below.
80
+
81
+ ```
82
+ in /config/initializers/active_smser.rb
83
+
84
+ ActionSmser.delivery_options[:save_delivery_reports] = true
85
+
86
+
87
+ class ActionSmserConfigExample
88
+ # This returns true if we can show delivery reports page, return true if always permissed
89
+ def self.admin_access(controller)
90
+ if controller.session[:admin_logged].blank?
91
+ return controller.session[:admin_logged]
92
+ else
93
+ return true
94
+ end
95
+ end
96
+
97
+ # This has to return array of hashes. In hash msg_id is the key and other params are updated to db
98
+ def self.process_delivery_report(params)
99
+ processable_array = []
100
+ if params["DeliveryReport"] && params["DeliveryReport"]["message"]
101
+ reports = params["DeliveryReport"]["message"]
102
+ reports = [reports] unless reports.is_a?(Array)
103
+
104
+ reports.each do |report|
105
+ processable_array << {'msg_id' => report['id'], 'status' => report['status']}
106
+ end
107
+ end
108
+
109
+ return processable_array
110
+ end
111
+ end
112
+
113
+
114
+ # This is simple proc that is used in a before filter, if it returns true it allows access to
115
+ # http://localhost.inv:3000/action_smser/delivery_reports/ with infos about delivery reports
116
+ ActionSmser.delivery_options[:admin_access] = ActionSmserConfigExample
117
+
118
+ # This gives ActionSmser way to parse infos from pushed to gateway
119
+ # Params is all params gotten in the request
120
+ test_gateway = lambda
121
+
122
+ # Parser is used with urls like
123
+ # /action_smser/delivery_reports/gateway_commit/test_gateway
124
+ # where 'test_gateway' is the part that is used for locating right parser.
125
+ ActionSmser.delivery_options[:gateway_commit] = {'test_gateway' => test_gateway}
126
+
127
+
128
+ ```
129
+
130
+ ## Other options
131
+
132
+ Observers can be used by implementing "delivery_observer" in your sms class
133
+
134
+ ```
135
+ class TestSms < ActionSmser::Base
136
+ def hello_user(to, from, user)
137
+ str = "Hello #{user}."
138
+ sms(:to => to, :from => from, :body => str)
139
+ end
140
+
141
+ def after_delivery(response_from_delivery_method)
142
+ puts "Done with delivery"
143
+ end
144
+ end
145
+ ```
146
+
147
+ ## Testing
148
+
149
+ Default delivery method is "test_array". It saves delivered sms to ActionSmser::DeliveryMethods::TestArray.deliveries to help test your own software.
150
+ Its normal array, see sms by 'ActionSmser::DeliveryMethods::TestArray.deliveries' and
151
+ clear it between tests by 'ActionSmser::DeliveryMethods::TestArray.deliveries.clear'
152
+
153
+
154
+ ```
155
+ E.g. in functional tests
156
+
157
+ test "should send right msg" do
158
+ ActionSmser::DeliveryMethods::TestArray.deliveries.clear
159
+ get :send_invite, :user_id => 1
160
+
161
+ assert_equal 1, ActionSmser::DeliveryMethods::TestArray.deliveries.size
162
+ assert_equal "UserSms.sent_invite", ActionSmser::DeliveryMethods::TestArray.deliveries.first.sms_type
163
+ end
164
+
165
+ ```
166
+
167
+ # Requirements
168
+
169
+ Gem has been tested with ruby 1.8.7, 1.9.2 and Rails 3.1.
170
+
171
+ [<img src="https://secure.travis-ci.org/holli/action_smser.png" />](http://travis-ci.org/holli/action_smser)
172
+
173
+ http://travis-ci.org/#!/holli/action_smser
174
+
175
+ # Support
176
+
177
+ Submit suggestions or feature requests as a GitHub Issue or Pull Request. Remember to update tests. Tests are quite extensive.
178
+
179
+
180
+ ### Similar gems
181
+
182
+ There are many gems to use custom gateways but none of them had a possibility to create classes like smser. Also these don't have
183
+ delivery reporting.
184
+
185
+ - https://github.com/dwilkie/action_sms
186
+ - https://github.com/forrestgrant/textr
187
+
188
+ # Licence
189
+
190
+ This project rocks and uses MIT-LICENSE. (http://www.opensource.org/licenses/mit-license.php)
191
+
data/Rakefile ADDED
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'ActionSmser'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
24
+ load 'rails/tasks/engine.rake'
25
+
26
+
27
+ Bundler::GemHelper.install_tasks
28
+
29
+ require 'rake/testtask'
30
+
31
+ Rake::TestTask.new(:test) do |t|
32
+ t.libs << 'lib'
33
+ t.libs << 'test'
34
+ t.pattern = 'test/**/*_test.rb'
35
+ t.verbose = false
36
+ end
37
+
38
+
39
+ task :default => :test
@@ -0,0 +1,2 @@
1
+ // Place all the behaviors and hooks related to the matching controller here.
2
+ // All this logic will automatically be available in application.js.
@@ -0,0 +1,7 @@
1
+ /*
2
+ * This is a manifest file that'll automatically include all the stylesheets available in this directory
3
+ * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
4
+ * the top of the compiled file, but it's generally better to create a new file per style scope.
5
+ *= require_self
6
+ *= require_tree .
7
+ */
@@ -0,0 +1,4 @@
1
+ /*
2
+ Place all the styles related to the matching controller here.
3
+ They will automatically be included in application.css.
4
+ */
@@ -0,0 +1,56 @@
1
+ body { background-color: #fff; color: #333; }
2
+
3
+ body, p, ol, ul, td {
4
+ font-family: verdana, arial, helvetica, sans-serif;
5
+ font-size: 13px;
6
+ line-height: 18px;
7
+ }
8
+
9
+ pre {
10
+ background-color: #eee;
11
+ padding: 10px;
12
+ font-size: 11px;
13
+ }
14
+
15
+ a { color: #000; }
16
+ a:visited { color: #666; }
17
+ a:hover { color: #fff; background-color:#000; }
18
+
19
+ div.field, div.actions {
20
+ margin-bottom: 10px;
21
+ }
22
+
23
+ #notice {
24
+ color: green;
25
+ }
26
+
27
+ .field_with_errors {
28
+ padding: 2px;
29
+ background-color: red;
30
+ display: table;
31
+ }
32
+
33
+ #error_explanation {
34
+ width: 450px;
35
+ border: 2px solid red;
36
+ padding: 7px;
37
+ padding-bottom: 0;
38
+ margin-bottom: 20px;
39
+ background-color: #f0f0f0;
40
+ }
41
+
42
+ #error_explanation h2 {
43
+ text-align: left;
44
+ font-weight: bold;
45
+ padding: 5px 5px 5px 15px;
46
+ font-size: 12px;
47
+ margin: -7px;
48
+ margin-bottom: 0px;
49
+ background-color: #c00;
50
+ color: #fff;
51
+ }
52
+
53
+ #error_explanation ul li {
54
+ font-size: 12px;
55
+ list-style: square;
56
+ }
@@ -0,0 +1,4 @@
1
+ module ActionSmser
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,63 @@
1
+ module ActionSmser
2
+ class DeliveryReportsController < ApplicationController
3
+
4
+ def gateway_commit
5
+
6
+ updated_count = 0
7
+
8
+
9
+ if !ActionSmser.delivery_options[:gateway_commit].blank? &&
10
+ !ActionSmser.delivery_options[:gateway_commit][params['gateway']].blank?
11
+
12
+ ActionSmser::Logger.info("Gateway_commit found parser for gateway: #{params['gateway']}")
13
+
14
+ dr_array = ActionSmser.delivery_options[:gateway_commit][params['gateway']].send(:process_delivery_report, params)
15
+
16
+ if !dr_array.blank?
17
+ dr_array.each do |dr_update|
18
+ msg_id = dr_update["msg_id"]
19
+ dr = ActionSmser::DeliveryReport.where(:msg_id => msg_id).first
20
+
21
+ if dr
22
+ dr_update.each_pair do |key, value|
23
+ dr.send("#{key}=", value) if dr.attribute_names.include?(key.to_s)
24
+ end
25
+
26
+
27
+ if dr.save
28
+ updated_count += 1
29
+ ActionSmser::Logger.info("Gateway_commit updated item with id: #{msg_id}, params: #{dr_update.inspect}")
30
+ else
31
+ ActionSmser::Logger.info("Gateway_commit problem updating item with id: #{msg_id}, params: #{dr_update.inspect}")
32
+ end
33
+ else
34
+ ActionSmser::Logger.info("Gateway_commit not found item with id: #{msg_id}, params: #{dr_update.inspect}")
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ if updated_count > 0
41
+ render :text => "Updated info for #{updated_count} items"
42
+ else
43
+ render :text => "Not saved"
44
+ end
45
+ end
46
+
47
+ before_filter :admin_access_only, :except => :gateway_commit
48
+
49
+ def index
50
+
51
+ end
52
+
53
+ def admin_access_only
54
+ if !ActionSmser.delivery_options[:admin_access].blank? && ActionSmser.delivery_options[:admin_access].send(:admin_access, self)
55
+ return true
56
+ else
57
+ render :text => "Forbidden, only for admins", :status => 403
58
+ return false
59
+ end
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,30 @@
1
+ module ActionSmser
2
+ class DeliveryReport < ActiveRecord::Base
3
+
4
+ def self.build_from_sms(sms, to, msg_id)
5
+ @delivery_report = self.new
6
+
7
+ [:from, :body, :sms_type].each do |var|
8
+ @delivery_report.send("#{var}=", sms.send(var))
9
+ end
10
+ @delivery_report.to = to
11
+ @delivery_report.msg_id = msg_id
12
+ @delivery_report.status = "LOCAL_SENT"
13
+ @delivery_report
14
+ end
15
+
16
+ def self.create_from_sms(sms, to, sms_id)
17
+ @delivery_report = self.build_from_sms(sms, to, sms_id)
18
+ @delivery_report.save
19
+ @delivery_report
20
+ end
21
+
22
+ def status=(stat, skip_log = false)
23
+ self[:status] = stat
24
+ self.status_updated_at = Time.now
25
+ self.log = "" if self.log.nil?
26
+ self.log += "#{Time.now.to_s(:db)}: #{stat}\n" if !skip_log
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,143 @@
1
+
2
+ <%
3
+ params['time_span'] ||= 'Last Month'
4
+
5
+ time_span = case params['time_span'].to_s.downcase
6
+ when 'previous week'
7
+ 2.weeks.ago..1.week.ago
8
+ when 'last month'
9
+ 1.month.ago..2.minutes.ago
10
+ else
11
+ 1.week.ago..2.minutes.ago
12
+ end
13
+ %>
14
+
15
+
16
+ <h1>Delivery Reports Summary for <%= params['time_span'] %></h1>
17
+
18
+
19
+ Show summary:
20
+ <%= link_to 'Last week', delivery_reports_path(:time_span => 'Last Week') %>,
21
+ <%= link_to 'Week before that', delivery_reports_path(:time_span => 'Previous Week') %> or
22
+ <%= link_to 'Last month', delivery_reports_path(:time_span => 'Last Month') %>.
23
+
24
+ <h2>Delivered Within (hour:min:sec)</h2>
25
+
26
+ <%
27
+ ar = ActionSmser::DeliveryReport.where(:created_at => time_span)
28
+ ar_delivered = ar.where(:status => 'delivered')
29
+ total = ar.count
30
+ total_delivered = ar_delivered.count
31
+ %>
32
+
33
+ <% if ActiveRecord::Base.connection.adapter_name.downcase.to_s.include?("mysql") %>
34
+
35
+ <table>
36
+ <tr>
37
+ <th>DeliveryTime</th>
38
+ <th>Sms</th>
39
+ <th>% of sent sms</th>
40
+ <th>% of delivered sms</th>
41
+ </tr>
42
+
43
+ <% ["00:00:30", "00:01:00", "00:02:00", "00:10:00", "00:30:00", "00:90:00", "24:00:00", "48:00:00"].each do |time| %>
44
+ <% dev_count = ar_delivered.where('TIMEDIFF(status_updated_at, created_at) < ?', time).count %>
45
+ <tr>
46
+ <td><%= time %></td>
47
+ <td><%= dev_count %></td>
48
+ <td><%= number_to_percentage(100.0 * dev_count / total, :precision => 2) %></td>
49
+ <td><%= number_to_percentage(100.0 * dev_count / total_delivered, :precision => 2) %></td>
50
+ </tr>
51
+ <% end %>
52
+
53
+ </table>
54
+
55
+ <% else %>
56
+ Delivery report uses timediff sql and has only been tested with MySql. Disabled with other adapters for now.
57
+ <% end %>
58
+
59
+ <h2>DeliveryReport Statuses</h2>
60
+ <ul>
61
+ <%
62
+ quest = ActionSmser::DeliveryReport.where(:created_at => time_span)
63
+ total = quest.count
64
+ grouped_by_status = quest.group(:status).count.each do |key, val| %>
65
+
66
+ <li>
67
+ <strong><%= key %></strong>:
68
+ &nbsp;&nbsp;
69
+ <%= val %> sms
70
+ &nbsp;&nbsp;
71
+ (<strong><%= number_to_percentage(100.0 * val / total, :precision => 2) %></strong>)
72
+ </li>
73
+ <% end %>
74
+ </ul>
75
+
76
+ <h2>By Sms Type</h2>
77
+ <table>
78
+ <tr>
79
+ <th>SmsType</th>
80
+ <th>Count</th>
81
+ <th>% of total</th>
82
+ <th>Delivered count</th>
83
+ <th>Delivery %</th>
84
+ </tr>
85
+
86
+ <%
87
+ quest = ActionSmser::DeliveryReport.where(:created_at => time_span)
88
+ total = quest.count
89
+ grouped_by_status = quest.group(:sms_type).count.each do |key, val| %>
90
+ <tr>
91
+ <td><strong><%= key %></strong></td>
92
+ <td><%= val %></td>
93
+ <td><strong><%= number_to_percentage(100 * val / total, :precision => 2) %></strong></td>
94
+ <td><%= sms_count = ActionSmser::DeliveryReport.where(:created_at => time_span).where(:sms_type => key).where(:status => 'delivered').count %></td>
95
+ <td><strong><%= number_to_percentage(100 * sms_count / val, :precision => 2) %></strong></td>
96
+ </tr>
97
+ <% end %>
98
+ </table>
99
+
100
+
101
+ <hr>
102
+
103
+ <% items_within_page = 20 %>
104
+ <h1 id="delivery_reports_list"><%= items_within_page %> delivery_reports ordered by created_at</h1>
105
+
106
+ <%
107
+ offset = params[:offset].to_i
108
+ offset = 0 if offset < -0
109
+ %>
110
+
111
+ <p>
112
+ <strong>List offset: <%= offset %>.</strong>
113
+ Add
114
+ <%= link_to("- #{items_within_page}", delivery_reports_path(:offset => offset-items_within_page, :anchor => 'delivery_reports_list')) unless offset == 0 %>
115
+ <%= link_to "+ #{items_within_page}", delivery_reports_path(:offset => offset+items_within_page, :anchor => 'delivery_reports_list') %>
116
+ to offset
117
+ </p>
118
+
119
+ <table style="margin-top: 20px;">
120
+ <tr>
121
+ <th>Created_at</th>
122
+ <th>Msg id</th>
123
+ <th>Status</th>
124
+ <th>Status updated at</th>
125
+ <th>Recipient</th>
126
+ <th>Sender</th>
127
+ <th>Text body</th>
128
+ </tr>
129
+
130
+ <% ActionSmser::DeliveryReport.order('created_at DESC').offset(offset).first(items_within_page).each do |delivery_report| %>
131
+ <tr>
132
+ <td style="white-space: nowrap;"><%= delivery_report.created_at %></td>
133
+ <td><%= delivery_report.msg_id %></td>
134
+ <td><%= delivery_report.status %></td>
135
+ <td style="white-space: nowrap;"><%= delivery_report.status_updated_at %></td>
136
+ <td><%= delivery_report.to %></td>
137
+ <td><%= delivery_report.from %></td>
138
+ <td><%= delivery_report.body %></td>
139
+ </tr>
140
+ <% end %>
141
+ </table>
142
+
143
+