Skip to content

Commit

Permalink
batching finds, fall back to default app, change last_registered_at b…
Browse files Browse the repository at this point in the history
…efore_save
  • Loading branch information
PRXci committed Nov 9, 2010
2 parents 0af1f4e + 8a118a1 commit 14897be
Show file tree
Hide file tree
Showing 13 changed files with 188 additions and 67 deletions.
3 changes: 2 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ gem 'configatron'
# Add dependencies to develop your gem here.
# Include everything needed to run rake, tests, features, etc.
group :development do
gem 'autotest'
gem 'sqlite3-ruby'
gem "rspec", ">= 2.0.0.beta.19"
gem "rspec", ">= 2.0.0"
gem "bundler", ">= 1.0.0.rc.5"
gem "jeweler", "~> 1.5.0.pre2"
gem "rcov", ">= 0"
Expand Down
22 changes: 12 additions & 10 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ GEM
activerecord (2.3.9)
activesupport (= 2.3.9)
activesupport (2.3.9)
autotest (4.3.2)
configatron (2.6.4)
yamler (>= 0.1.0)
diff-lcs (1.1.2)
Expand All @@ -18,16 +19,16 @@ GEM
rack (1.1.0)
rake (0.8.7)
rcov (0.9.9)
rspec (2.0.0.rc)
rspec-core (= 2.0.0.rc)
rspec-expectations (= 2.0.0.rc)
rspec-mocks (= 2.0.0.rc)
rspec-core (2.0.0.rc)
rspec-expectations (2.0.0.rc)
rspec (2.0.1)
rspec-core (~> 2.0.1)
rspec-expectations (~> 2.0.1)
rspec-mocks (~> 2.0.1)
rspec-core (2.0.1)
rspec-expectations (2.0.1)
diff-lcs (>= 1.1.2)
rspec-mocks (2.0.0.rc)
rspec-core (= 2.0.0.rc)
rspec-expectations (= 2.0.0.rc)
rspec-mocks (2.0.1)
rspec-core (~> 2.0.1)
rspec-expectations (~> 2.0.1)
sqlite3-ruby (1.3.1)
yamler (0.1.0)

Expand All @@ -37,9 +38,10 @@ PLATFORMS
DEPENDENCIES
actionpack (~> 2.3.8)
activerecord (~> 2.3.8)
autotest
bundler (>= 1.0.0.rc.5)
configatron
jeweler (~> 1.5.0.pre2)
rcov
rspec (>= 2.0.0.beta.19)
rspec (>= 2.0.0)
sqlite3-ruby
28 changes: 19 additions & 9 deletions README
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ APN on Rails is a Ruby on Rails gem that allows you to easily add Apple Push Not
support to your Rails application.

It supports:
* Multiple iPhone apps managed from the same Rails application
* Multiple iPhone apps managed from the same Rails application as well as a legacy default "app" with certs stored in config
* Individual notifications and group notifications
* Alerts, badges, sounds, and custom properties in notifications
* Pull notifications
Expand All @@ -14,21 +14,31 @@ It supports:
Multiple iPhone Apps: In previous versions of this gem a single Rails application was set up to
manage push notifications for a single iPhone app. In many cases it is useful to have a single Rails
app manage push notifications for multiple iPhone apps. With the addition of an APN::App model, this
is now possible. The certificates are now stored on instances of APN::APP and all devices are associated
with a particular app.
is now possible. The certificates are now stored on instances of APN::App and devices are intended to be associated
with a particular app. For compatibility with existing implementations it is still possible to create devices that
are not associated with an APN::App and to send individual notifications to them using the certs stored in the
config directory.

Individual and Group Notifications: Previous versions of this gem treated each notification individually
and did not provide a built-in way to send a broadcast notification to a group of devices. Group notifications
are now built into the gem. A group notification is associated with a group of devices and shares its
contents across the entire group of devices.
contents across the entire group of devices. (Group notifications are only available for groups of devices associated
with an APN::App)

Notification Content Areas: Notifications may contain alerts, badges, sounds, and custom properties.

Pull Notifications: This version of the gem supports an alternative notification method that relies
on pulls from client devices and does not interact with the Apple Push Notification servers. This feature
may be used entirely independently of the push notification features. Pull notifications may be
created for an app. A client app can query for the most recent pull notification available since a
given date to retrieve any notifications waiting for it.
given date to retrieve any notifications waiting for it.

==Version 0.4.1 Notes

* Backwards compatibility. 0.4.0 required a manual upgrade to associate existing and new devices with an APN::App model. This version allows continued use of devices that are associated with a default "app" that stores its certificates in the config directory. This ought to allow upgrade to this version without code changes.
* Batched finds. Finds on the APN::Device model that can return large numbers of records have been batched to limit memory impact.
* Custom properties migration. At a pre-0.4.0 version the custom_properties attribute was added to the migration template that created the notifications table. This introduced a potential problem for gem users who had previously run this migration. The custom_properties alteration to the apn_notifications table has been moved to its own migration and should work regardless of whether your apn_notifications table already has a custom_properties attribute.
* last_registered_at changed to work intuitively. The last_registered_at attribute of devices was being updated only on creation potentially causing a bug in which a device that opts out of APNs and then opts back in before apn_on_rails received feedback about it might miss a period of APNs that it should receive.

==Acknowledgements:

Expand Down Expand Up @@ -105,9 +115,9 @@ Now, to create the tables you need for APN on Rails, run the following task:

APN on Rails uses the Configatron gem, http://github.com/markbates/configatron/tree/master,
to configure itself. (With the change to multi-app support, the certifications are stored in the
database rather than in the config directory. These configurations remain for now.)
APN on Rails has the following default configurations that you change as you
see fit:
database rather than in the config directory, however, it is still possible to use the default "app" and the certificates
stored in the config directory. For this setup, the following configurations apply.)
APN on Rails has the following default configurations that you change as you see fit:

# development (delivery):
configatron.apn.passphrase # => ''
Expand Down Expand Up @@ -142,7 +152,7 @@ That way you ensure you have the latest version of the database tables needed.
==Example (assuming you have created an app and stored your keys on it):

$ ./script/console
>> app = APN::App.find(:first)
>> app = APN::App.create(:name => "My App", :apn_dev_cert => "PASTE YOUR DEV CERT HERE", :apn_prod_cert => "PASTE YOUR PROD CERT HERE")
>> device = APN::Device.create(:token => "XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX",:app_id => app.id)
>> notification = APN::Notification.new
>> notification.device = device
Expand Down
23 changes: 17 additions & 6 deletions README.textile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ APN on Rails is a Ruby on Rails gem that allows you to easily add Apple Push Not
support to your Rails application.

It supports:
* Multiple iPhone apps managed from the same Rails application
* Multiple iPhone apps managed from the same Rails application as well as a legacy default "app" with certs stored in config
* Individual notifications and group notifications
* Alerts, badges, sounds, and custom properties in notifications
* Pull notifications
Expand All @@ -14,13 +14,16 @@ h2. Feature Descriptions
Multiple iPhone Apps: In previous versions of this gem a single Rails application was set up to
manage push notifications for a single iPhone app. In many cases it is useful to have a single Rails
app manage push notifications for multiple iPhone apps. With the addition of an APN::App model, this
is now possible. The certificates are now stored on instances of APN::APP and all devices are associated
with a particular app.
is now possible. The certificates are now stored on instances of APN::App and all devices are intended to be associated
with a particular app. For compatibility with existing implementations it is still possible to create devices that
are not associated with an APN::App and to send individual notifications to them using the certs stored in the
config directory.

Individual and Group Notifications: Previous versions of this gem treated each notification individually
and did not provide a built-in way to send a broadcast notification to a group of devices. Group notifications
are now built into the gem. A group notification is associated with a group of devices and shares its
contents across the entire group of devices.
contents across the entire group of devices. (Group notifications are only available for groups of devices associated
with an APN::App)

Notification Content Areas: Notifications may contain alerts, badges, sounds, and custom properties.

Expand All @@ -30,6 +33,13 @@ may be used entirely independently of the push notification features. Pull noti
created for an app. A client app can query for the most recent pull notification available since a
given date to retrieve any notifications waiting for it.

h2. Version 0.4.1 Notes

* Backwards compatibility. 0.4.0 required a manual upgrade to associate existing and new devices with an APN::App model. This version allows continued use of devices that are associated with a default "app" that stores its certificates in the config directory. This ought to allow upgrade to this version without code changes.
* Batched finds. Finds on the APN::Device model that can return large numbers of records have been batched to limit memory impact.
* Custom properties migration. At a pre-0.4.0 version the custom_properties attribute was added to the migration template that created the notifications table. This introduced a potential problem for gem users who had previously run this migration. The custom_properties alteration to the apn_notifications table has been moved to its own migration and should work regardless of whether your apn_notifications table already has a custom_properties attribute.
* last_registered_at changed to work intuitively. The last_registered_at attribute of devices was being updated only on creation potentially causing a bug in which a device that opts out of APNs and then opts back in before apn_on_rails received feedback about it might miss a period of APNs that it should receive.

h2. Acknowledgements:

From Mark Bates:
Expand Down Expand Up @@ -121,7 +131,8 @@ Now, to create the tables you need for APN on Rails, run the following task:

APN on Rails uses the Configatron gem, http://github.com/markbates/configatron/tree/master,
to configure itself. (With the change to multi-app support, the certifications are stored in the
database rather than in the config directory. These configurations remain for now.)
database rather than in the config directory, however, it is still possible to use the default "app" and the certificates
stored in the config directory. For this setup, the following configurations apply.)
APN on Rails has the following default configurations that you change as you
see fit:

Expand Down Expand Up @@ -166,7 +177,7 @@ h2. Example (assuming you have created an app and stored your keys on it):

<pre><code>
$ ./script/console
>> app = APN::App.find(:first)
>> app = APN::App.create(:name => "My App", :apn_dev_cert => "PASTE YOUR DEV CERT HERE", :apn_prod_cert => "PASTE YOUR PROD CERT HERE")
>> device = APN::Device.create(:token => "XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX",:app_id => app.id)
>> notification = APN::Notification.new
>> notification.device = device
Expand Down
10 changes: 7 additions & 3 deletions apn_on_rails.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ easily add Apple Push Notification (iPhone) support to your Rails application.
"Rakefile",
"VERSION",
"apn_on_rails.gemspec",
"autotest/discover.rb",
"generators/apn_migrations_generator.rb",
"generators/templates/apn_migrations/001_create_apn_devices.rb",
"generators/templates/apn_migrations/002_create_apn_notifications.rb",
Expand Down Expand Up @@ -108,17 +109,19 @@ easily add Apple Push Notification (iPhone) support to your Rails application.

if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
s.add_runtime_dependency(%q<configatron>, [">= 0"])
s.add_development_dependency(%q<autotest>, [">= 0"])
s.add_development_dependency(%q<sqlite3-ruby>, [">= 0"])
s.add_development_dependency(%q<rspec>, [">= 2.0.0.beta.19"])
s.add_development_dependency(%q<rspec>, [">= 2.0.0"])
s.add_development_dependency(%q<bundler>, [">= 1.0.0.rc.5"])
s.add_development_dependency(%q<jeweler>, ["~> 1.5.0.pre2"])
s.add_development_dependency(%q<rcov>, [">= 0"])
s.add_development_dependency(%q<actionpack>, ["~> 2.3.8"])
s.add_development_dependency(%q<activerecord>, ["~> 2.3.8"])
else
s.add_dependency(%q<configatron>, [">= 0"])
s.add_dependency(%q<autotest>, [">= 0"])
s.add_dependency(%q<sqlite3-ruby>, [">= 0"])
s.add_dependency(%q<rspec>, [">= 2.0.0.beta.19"])
s.add_dependency(%q<rspec>, [">= 2.0.0"])
s.add_dependency(%q<bundler>, [">= 1.0.0.rc.5"])
s.add_dependency(%q<jeweler>, ["~> 1.5.0.pre2"])
s.add_dependency(%q<rcov>, [">= 0"])
Expand All @@ -127,8 +130,9 @@ easily add Apple Push Notification (iPhone) support to your Rails application.
end
else
s.add_dependency(%q<configatron>, [">= 0"])
s.add_dependency(%q<autotest>, [">= 0"])
s.add_dependency(%q<sqlite3-ruby>, [">= 0"])
s.add_dependency(%q<rspec>, [">= 2.0.0.beta.19"])
s.add_dependency(%q<rspec>, [">= 2.0.0"])
s.add_dependency(%q<bundler>, [">= 1.0.0.rc.5"])
s.add_dependency(%q<jeweler>, ["~> 1.5.0.pre2"])
s.add_dependency(%q<rcov>, [">= 0"])
Expand Down
1 change: 1 addition & 0 deletions autotest/discover.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Autotest.add_discovery { "rspec2" }
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
class AlterApnNotifications < ActiveRecord::Migration # :nodoc:

module APN # :nodoc:
class Notification < ActiveRecord::Base # :nodoc:
set_table_name 'apn_notifications'
end
end

def self.up
unless APN::Notification.column_names.include?("custom_properties")
add_column :apn_notifications, :custom_properties, :text
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class MakeDeviceTokenIndexNonunique < ActiveRecord::Migration
def self.up
remove_index :apn_devices, :column => :token
add_index :apn_devices, :token
end

def self.down
remove_index :apn_devices, :column => :token
add_index :apn_devices, :token, :unique => true
end
end
59 changes: 44 additions & 15 deletions lib/apn_on_rails/app/models/apn/app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,40 @@ def send_notifications
raise APN::Errors::MissingCertificateError.new
return
end
unless self.unsent_notifications.nil? || self.unsent_notifications.empty?
APN::Connection.open_for_delivery({:cert => self.cert}) do |conn, sock|
unsent_notifications.find_each do |noty|
conn.write(noty.message_for_sending)
noty.sent_at = Time.now
noty.save
end
end
end
APN::App.send_notifications_for_cert(self.cert, self.id)
end

def self.send_notifications
apps = APN::App.all
apps.each do |app|
app.send_notifications
end
global_cert = File.read(configatron.apn.cert)
if global_cert
send_notifications_for_cert(global_cert, nil)
end
end

def self.send_notifications_for_cert(the_cert, app_id)
# unless self.unsent_notifications.nil? || self.unsent_notifications.empty?
if (app_id == nil)
conditions = "app_id is null"
else
conditions = ["app_id = ?", app_id]
end
begin
APN::Connection.open_for_delivery({:cert => the_cert}) do |conn, sock|
APN::Device.find_each(:conditions => conditions) do |dev|
dev.unsent_notifications.each do |noty|
conn.write(noty.message_for_sending)
noty.sent_at = Time.now
noty.save
end
end
end
rescue
end
# end
end

def send_group_notifications
Expand All @@ -49,7 +67,6 @@ def send_group_notifications
unless self.unsent_group_notifications.nil? || self.unsent_group_notifications.empty?
APN::Connection.open_for_delivery({:cert => self.cert}) do |conn, sock|
unsent_group_notifications.each do |gnoty|
puts "number of devices is #{gnoty.devices.size}"
gnoty.devices.find_each do |device|
conn.write(gnoty.message_for_sending(device))
end
Expand Down Expand Up @@ -98,18 +115,30 @@ def process_devices
raise APN::Errors::MissingCertificateError.new
return
end
APN::Feedback.devices(self.cert).each do |device|
if device.last_registered_at < device.feedback_at
device.destroy
end
end
APN::App.process_devices_for_cert(self.cert)
end # process_devices

def self.process_devices
apps = APN::App.all
apps.each do |app|
app.process_devices
end
global_cert = File.read(configatron.apn.cert)
if global_cert
APN::App.process_devices_for_cert(global_cert)
end
end

def self.process_devices_for_cert(the_cert)
puts "in APN::App.process_devices_for_cert"
APN::Feedback.devices(the_cert).each do |device|
if device.last_registered_at < device.feedback_at
puts "device #{device.id} -> #{device.last_registered_at} < #{device.feedback_at}"
device.destroy
else
puts "device #{device.id} -> #{device.last_registered_at} not < #{device.feedback_at}"
end
end
end

end
5 changes: 2 additions & 3 deletions lib/apn_on_rails/app/models/apn/device.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class APN::Device < APN::Base
validates_uniqueness_of :token, :scope => :app_id
validates_format_of :token, :with => /^[a-z0-9]{8}\s[a-z0-9]{8}\s[a-z0-9]{8}\s[a-z0-9]{8}\s[a-z0-9]{8}\s[a-z0-9]{8}\s[a-z0-9]{8}\s[a-z0-9]{8}$/

before_save :set_last_registered_at
before_create :set_last_registered_at

# The <tt>feedback_at</tt> accessor is set when the
# device is marked as potentially disconnected from your
Expand All @@ -42,9 +42,8 @@ def to_hexa
[self.token.delete(' ')].pack('H*')
end

private
def set_last_registered_at
self.last_registered_at = Time.now if self.last_registered_at.nil?
self.last_registered_at = Time.now #if self.last_registered_at.nil?
end

end
Loading

0 comments on commit 14897be

Please sign in to comment.