apn_on_rails 0.3.1 → 0.4.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.
Files changed (48) hide show
  1. data/.gitignore +16 -0
  2. data/.rspec +2 -0
  3. data/.specification +80 -0
  4. data/Gemfile +18 -0
  5. data/Gemfile.lock +45 -0
  6. data/README +51 -9
  7. data/README.textile +198 -0
  8. data/Rakefile +49 -0
  9. data/VERSION +1 -0
  10. data/apn_on_rails.gemspec +110 -0
  11. data/generators/templates/apn_migrations/004_create_apn_apps.rb +18 -0
  12. data/generators/templates/apn_migrations/005_create_groups.rb +23 -0
  13. data/generators/templates/apn_migrations/006_alter_apn_groups.rb +11 -0
  14. data/generators/templates/apn_migrations/007_create_device_groups.rb +27 -0
  15. data/generators/templates/apn_migrations/008_create_apn_group_notifications.rb +23 -0
  16. data/generators/templates/apn_migrations/009_create_pull_notifications.rb +16 -0
  17. data/lib/apn_on_rails/apn_on_rails.rb +22 -3
  18. data/lib/apn_on_rails/app/models/apn/app.rb +115 -0
  19. data/lib/apn_on_rails/app/models/apn/device.rb +3 -1
  20. data/lib/apn_on_rails/app/models/apn/device_grouping.rb +16 -0
  21. data/lib/apn_on_rails/app/models/apn/group.rb +12 -0
  22. data/lib/apn_on_rails/app/models/apn/group_notification.rb +79 -0
  23. data/lib/apn_on_rails/app/models/apn/notification.rb +6 -30
  24. data/lib/apn_on_rails/app/models/apn/pull_notification.rb +15 -0
  25. data/lib/apn_on_rails/libs/connection.rb +2 -1
  26. data/lib/apn_on_rails/libs/feedback.rb +6 -18
  27. data/lib/apn_on_rails/tasks/apn.rake +13 -4
  28. data/spec/active_record/setup_ar.rb +19 -0
  29. data/spec/apn_on_rails/app/models/apn/app_spec.rb +178 -0
  30. data/spec/apn_on_rails/app/models/apn/device_spec.rb +60 -0
  31. data/spec/apn_on_rails/app/models/apn/group_notification_spec.rb +66 -0
  32. data/spec/apn_on_rails/app/models/apn/notification_spec.rb +71 -0
  33. data/spec/apn_on_rails/app/models/apn/pull_notification_spec.rb +37 -0
  34. data/spec/apn_on_rails/libs/connection_spec.rb +40 -0
  35. data/spec/apn_on_rails/libs/feedback_spec.rb +45 -0
  36. data/spec/extensions/string.rb +10 -0
  37. data/spec/factories/app_factory.rb +27 -0
  38. data/spec/factories/device_factory.rb +29 -0
  39. data/spec/factories/device_grouping_factory.rb +22 -0
  40. data/spec/factories/group_factory.rb +27 -0
  41. data/spec/factories/group_notification_factory.rb +22 -0
  42. data/spec/factories/notification_factory.rb +22 -0
  43. data/spec/factories/pull_notification_factory.rb +22 -0
  44. data/spec/fixtures/hexa.bin +1 -0
  45. data/spec/fixtures/message_for_sending.bin +0 -0
  46. data/spec/rails_root/config/apple_push_notification_development.pem +19 -0
  47. data/spec/spec_helper.rb +55 -0
  48. metadata +214 -24
@@ -0,0 +1,49 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "apn_on_rails"
16
+ gem.summary = %Q{Apple Push Notifications on Rails}
17
+
18
+ gem.description = %Q{APN on Rails is a Ruby on Rails gem that allows you to
19
+ easily add Apple Push Notification (iPhone) support to your Rails application.
20
+ }
21
+
22
+ gem.email = "tech-team@prx.org"
23
+ gem.homepage = "http://github.com/PRX/apn_on_rails"
24
+ gem.authors = ["markbates", "Rebecca Nesson"]
25
+ end
26
+ #Jeweler::RubygemsDotOrgsTasks.new
27
+
28
+ require 'rspec/core'
29
+ require 'rspec/core/rake_task'
30
+ RSpec::Core::RakeTask.new(:spec) do |spec|
31
+ spec.pattern = FileList['spec/**/*_spec.rb']
32
+ end
33
+
34
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
35
+ spec.pattern = 'spec/**/*_spec.rb'
36
+ spec.rcov = true
37
+ end
38
+
39
+ task :default => :spec
40
+
41
+ require 'rake/rdoctask'
42
+ Rake::RDocTask.new do |rdoc|
43
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
44
+
45
+ rdoc.rdoc_dir = 'rdoc'
46
+ rdoc.title = "apn #{version}"
47
+ rdoc.rdoc_files.include('README*')
48
+ rdoc.rdoc_files.include('lib/**/*.rb')
49
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.4.0
@@ -0,0 +1,110 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{apn_on_rails}
8
+ s.version = "0.3.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["markbates", "Rebecca Nesson"]
12
+ s.date = %q{2010-10-06}
13
+ s.description = %q{APN on Rails is a Ruby on Rails gem that allows you to
14
+ easily add Apple Push Notification (iPhone) support to your Rails application.
15
+ }
16
+ s.email = %q{tech-team@prx.org}
17
+ s.extra_rdoc_files = [
18
+ "LICENSE",
19
+ "README",
20
+ "README.textile"
21
+ ]
22
+ s.files = [
23
+ ".gitignore",
24
+ ".rspec",
25
+ "Gemfile",
26
+ "Gemfile.lock",
27
+ "LICENSE",
28
+ "README",
29
+ "README.textile",
30
+ "Rakefile",
31
+ "VERSION",
32
+ "apn_on_rails.gemspec",
33
+ "generators/apn_migrations_generator.rb",
34
+ "generators/templates/apn_migrations/001_create_apn_devices.rb",
35
+ "generators/templates/apn_migrations/002_create_apn_notifications.rb",
36
+ "generators/templates/apn_migrations/003_alter_apn_devices.rb",
37
+ "lib/apn_on_rails.rb",
38
+ "lib/apn_on_rails/apn_on_rails.rb",
39
+ "lib/apn_on_rails/app/models/apn/base.rb",
40
+ "lib/apn_on_rails/app/models/apn/device.rb",
41
+ "lib/apn_on_rails/app/models/apn/notification.rb",
42
+ "lib/apn_on_rails/libs/connection.rb",
43
+ "lib/apn_on_rails/libs/feedback.rb",
44
+ "lib/apn_on_rails/tasks/apn.rake",
45
+ "lib/apn_on_rails/tasks/db.rake",
46
+ "lib/apn_on_rails_tasks.rb",
47
+ "spec/active_record/setup_ar.rb",
48
+ "spec/apn_on_rails/app/models/apn/device_spec.rb",
49
+ "spec/apn_on_rails/app/models/apn/notification_spec.rb",
50
+ "spec/apn_on_rails/libs/connection_spec.rb",
51
+ "spec/apn_on_rails/libs/feedback_spec.rb",
52
+ "spec/extensions/string.rb",
53
+ "spec/factories/device_factory.rb",
54
+ "spec/factories/notification_factory.rb",
55
+ "spec/fixtures/hexa.bin",
56
+ "spec/fixtures/message_for_sending.bin",
57
+ "spec/rails_root/config/apple_push_notification_development.pem",
58
+ "spec/spec_helper.rb"
59
+ ]
60
+ s.homepage = %q{http://github.com/PRX/apn_on_rails}
61
+ s.require_paths = ["lib"]
62
+ s.rubygems_version = %q{1.3.7}
63
+ s.summary = %q{Apple Push Notifications on Rails}
64
+ s.test_files = [
65
+ "spec/active_record/setup_ar.rb",
66
+ "spec/apn_on_rails/app/models/apn/device_spec.rb",
67
+ "spec/apn_on_rails/app/models/apn/notification_spec.rb",
68
+ "spec/apn_on_rails/libs/connection_spec.rb",
69
+ "spec/apn_on_rails/libs/feedback_spec.rb",
70
+ "spec/extensions/string.rb",
71
+ "spec/factories/device_factory.rb",
72
+ "spec/factories/notification_factory.rb",
73
+ "spec/spec_helper.rb"
74
+ ]
75
+
76
+ if s.respond_to? :specification_version then
77
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
78
+ s.specification_version = 3
79
+
80
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
81
+ s.add_runtime_dependency(%q<configatron>, [">= 0"])
82
+ s.add_development_dependency(%q<sqlite3-ruby>, [">= 0"])
83
+ s.add_development_dependency(%q<rspec>, [">= 2.0.0.beta.19"])
84
+ s.add_development_dependency(%q<bundler>, [">= 1.0.0.rc.5"])
85
+ s.add_development_dependency(%q<jeweler>, ["~> 1.5.0.pre2"])
86
+ s.add_development_dependency(%q<rcov>, [">= 0"])
87
+ s.add_development_dependency(%q<actionpack>, ["~> 2.3.8"])
88
+ s.add_development_dependency(%q<activerecord>, ["~> 2.3.8"])
89
+ else
90
+ s.add_dependency(%q<configatron>, [">= 0"])
91
+ s.add_dependency(%q<sqlite3-ruby>, [">= 0"])
92
+ s.add_dependency(%q<rspec>, [">= 2.0.0.beta.19"])
93
+ s.add_dependency(%q<bundler>, [">= 1.0.0.rc.5"])
94
+ s.add_dependency(%q<jeweler>, ["~> 1.5.0.pre2"])
95
+ s.add_dependency(%q<rcov>, [">= 0"])
96
+ s.add_dependency(%q<actionpack>, ["~> 2.3.8"])
97
+ s.add_dependency(%q<activerecord>, ["~> 2.3.8"])
98
+ end
99
+ else
100
+ s.add_dependency(%q<configatron>, [">= 0"])
101
+ s.add_dependency(%q<sqlite3-ruby>, [">= 0"])
102
+ s.add_dependency(%q<rspec>, [">= 2.0.0.beta.19"])
103
+ s.add_dependency(%q<bundler>, [">= 1.0.0.rc.5"])
104
+ s.add_dependency(%q<jeweler>, ["~> 1.5.0.pre2"])
105
+ s.add_dependency(%q<rcov>, [">= 0"])
106
+ s.add_dependency(%q<actionpack>, ["~> 2.3.8"])
107
+ s.add_dependency(%q<activerecord>, ["~> 2.3.8"])
108
+ end
109
+ end
110
+
@@ -0,0 +1,18 @@
1
+ class CreateApnApps < ActiveRecord::Migration # :nodoc:
2
+ def self.up
3
+ create_table :apn_apps do |t|
4
+ t.text :apn_dev_cert
5
+ t.text :apn_prod_cert
6
+
7
+ t.timestamps
8
+ end
9
+
10
+ add_column :apn_devices, :app_id, :integer
11
+
12
+ end
13
+
14
+ def self.down
15
+ drop_table :apps
16
+ remove_column :apn_devices, :app_id
17
+ end
18
+ end
@@ -0,0 +1,23 @@
1
+ class CreateGroups < ActiveRecord::Migration # :nodoc:
2
+ def self.up
3
+ create_table :apn_groups do |t|
4
+ t.column :name, :string
5
+
6
+ t.timestamps
7
+ end
8
+
9
+ create_table :apn_devices_apn_groups, :id => false do |t|
10
+ t.column :group_id, :integer
11
+ t.column :device_id, :integer
12
+ end
13
+
14
+ add_index :apn_devices_apn_groups, [:group_id, :device_id]
15
+ add_index :apn_devices_apn_groups, :device_id
16
+ add_index :apn_devices_apn_groups, :group_id
17
+ end
18
+
19
+ def self.down
20
+ drop_table :apn_groups
21
+ drop_table :apn_devices_apn_groups
22
+ end
23
+ end
@@ -0,0 +1,11 @@
1
+ class AlterApnGroups < ActiveRecord::Migration # :nodoc:
2
+
3
+ def self.up
4
+ add_column :apn_groups, :app_id, :integer
5
+ end
6
+
7
+ def self.down
8
+ remove_column :apn_groups, :app_id
9
+ end
10
+
11
+ end
@@ -0,0 +1,27 @@
1
+ class CreateDeviceGroups < ActiveRecord::Migration # :nodoc:
2
+ def self.up
3
+ drop_table :apn_devices_apn_groups
4
+
5
+ create_table :apn_device_groupings do |t|
6
+ t.column :group_id, :integer
7
+ t.column :device_id, :integer
8
+ end
9
+
10
+ add_index :apn_device_groupings, [:group_id, :device_id]
11
+ add_index :apn_device_groupings, :device_id
12
+ add_index :apn_device_groupings, :group_id
13
+ end
14
+
15
+ def self.down
16
+ drop_table :apn_device_groupings
17
+
18
+ create_table :apn_devices_apn_groups, :id => false do |t|
19
+ t.column :group_id, :integer
20
+ t.column :device_id, :integer
21
+ end
22
+
23
+ add_index :apn_devices_apn_groups, [:group_id, :device_id]
24
+ add_index :apn_devices_apn_groups, :device_id
25
+ add_index :apn_devices_apn_groups, :group_id
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+ class CreateApnGroupNotifications < ActiveRecord::Migration # :nodoc:
2
+
3
+ def self.up
4
+
5
+ create_table :apn_group_notifications do |t|
6
+ t.integer :group_id, :null => false
7
+ t.string :device_language, :size => 5 # if you don't want to send localized strings
8
+ t.string :sound
9
+ t.string :alert, :size => 150
10
+ t.integer :badge
11
+ t.text :custom_properties
12
+ t.datetime :sent_at
13
+ t.timestamps
14
+ end
15
+
16
+ add_index :apn_group_notifications, :group_id
17
+ end
18
+
19
+ def self.down
20
+ drop_table :apn_group_notifications
21
+ end
22
+
23
+ end
@@ -0,0 +1,16 @@
1
+ class CreatePullNotifications < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :apn_pull_notifications do |t|
4
+ t.integer :app_id
5
+ t.string :title
6
+ t.string :content
7
+ t.string :link
8
+
9
+ t.timestamps
10
+ end
11
+ end
12
+
13
+ def self.down
14
+ drop_table :apn_pull_notifications
15
+ end
16
+ end
@@ -45,10 +45,19 @@ module APN # :nodoc:
45
45
 
46
46
  end
47
47
 
48
+ class MissingCertificateError < StandardError
49
+ def initialize
50
+ super("This app has no certificate")
51
+ end
52
+ end
53
+
48
54
  end # Errors
49
55
 
50
56
  end # APN
51
57
 
58
+ base = File.join(File.dirname(__FILE__), 'app', 'models', 'apn', 'base.rb')
59
+ require base
60
+
52
61
  Dir.glob(File.join(File.dirname(__FILE__), 'app', 'models', 'apn', '*.rb')).sort.each do |f|
53
62
  require f
54
63
  end
@@ -57,6 +66,16 @@ end
57
66
  path = File.join(File.dirname(__FILE__), 'app', dir)
58
67
  $LOAD_PATH << path
59
68
  # puts "Adding #{path}"
60
- ActiveSupport::Dependencies.load_paths << path
61
- ActiveSupport::Dependencies.load_once_paths.delete(path)
62
- end
69
+ begin
70
+ if ActiveSupport::Dependencies.respond_to? :autoload_paths
71
+ ActiveSupport::Dependencies.autoload_paths << path
72
+ ActiveSupport::Dependencies.autoload_once_paths.delete(path)
73
+ else
74
+ ActiveSupport::Dependencies.load_paths << path
75
+ ActiveSupport::Dependencies.load_once_paths.delete(path)
76
+ end
77
+ rescue NameError
78
+ Dependencies.load_paths << path
79
+ Dependencies.load_once_paths.delete(path)
80
+ end
81
+ end
@@ -0,0 +1,115 @@
1
+ class APN::App < APN::Base
2
+
3
+ has_many :groups, :class_name => 'APN::Group', :dependent => :destroy
4
+ has_many :devices, :class_name => 'APN::Device', :dependent => :destroy
5
+ has_many :notifications, :through => :devices, :dependent => :destroy
6
+ has_many :unsent_notifications, :through => :devices
7
+ has_many :group_notifications, :through => :groups
8
+ has_many :unsent_group_notifications, :through => :groups
9
+
10
+ def cert
11
+ (RAILS_ENV == 'production' ? apn_prod_cert : apn_dev_cert)
12
+ end
13
+
14
+ # Opens a connection to the Apple APN server and attempts to batch deliver
15
+ # an Array of group notifications.
16
+ #
17
+ #
18
+ # As each APN::GroupNotification is sent the <tt>sent_at</tt> column will be timestamped,
19
+ # so as to not be sent again.
20
+ #
21
+ def send_notifications
22
+ if self.cert.nil?
23
+ raise APN::Errors::MissingCertificateError.new
24
+ return
25
+ end
26
+ unless self.unsent_notifications.nil? || self.unsent_notifications.empty?
27
+ APN::Connection.open_for_delivery({:cert => self.cert}) do |conn, sock|
28
+ unsent_notifications.each do |noty|
29
+ conn.write(noty.message_for_sending)
30
+ noty.sent_at = Time.now
31
+ noty.save
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ def self.send_notifications
38
+ apps = APN::App.all
39
+ apps.each do |app|
40
+ app.send_notifications
41
+ end
42
+ end
43
+
44
+ def send_group_notifications
45
+ if self.cert.nil?
46
+ raise APN::Errors::MissingCertificateError.new
47
+ return
48
+ end
49
+ unless self.unsent_group_notifications.nil? || self.unsent_group_notifications.empty?
50
+ APN::Connection.open_for_delivery({:cert => self.cert}) do |conn, sock|
51
+ unsent_group_notifications.each do |gnoty|
52
+ puts "number of devices is #{gnoty.devices.size}"
53
+ gnoty.devices.each do |device|
54
+ conn.write(gnoty.message_for_sending(device))
55
+ end
56
+ gnoty.sent_at = Time.now
57
+ gnoty.save
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ def send_group_notification(gnoty)
64
+ if self.cert.nil?
65
+ raise APN::Errors::MissingCertificateError.new
66
+ return
67
+ end
68
+ unless gnoty.nil?
69
+ APN::Connection.open_for_delivery({:cert => self.cert}) do |conn, sock|
70
+ gnoty.devices.each do |device|
71
+ conn.write(gnoty.message_for_sending(device))
72
+ end
73
+ gnoty.sent_at = Time.now
74
+ gnoty.save
75
+ end
76
+ end
77
+ end
78
+
79
+ def self.send_group_notifications
80
+ apps = APN::App.all
81
+ apps.each do |app|
82
+ app.send_group_notifications
83
+ end
84
+ end
85
+
86
+ # Retrieves a list of APN::Device instnces from Apple using
87
+ # the <tt>devices</tt> method. It then checks to see if the
88
+ # <tt>last_registered_at</tt> date of each APN::Device is
89
+ # before the date that Apple says the device is no longer
90
+ # accepting notifications then the device is deleted. Otherwise
91
+ # it is assumed that the application has been re-installed
92
+ # and is available for notifications.
93
+ #
94
+ # This can be run from the following Rake task:
95
+ # $ rake apn:feedback:process
96
+ def process_devices
97
+ if self.cert.nil?
98
+ raise APN::Errors::MissingCertificateError.new
99
+ return
100
+ end
101
+ APN::Feedback.devices(self.cert).each do |device|
102
+ if device.last_registered_at < device.feedback_at
103
+ device.destroy
104
+ end
105
+ end
106
+ end # process_devices
107
+
108
+ def self.process_devices
109
+ apps = APN::App.all
110
+ apps.each do |app|
111
+ app.process_devices
112
+ end
113
+ end
114
+
115
+ end