apn_on_rails 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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