jirapong-apn_on_rails 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. data/.gitignore +17 -0
  2. data/.rspec +2 -0
  3. data/.specification +80 -0
  4. data/Gemfile +4 -0
  5. data/Gemfile.lock +47 -0
  6. data/LICENSE +21 -0
  7. data/README +179 -0
  8. data/README.textile +224 -0
  9. data/Rakefile +35 -0
  10. data/apn_on_rails.gemspec +32 -0
  11. data/autotest/discover.rb +1 -0
  12. data/generators/apn_migrations_generator.rb +33 -0
  13. data/generators/templates/apn_migrations/001_create_apn_devices.rb +13 -0
  14. data/generators/templates/apn_migrations/002_create_apn_notifications.rb +23 -0
  15. data/generators/templates/apn_migrations/003_alter_apn_devices.rb +23 -0
  16. data/generators/templates/apn_migrations/004_create_apn_apps.rb +25 -0
  17. data/generators/templates/apn_migrations/005_create_groups.rb +23 -0
  18. data/generators/templates/apn_migrations/006_alter_apn_groups.rb +11 -0
  19. data/generators/templates/apn_migrations/007_create_device_groups.rb +27 -0
  20. data/generators/templates/apn_migrations/008_create_apn_group_notifications.rb +23 -0
  21. data/generators/templates/apn_migrations/009_create_pull_notifications.rb +16 -0
  22. data/generators/templates/apn_migrations/010_alter_apn_notifications.rb +21 -0
  23. data/generators/templates/apn_migrations/011_make_device_token_index_nonunique.rb +11 -0
  24. data/generators/templates/apn_migrations/012_add_launch_notification_to_apn_pull_notifications.rb +9 -0
  25. data/lib/apn_on_rails.rb +4 -0
  26. data/lib/apn_on_rails/apn_on_rails.rb +81 -0
  27. data/lib/apn_on_rails/app/models/apn/app.rb +151 -0
  28. data/lib/apn_on_rails/app/models/apn/base.rb +11 -0
  29. data/lib/apn_on_rails/app/models/apn/device.rb +49 -0
  30. data/lib/apn_on_rails/app/models/apn/device_grouping.rb +16 -0
  31. data/lib/apn_on_rails/app/models/apn/group.rb +12 -0
  32. data/lib/apn_on_rails/app/models/apn/group_notification.rb +79 -0
  33. data/lib/apn_on_rails/app/models/apn/notification.rb +93 -0
  34. data/lib/apn_on_rails/app/models/apn/pull_notification.rb +28 -0
  35. data/lib/apn_on_rails/libs/connection.rb +70 -0
  36. data/lib/apn_on_rails/libs/feedback.rb +39 -0
  37. data/lib/apn_on_rails/rails/railtie.rb +15 -0
  38. data/lib/apn_on_rails/tasks/apn.rake +30 -0
  39. data/lib/apn_on_rails/tasks/db.rake +23 -0
  40. data/lib/apn_on_rails/version.rb +3 -0
  41. data/lib/apn_on_rails_tasks.rb +3 -0
  42. data/lib/generators/apn_on_rails/install/USAGE +8 -0
  43. data/lib/generators/apn_on_rails/install/install_generator.rb +38 -0
  44. data/lib/generators/apn_on_rails/install/templates/001_create_apn_devices.rb +13 -0
  45. data/lib/generators/apn_on_rails/install/templates/002_create_apn_notifications.rb +23 -0
  46. data/lib/generators/apn_on_rails/install/templates/003_alter_apn_devices.rb +25 -0
  47. data/lib/generators/apn_on_rails/install/templates/004_create_apn_apps.rb +18 -0
  48. data/lib/generators/apn_on_rails/install/templates/005_create_groups.rb +23 -0
  49. data/lib/generators/apn_on_rails/install/templates/006_alter_apn_groups.rb +11 -0
  50. data/lib/generators/apn_on_rails/install/templates/007_create_device_groups.rb +27 -0
  51. data/lib/generators/apn_on_rails/install/templates/008_create_apn_group_notifications.rb +23 -0
  52. data/lib/generators/apn_on_rails/install/templates/009_create_pull_notifications.rb +16 -0
  53. data/lib/generators/apn_on_rails/install/templates/010_alter_apn_notifications.rb +21 -0
  54. data/lib/generators/apn_on_rails/install/templates/011_make_device_token_index_nonunique.rb +11 -0
  55. data/lib/generators/apn_on_rails/install/templates/012_add_launch_notification_to_apn_pull_notifications.rb +9 -0
  56. data/spec/active_record/setup_ar.rb +20 -0
  57. data/spec/apn_on_rails/app/models/apn/app_spec.rb +230 -0
  58. data/spec/apn_on_rails/app/models/apn/device_spec.rb +61 -0
  59. data/spec/apn_on_rails/app/models/apn/group_notification_spec.rb +66 -0
  60. data/spec/apn_on_rails/app/models/apn/notification_spec.rb +71 -0
  61. data/spec/apn_on_rails/app/models/apn/pull_notification_spec.rb +100 -0
  62. data/spec/apn_on_rails/libs/connection_spec.rb +40 -0
  63. data/spec/apn_on_rails/libs/feedback_spec.rb +43 -0
  64. data/spec/extensions/string.rb +10 -0
  65. data/spec/factories/app_factory.rb +27 -0
  66. data/spec/factories/device_factory.rb +29 -0
  67. data/spec/factories/device_grouping_factory.rb +22 -0
  68. data/spec/factories/group_factory.rb +27 -0
  69. data/spec/factories/group_notification_factory.rb +22 -0
  70. data/spec/factories/notification_factory.rb +22 -0
  71. data/spec/factories/pull_notification_factory.rb +22 -0
  72. data/spec/fixtures/hexa.bin +1 -0
  73. data/spec/fixtures/message_for_sending.bin +0 -0
  74. data/spec/rails_root/config/apple_push_notification_development.pem +19 -0
  75. data/spec/spec_helper.rb +64 -0
  76. metadata +242 -0
@@ -0,0 +1,100 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', '..', '..', 'spec_helper.rb')
2
+
3
+ describe APN::PullNotification do
4
+
5
+ describe 'latest_since_when_already_seen_latest' do
6
+
7
+ it 'should return nothing because since date is after the latest pull notification' do
8
+ app = APN::App.first
9
+ noty1 = PullNotificationFactory.create({:app_id => app.id})
10
+ noty1.created_at = Time.now - 1.week
11
+ noty1.save
12
+ APN::PullNotification.latest_since(app.id,Time.now).should == nil
13
+ end
14
+
15
+ end
16
+
17
+ describe 'latest_since_when_have_not_seen_latest' do
18
+
19
+ it 'should return the most recent pull notification because it has not yet been seen' do
20
+ app = APN::App.first
21
+ noty1 = PullNotificationFactory.create({:app_id => app.id})
22
+ noty1.created_at = Time.now + 1.week
23
+ noty1.save
24
+ latest = APN::PullNotification.latest_since(app.id,Time.now - 1.week)
25
+ puts "latest is #{latest}"
26
+ latest.should == noty1
27
+ end
28
+
29
+ end
30
+
31
+ describe 'latest_since_with_no_date_when_there_is_no_launch_notification' do
32
+ it 'should return the most recent pull notification because no date is given' do
33
+ app = APN::App.first
34
+ noty1 = APN::PullNotification.find(:first, :order => "created_at DESC")
35
+ APN::PullNotification.latest_since(app.id).should == noty1
36
+ end
37
+ end
38
+
39
+ describe 'latest_since_with_no_date_when_there_is_a_launch_notification' do
40
+ it 'should return the launch notification even though there is a more recent notification' do
41
+ app = APN::App.first
42
+ noty_launch = PullNotificationFactory.create({:app_id => app.id, :launch_notification => true})
43
+ noty_launch.created_at = Time.now - 1.week
44
+ noty_launch.save
45
+ noty_nonlaunch = PullNotificationFactory.create({:app_id => app.id})
46
+ APN::PullNotification.latest_since(app.id).should == noty_launch
47
+ end
48
+ end
49
+
50
+ describe 'older_non_launch_noty_with_newer_launch_noty' do
51
+ it 'should return the older non launch notification even though a newer launch notification exists' do
52
+ APN::PullNotification.all.each { |n| n.destroy }
53
+ app = APN::App.first
54
+ noty_launch = PullNotificationFactory.create({:app_id => app.id, :launch_notification => true})
55
+ puts "noty_launch id is #{noty_launch.id}"
56
+ noty_nonlaunch = PullNotificationFactory.create({:app_id => app.id})
57
+ noty_nonlaunch.created_at = Time.now - 1.week
58
+ noty_nonlaunch.save
59
+ puts "noty_nonlaunch id is #{noty_nonlaunch.id}"
60
+ APN::PullNotification.latest_since(app.id, Time.now - 2.weeks).should == noty_nonlaunch
61
+ end
62
+ end
63
+
64
+ describe 'all_since_date_with_date_given' do
65
+ it 'should return all the non-launch notifications after the given date but not the ones before it' do
66
+ APN::PullNotification.all.each { |n| n.destroy }
67
+ app = APN::App.first
68
+ noty_launch = PullNotificationFactory.create({:app_id => app.id, :launch_notification => true})
69
+ noty_launch.created_at = Time.now - 2.weeks
70
+ noty_launch.save
71
+ old_noty = PullNotificationFactory.create({:app_id => app.id})
72
+ old_noty.created_at = Time.now - 2.weeks
73
+ old_noty.save
74
+ new_noty_one = PullNotificationFactory.create({:app_id => app.id})
75
+ new_noty_one.created_at = Time.now - 1.day
76
+ new_noty_one.save
77
+ new_noty_two = PullNotificationFactory.create({:app_id => app.id})
78
+ APN::PullNotification.all_since(app.id, Time.now - 1.week).should == [new_noty_two,new_noty_one]
79
+ end
80
+ end
81
+
82
+ describe 'all_since_with_no_since_date_given' do
83
+ it 'should return all of the non-launch notifications' do
84
+ APN::PullNotification.all.each { |n| n.destroy }
85
+ app = APN::App.first
86
+ noty_launch = PullNotificationFactory.create({:app_id => app.id, :launch_notification => true})
87
+ noty_launch.created_at = Time.now - 2.weeks
88
+ noty_launch.save
89
+ old_noty = PullNotificationFactory.create({:app_id => app.id})
90
+ old_noty.created_at = Time.now - 2.weeks
91
+ old_noty.save
92
+ new_noty_one = PullNotificationFactory.create({:app_id => app.id})
93
+ new_noty_one.created_at = Time.now - 1.day
94
+ new_noty_one.save
95
+ new_noty_two = PullNotificationFactory.create({:app_id => app.id})
96
+ APN::PullNotification.all_since(app.id, Time.now - 3.weeks).should == [new_noty_two,new_noty_one,old_noty]
97
+ end
98
+ end
99
+
100
+ end
@@ -0,0 +1,40 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe APN::Connection do
4
+
5
+ describe 'open_for_delivery' do
6
+
7
+ it 'should create a connection to Apple, yield it, and then close' do
8
+ rsa_mock = mock('rsa_mock')
9
+ OpenSSL::PKey::RSA.should_receive(:new).and_return(rsa_mock)
10
+
11
+ cert_mock = mock('cert_mock')
12
+ OpenSSL::X509::Certificate.should_receive(:new).and_return(cert_mock)
13
+
14
+ ctx_mock = mock('ctx_mock')
15
+ ctx_mock.should_receive(:key=).with(rsa_mock)
16
+ ctx_mock.should_receive(:cert=).with(cert_mock)
17
+ OpenSSL::SSL::SSLContext.should_receive(:new).and_return(ctx_mock)
18
+
19
+ tcp_mock = mock('tcp_mock')
20
+ tcp_mock.should_receive(:close)
21
+ TCPSocket.should_receive(:new).with('gateway.sandbox.push.apple.com', 2195).and_return(tcp_mock)
22
+
23
+ ssl_mock = mock('ssl_mock')
24
+ ssl_mock.should_receive(:sync=).with(true)
25
+ ssl_mock.should_receive(:connect)
26
+ ssl_mock.should_receive(:write).with('message-0')
27
+ ssl_mock.should_receive(:write).with('message-1')
28
+ ssl_mock.should_receive(:close)
29
+ OpenSSL::SSL::SSLSocket.should_receive(:new).with(tcp_mock, ctx_mock).and_return(ssl_mock)
30
+
31
+ APN::Connection.open_for_delivery do |conn, sock|
32
+ conn.write('message-0')
33
+ conn.write('message-1')
34
+ end
35
+
36
+ end
37
+
38
+ end
39
+
40
+ end
@@ -0,0 +1,43 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe APN::Feedback do
4
+
5
+ describe 'devices' do
6
+
7
+ before(:each) do
8
+ @time = Time.now
9
+ @device = DeviceFactory.create
10
+ @cert = mock('cert_mock')
11
+
12
+ @data_mock = mock('data_mock')
13
+ @data_mock.should_receive(:unpack).with('N1n1H140').and_return([@time.to_i, 12388, @device.token.delete(' ')])
14
+
15
+ @ssl_mock = mock('ssl_mock')
16
+ @ssl_mock.should_receive(:read).with(38).twice.and_return(@data_mock, nil)
17
+ @sock_mock = mock('sock_mock')
18
+ end
19
+
20
+ it 'should an Array of devices that need to be processed' do
21
+ APN::Connection.should_receive(:open_for_feedback).and_yield(@ssl_mock, @sock_mock)
22
+
23
+ devices = APN::Feedback.devices(@cert)
24
+ devices.size.should == 1
25
+ r_device = devices.first
26
+ r_device.token.should == @device.token
27
+ r_device.feedback_at.to_s.should == @time.to_s
28
+ end
29
+
30
+ it 'should yield up each device' do
31
+ APN::Connection.should_receive(:open_for_feedback).and_yield(@ssl_mock, @sock_mock)
32
+ lambda {
33
+ APN::Feedback.devices(@cert) do |r_device|
34
+ r_device.token.should == @device.token
35
+ r_device.feedback_at.to_s.should == @time.to_s
36
+ raise BlockRan.new
37
+ end
38
+ }.should raise_error(BlockRan)
39
+ end
40
+
41
+ end
42
+
43
+ end
@@ -0,0 +1,10 @@
1
+ class String
2
+
3
+ def self.randomize(length = 10)
4
+ chars = ("A".."H").to_a + ("J".."N").to_a + ("P".."T").to_a + ("W".."Z").to_a + ("3".."9").to_a
5
+ newpass = ""
6
+ 1.upto(length) { |i| newpass << chars[rand(chars.size-1)] }
7
+ return newpass.upcase
8
+ end
9
+
10
+ end
@@ -0,0 +1,27 @@
1
+ module AppFactory
2
+
3
+ class << self
4
+
5
+ def new(options = {})
6
+ options = {:apn_dev_cert => AppFactory.random_cert,
7
+ :apn_prod_cert => AppFactory.random_cert}.merge(options)
8
+ return APN::App.new(options)
9
+ end
10
+
11
+ def create(options = {})
12
+ app = AppFactory.new(options)
13
+ app.save
14
+ return app
15
+ end
16
+
17
+ def random_cert
18
+ tok = []
19
+ tok << String.randomize(50)
20
+ tok.join('').downcase
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+
27
+ AppFactory.create
@@ -0,0 +1,29 @@
1
+ module DeviceFactory
2
+
3
+ class << self
4
+
5
+ def new(options = {})
6
+ app = APN::App.first
7
+ options = {:token => DeviceFactory.random_token, :app_id => app.id}.merge(options)
8
+ return APN::Device.new(options)
9
+ end
10
+
11
+ def create(options = {})
12
+ device = DeviceFactory.new(options)
13
+ device.save
14
+ return device
15
+ end
16
+
17
+ def random_token
18
+ tok = []
19
+ 8.times do
20
+ tok << String.randomize(8)
21
+ end
22
+ tok.join(' ').downcase
23
+ end
24
+
25
+ end
26
+
27
+ end
28
+
29
+ DeviceFactory.create
@@ -0,0 +1,22 @@
1
+ module DeviceGroupingFactory
2
+
3
+ class << self
4
+
5
+ def new(options = {})
6
+ device = APN::Device.first
7
+ group = APN::Group.first
8
+ options = {:device_id => device.id, :group_id => group.id}.merge(options)
9
+ return APN::DeviceGrouping.new(options)
10
+ end
11
+
12
+ def create(options = {})
13
+ device_grouping = DeviceGroupingFactory.new(options)
14
+ device_grouping.save
15
+ return device_grouping
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+
22
+ DeviceGroupingFactory.create
@@ -0,0 +1,27 @@
1
+ module GroupFactory
2
+
3
+ class << self
4
+
5
+ def new(options = {})
6
+ app = APN::App.first
7
+ options = {:app_id => app.id, :name => GroupFactory.random_name}.merge(options)
8
+ return APN::Group.new(options)
9
+ end
10
+
11
+ def create(options = {})
12
+ group = GroupFactory.new(options)
13
+ group.save
14
+ return group
15
+ end
16
+
17
+ def random_name
18
+ tok = []
19
+ tok << String.randomize(8)
20
+ tok.join(' ').downcase
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+
27
+ GroupFactory.create
@@ -0,0 +1,22 @@
1
+ module GroupNotificationFactory
2
+
3
+ class << self
4
+
5
+ def new(options = {})
6
+ group = APN::Group.first
7
+ options = {:group_id => group.id, :sound => 'my_sound.aiff',
8
+ :badge => 5, :alert => 'Hello!', :custom_properties => {'typ' => 1}}.merge(options)
9
+ return APN::GroupNotification.new(options)
10
+ end
11
+
12
+ def create(options = {})
13
+ notification = GroupNotificationFactory.new(options)
14
+ notification.save
15
+ return notification
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+
22
+ GroupNotificationFactory.create
@@ -0,0 +1,22 @@
1
+ module NotificationFactory
2
+
3
+ class << self
4
+
5
+ def new(options = {})
6
+ device = APN::Device.first
7
+ options = {:device_id => device.id, :sound => 'my_sound.aiff',
8
+ :badge => 5, :alert => 'Hello!', :custom_properties => {'typ' => 1}}.merge(options)
9
+ return APN::Notification.new(options)
10
+ end
11
+
12
+ def create(options = {})
13
+ notification = NotificationFactory.new(options)
14
+ notification.save
15
+ return notification
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+
22
+ NotificationFactory.create
@@ -0,0 +1,22 @@
1
+ module PullNotificationFactory
2
+
3
+ class << self
4
+
5
+ def new(options = {})
6
+ app = APN::App.first
7
+ options = {:app_id => app.id, :title => 'Pull Notification Title',
8
+ :content => 'blah blah blah', :link => 'http://www.prx.org', :launch_notification => false}.merge(options)
9
+ return APN::PullNotification.new(options)
10
+ end
11
+
12
+ def create(options = {})
13
+ noty = PullNotificationFactory.new(options)
14
+ noty.save
15
+ return noty
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+
22
+ PullNotificationFactory.create
@@ -0,0 +1 @@
1
+ P�&cmVS��`�0tϬɵ]�GC�r-E�;�
@@ -0,0 +1,19 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDDDCCAfSgAwIBAgIBATANBgkqhkiG9w0BAQUFADA8MQswCQYDVQQGEwJVUzEN
3
+ MAsGA1UECgwEaG9tZTERMA8GA1UECwwIbWFjYmF0ZXMxCzAJBgNVBAMMAkNBMB4X
4
+ DTA5MDIyMjE5MDUyOVoXDTEwMDIyMjE5MDUyOVowUzELMAkGA1UEBhMCVVMxDTAL
5
+ BgNVBAoMBGhvbWUxETAPBgNVBAsMCG1hY2JhdGVzMQswCQYDVQQLDAJDQTEVMBMG
6
+ A1UEAwwMaGVsbG8tc2VydmVyMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCr
7
+ LhTbcQc6hpYVeB8O94JzWnS41wZTaHReYe2mAxkIH9gF11Gm/Tejdfy7TboVsVtD
8
+ FZ+vrVYPFnnVZG2UNDUkfBvkbCBrFQ8glnAHGRYtDxdFjrLDxm0BOfC58wEtV2cM
9
+ hZhiLqjHFuSjHuAlAUshfCfWmKbEeDVtFSDxUMa6iQIDAQABo4GFMIGCMAwGA1Ud
10
+ EwEB/wQCMAAwMQYJYIZIAYb4QgENBCQWIlJ1YnkvT3BlblNTTCBHZW5lcmF0ZWQg
11
+ Q2VydGlmaWNhdGUwHQYDVR0OBBYEFEIBIEcdhFKPB+QILbsupdz3uD6YMAsGA1Ud
12
+ DwQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATANBgkqhkiG9w0BAQUFAAOCAQEA
13
+ dOKP/y/hsdnn2cbYpu2I6r8Lzql52XKa+jOaOT0TyPhmUAz0bFUgA53a202MDhbS
14
+ KDVhIkC88KTjyyRwVNnwsrS5JD/IOXIJw/vy9VX14aCymPkup0TQR6ZIicKrjcMS
15
+ yhmU5I0+fmsUN4PnayOuT/tJ0giy/x+1L/pgMicS47TvyNLB0vl34FplgmH6zlXv
16
+ nS/5phroEJm71DPyDNNzoohZo54YHpGmvEDqjLc6DB+Ihu6/sghmd5dlSPNqsubO
17
+ sBQeOyNuscbXo6MXI8uDYrZ/PqAtdzPXBjB7LXvVs69YT4KT7BaO3rqobgfJ0kNU
18
+ e7roqj04VUJGmU47qrMLBg==
19
+ -----END CERTIFICATE-----
@@ -0,0 +1,64 @@
1
+ require 'rubygems'
2
+ require 'rspec'
3
+ require 'action_view'
4
+
5
+ Dir.glob(File.join(File.dirname(__FILE__), 'extensions', '*.rb')).sort.each do |f|
6
+ require f
7
+ end
8
+
9
+ require File.join(File.dirname(__FILE__), 'active_record', 'setup_ar.rb')
10
+
11
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'apn_on_rails')
12
+
13
+ # Dir.glob(File.join(File.dirname(__FILE__), 'factories', '*.rb')).sort.each do |f|
14
+ # require f
15
+ # end
16
+
17
+ require File.join(File.dirname(__FILE__), 'factories', 'app_factory.rb')
18
+ require File.join(File.dirname(__FILE__), 'factories', 'device_factory.rb')
19
+ require File.join(File.dirname(__FILE__), 'factories', 'group_factory.rb')
20
+ require File.join(File.dirname(__FILE__), 'factories', 'device_grouping_factory.rb')
21
+ require File.join(File.dirname(__FILE__), 'factories', 'group_notification_factory.rb')
22
+ require File.join(File.dirname(__FILE__), 'factories', 'notification_factory.rb')
23
+ require File.join(File.dirname(__FILE__), 'factories', 'pull_notification_factory.rb')
24
+
25
+ configatron.apn.cert = File.expand_path(File.join(File.dirname(__FILE__), 'rails_root', 'config', 'apple_push_notification_development.pem'))
26
+
27
+ RSpec.configure do |config|
28
+
29
+ config.before(:all) do
30
+
31
+ end
32
+
33
+ config.after(:all) do
34
+
35
+ end
36
+
37
+ config.before(:each) do
38
+
39
+ end
40
+
41
+ config.after(:each) do
42
+
43
+ end
44
+
45
+ end
46
+
47
+ def fixture_path(*name)
48
+ return File.join(File.dirname(__FILE__), 'fixtures', *name)
49
+ end
50
+
51
+ def fixture_value(*name)
52
+ return File.read(fixture_path(*name))
53
+ end
54
+
55
+ def write_fixture(name, value)
56
+ File.open(fixture_path(*name), 'w') {|f| f.write(value)}
57
+ end
58
+
59
+ def apn_cert
60
+ File.read(File.join(File.dirname(__FILE__), 'rails_root', 'config', 'apple_push_notification_development.pem'))
61
+ end
62
+
63
+ class BlockRan < StandardError
64
+ end