pling 0.0.1 → 0.1.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.
data/.gitignore CHANGED
@@ -1,5 +1,8 @@
1
1
  *.gem
2
2
  .bundle
3
3
  .rspec
4
+ .rvmrc
5
+ .yardoc/
6
+ doc/
4
7
  Gemfile.lock
5
8
  pkg/*
data/Gemfile CHANGED
@@ -3,4 +3,7 @@ source 'http://rubygems.org'
3
3
  # Specify your gem's dependencies in pling.gemspec
4
4
  gemspec
5
5
 
6
- gem 'guard'
6
+ gem 'guard'
7
+ gem 'guard-rspec'
8
+ gem 'growl_notify' if RUBY_PLATFORM =~ /darwin/
9
+ gem 'rdiscount', ">= 1.6.8", :platform => :ruby
data/Guardfile ADDED
@@ -0,0 +1,4 @@
1
+ guard 'rspec', :version => 2, :all_on_start => true, :all_after_pass => true do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
4
+ end
data/README.md CHANGED
@@ -1,10 +1,16 @@
1
- # Pling ![Travis build status of pling](http://travis-ci.org/flinc/pling.png)
1
+ # Pling [![Travis build status of pling](http://travis-ci.org/flinc/pling.png)](http://travis-ci.org/flinc/pling)
2
2
 
3
- Pling is a notification framework that supports multiple gateways.
3
+ Pling is a notification framework that supports multiple gateways. This gem implements the basic framework as well as a gateway to Google's Cloud to Device Messaging Service (C2DM) and Apple's Push Notification Service (APN).
4
4
 
5
5
 
6
6
  ## Requirements
7
7
 
8
+ This gem has two runtime dependencies
9
+
10
+ - faraday ~> 0.7
11
+ - json ~> 1.4
12
+
13
+ On JRuby it also requires the jruby-openssl gem.
8
14
 
9
15
 
10
16
  ## Install
@@ -15,27 +21,39 @@ Add this line to your `Gemfile`:
15
21
 
16
22
  ## Configuration
17
23
 
24
+ The configuration is pretty simple. Just add a configuration block like this to your code:
25
+
18
26
  Pling.configure do |config|
19
- config.gateways = [
20
- Pling::Gateways::C2DM.new(:options => 'here'),
21
- Pling::Gateways::IPhone.new(:options => 'here'),
22
- Pling::Gateways::Email.new(:options => 'here')
23
- ]
27
+ config.gateways.use Pling::Gateway::C2DM, :email => 'your-email@gmail.com', :password => 'your-password', :source => 'your-app-name'
28
+ config.gateways.use Pling::Gateway::APN, :certificate => '/path/to/certificate.pem'
29
+
30
+ # config.middleware.use Your::Custom::Middleware, :your => :custom, :configuration => true
31
+
32
+ # config.adapter = Your::Custom::Adapter.new
24
33
  end
25
34
 
26
35
  ## Usage
27
36
 
37
+ After configuring Pling you can send messages to devices by like this:
38
+
39
+ message = Pling::Message.new("Hello from pling!")
40
+ device = Pling::Device.new(:identifier => 'XXXXXXXXXX...XXXXXX', :type => :iphone)
41
+ device.deliver(message)
42
+
43
+ # ... or call Pling.delver
44
+ Pling.deliver(message, device)
45
+
28
46
  Pling has three core components:
29
47
 
30
- * A `Device` describes a concrete receiver such as a smartphone or an email address.
31
- * A `Message` wraps the content delivered to a device.
32
- * A `Gateway` handles the communication with the service provider used to deliver the message.
48
+ * A _device_ describes a concrete receiver such as a smartphone or an email address.
49
+ * A _message_ wraps the content delivered to a device.
50
+ * A _gateway_ handles the communication with the service provider used to deliver the message.
33
51
 
34
- To integrate Pling in your application you have to implement a `to_pling` method on each of your models to convert your data into Pling compatible objects.
52
+ You can easily integrate pling into your existing application by implementing `#to_pling_device` on your device models and `#to_pling_message` on your message models. Use these methods to either convert your models into `Pling::Device` and `Pling::Message` objects or return `self` and make sure your models implement the basic `Pling::Device` and `Pling::Message` interfaces.
35
53
 
36
54
  ### Devices
37
55
 
38
- Devices store an identifier and a type.
56
+ Devices store an identifier and a type.
39
57
 
40
58
  Example:
41
59
 
@@ -59,31 +77,74 @@ The Gateway delivers the message in the required format to the service provider.
59
77
 
60
78
  Currently there are these gateways available:
61
79
 
62
- * Android C2DM (not yet implemented)
63
- * iPhone Push (not yet implemented)
64
- * SMS via Mobilant (See `pling-mobilant` gem, not yet implemented)
65
- * E-Mail (See `pling-actionmailer` gem, not yet implemented)
80
+ * [Android C2DM](http://rdoc.info/github/flinc/pling/master/Pling/Gateway/C2DM)
81
+ * [Apple Push Notification](http://rdoc.info/github/flinc/pling/master/Pling/Gateway/APN)
82
+ * [SMS via Mobilant](https://github.com/flinc/pling-mobilant) (See `pling-mobilant` gem)
83
+ * [Email](https://github.com/flinc/pling-actionmailer) (See `pling-actionmailer` gem)
84
+
85
+ See the [API documentation](http://rdoc.info/github/flinc/pling) for details on the available gateways.
86
+
87
+
88
+ ### Middleware
89
+
90
+ Pling has support for middlewares. Currently pling itself does not provide any middlewares but you can easily implement your own. All you need is a class that responds to `#deliver(message, device)` which yields to call the next middleware on the stack. You might just want to subclass `Pling::Middleware::Base` to get a simple configuration management.
91
+
92
+ class Pling::Middleware::TimeFilter < Pling::Middleware::Base
93
+ def deliver(message, device)
94
+ yield(message, device) if configuration[:range].include? Time.now.hour
95
+ end
96
+
97
+ protected
98
+
99
+ def default_configuration
100
+ super.merge({
101
+ :range => 8..22
102
+ })
103
+ end
104
+ end
105
+
106
+ You can either add middlewares for all gateways or for specific gateways:
107
+
108
+ Pling.configure do |config|
109
+ config.gateways.use Pling::Gateway::APN, {
110
+ :certificate => '/path/to/certificate.pem',
111
+ :middlewares => [
112
+ [Pling::Middleware::TimeFilter, { :range => 9..17 }] # Don't deliver any messages to iOS devices between 9am and 5pm
113
+ ]
114
+ }
115
+
116
+ # Don't deliver any messages between 8am and 10pm
117
+ config.middleware.use Pling::Middleware::TimeFilter
118
+ end
119
+
120
+
121
+ ### Adapters
122
+
123
+ Pling supports different adapters. A adapter is in a way similar to a middleware but is responsible for dispatching a device and a message to a gateway.
124
+ The default adapter simply looks up the first matching gateway for the given device and calls its `#deliver(message, device)` method. Adapters are handy
125
+ when you want to add support for background queues. Have a look at [this example](https://gist.github.com/1308846) of an adapter for Resque.
126
+
66
127
 
67
128
  ## Build Status
68
129
 
69
- Pling is on [Travis](http://travis-ci.org/flinc/pling) running the specs on Ruby 1.8.7, Ruby 1.9.2 and Ruby Enterprise Edition.
130
+ Pling is on [Travis](http://travis-ci.org/flinc/pling) running the specs on Ruby 1.8.7, Ruby Enterprise Edition, Ruby 1.9.2, Ruby HEAD, JRuby, Rubinius and Rubinius 2.
70
131
 
71
132
 
72
133
  ## Known issues
73
134
 
74
- See https://github.com/flinc/pling/issues
135
+ See [the issue tracker on GitHub](https://github.com/flinc/pling/issues).
75
136
 
76
137
 
77
138
  ## Repository
78
139
 
79
- See https://github.com/flinc/pling and feel free to fork it!
140
+ See [the repository on GitHub](https://github.com/flinc/pling) and feel free to fork it!
80
141
 
81
142
 
82
143
  ## Contributors
83
144
 
84
- See a list of all contributors at https://github.com/flinc/pling/contributors. Thanks a lot everyone!
145
+ See a list of all contributors on [GitHub](https://github.com/flinc/pling/contributors). Thanks a lot everyone!
85
146
 
86
147
 
87
148
  ## Copyright
88
149
 
89
- Copyright (c) 2010-2011 flinc AG. See LICENSE for details.
150
+ Copyright (c) 2010-2011 [flinc AG](https://flinc.org/). See LICENSE for details.
data/Rakefile CHANGED
@@ -1,10 +1,15 @@
1
1
  require 'bundler/gem_tasks'
2
2
 
3
3
  require 'rspec/core/rake_task'
4
- require 'yard/rake/yardoc_task'
4
+ require 'yard'
5
5
 
6
6
  RSpec::Core::RakeTask.new(:spec)
7
7
 
8
8
  YARD::Rake::YardocTask.new(:doc)
9
9
 
10
10
  task :default => :spec
11
+
12
+ desc "Open an irb session"
13
+ task :console do
14
+ sh "irb -rubygems -I lib -r pling.rb"
15
+ end
data/lib/pling.rb CHANGED
@@ -1,5 +1,90 @@
1
1
  require "pling/version"
2
2
 
3
3
  module Pling
4
- # Your code goes here...
4
+
5
+ autoload :Device, 'pling/device'
6
+ autoload :Message, 'pling/message'
7
+ autoload :Gateway, 'pling/gateway'
8
+ autoload :Middleware, 'pling/middleware'
9
+ autoload :Adapter, 'pling/adapter'
10
+ autoload :Configurable, 'pling/configurable'
11
+ autoload :DelayedInitializer, 'pling/delayed_initializer'
12
+
13
+ @gateways = Pling::DelayedInitializer.new
14
+ @middlewares = Pling::DelayedInitializer.new
15
+ @adapter = Pling::Adapter::Base.new
16
+
17
+ class Error < StandardError; end
18
+ class AuthenticationFailed < Error; end
19
+ class DeliveryFailed < Error; end
20
+ class NoGatewayFound < Error; end
21
+
22
+ class << self
23
+ ##
24
+ # Stores the list of available gateway instances
25
+ #
26
+ # @return [Array] list of available gateways
27
+ attr_reader :gateways
28
+
29
+ def gateways=(gateways)
30
+ gateways.each { |gateway| @gateways << gateway }
31
+ end
32
+
33
+ ##
34
+ # Stores the list of avaiable middleware instances
35
+ #
36
+ # @return [Array] list of available middleware
37
+ attr_reader :middlewares
38
+
39
+ def middlewares=(middlewares)
40
+ middlewares.each { |middleware| @middlewares << middleware }
41
+ end
42
+
43
+ ##
44
+ # Stores the adapter
45
+ #
46
+ # @return [Pling::Adapter]
47
+ attr_accessor :adapter
48
+
49
+ ##
50
+ # Allows configuration of Pling by passing a config object to the given block
51
+ #
52
+ # @yield [config]
53
+ # @raise [ArgumentError] Raised when no block is given
54
+ def configure
55
+ raise ArgumentError, 'No block given for Pling.configure' unless block_given?
56
+ yield self
57
+ end
58
+
59
+ ##
60
+ # Delivers the given message to the given device using the given stack.
61
+ #
62
+ # @param message [#to_pling_message]
63
+ # @param device [#to_pling_device]
64
+ # @param stack [Array] The stack to use (Default: middlewares + [adapter])
65
+ def deliver(message, device, stack = nil)
66
+ message = Pling._convert(message, :message)
67
+ device = Pling._convert(device, :device)
68
+
69
+ stack ||= middlewares.initialize! + [adapter]
70
+
71
+ stack.shift.deliver(message, device) do |m, d|
72
+ deliver(m, d, stack)
73
+ end
74
+ end
75
+
76
+ ##
77
+ # [INTERNAL METHOD] Converts the given object to the given pling type
78
+ #
79
+ # @private
80
+ # @param object [Object] The object that needs to be converted
81
+ # @param type [Symbol, String] #to_pling_ method suffix
82
+ # @raise [ArgumentError] The object does not implement a #to_pling_ + type method
83
+ def _convert(object, type)
84
+ method = :"to_pling_#{type}"
85
+ raise ArgumentError, "Instances of #{object.class} do not implement ##{method}" unless object.respond_to?(method)
86
+ object && object.send(method)
87
+ end
88
+ end
89
+
5
90
  end
@@ -0,0 +1,5 @@
1
+ module Pling
2
+ module Adapter
3
+ autoload :Base, 'pling/adapter/base'
4
+ end
5
+ end
@@ -0,0 +1,17 @@
1
+ module Pling
2
+ module Adapter
3
+ class Base
4
+ include Pling::Configurable
5
+
6
+ def initialize(configuration = {})
7
+ setup_configuration(configuration)
8
+ end
9
+
10
+ def deliver(message, device)
11
+ gateway = Pling::Gateway.discover(device)
12
+ gateway.deliver(message, device)
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,29 @@
1
+ module Pling
2
+ module Configurable
3
+
4
+ protected
5
+
6
+ def configuration
7
+ @configuration ||= default_configuration
8
+ end
9
+
10
+ def default_configuration
11
+ {}
12
+ end
13
+
14
+ def setup_configuration(config = {}, opts = {})
15
+ config.each_pair do |key, value|
16
+ configuration[key.to_sym] = value
17
+ end
18
+
19
+ require_configuration(opts[:require] || [])
20
+ end
21
+
22
+ def require_configuration(keys, message = nil)
23
+ [keys].flatten.each do |key|
24
+ raise(ArgumentError, message || "Option :#{key} is missing") unless configuration.key?(key.to_sym)
25
+ end
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,13 @@
1
+ module Pling
2
+ class DelayedInitializer < Array
3
+ def use(*args)
4
+ self << args
5
+ end
6
+
7
+ def initialize!
8
+ map! do |item|
9
+ item.kind_of?(Array) ? item.shift.new(*item) : item
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,68 @@
1
+ module Pling
2
+ class Device
3
+
4
+ ##
5
+ # The device identifier
6
+ #
7
+ # @overload identifier
8
+ # @overload identifier=(identifier)
9
+ # @param [#to_s] identifier
10
+ attr_reader :identifier
11
+
12
+ def identifier=(identifier)
13
+ identifier &&= identifier.to_s
14
+ @identifier = identifier
15
+ end
16
+
17
+ ##
18
+ # The device type
19
+ #
20
+ # @overload type
21
+ # @overload type=(type)
22
+ # @param [#to_sym] type
23
+ attr_reader :type
24
+
25
+ def type=(type)
26
+ type &&= type.to_sym
27
+ @type = type
28
+ end
29
+
30
+ ##
31
+ # Creates a new Pling::Device instance with the given identifier and type
32
+ #
33
+ # @param [Hash] attributes
34
+ # @option attributes [#to_s] :identifier
35
+ # @option attributes [#to_sym] :type
36
+ def initialize(attributes = {})
37
+ attributes.each_pair do |key, value|
38
+ method = "#{key}="
39
+ send(method, value) if respond_to?(method)
40
+ end
41
+ end
42
+
43
+ ##
44
+ # A device is valid if it has a type and an identifier.
45
+ #
46
+ # @return [Boolean]
47
+ def valid?
48
+ !!(type && identifier)
49
+ end
50
+
51
+ ##
52
+ # Delivers the given message using an appropriate gateway.
53
+ #
54
+ # @param [#to_pling_message] message
55
+ def deliver(message)
56
+ Pling.deliver(message, self)
57
+ end
58
+
59
+ ##
60
+ # Returns the object itself as it is already a Pling::Device.
61
+ #
62
+ # @return [Pling::Device]
63
+ def to_pling_device
64
+ self
65
+ end
66
+
67
+ end
68
+ end
@@ -0,0 +1,17 @@
1
+ module Pling
2
+ module Gateway
3
+ autoload :Base, 'pling/gateway/base'
4
+ autoload :C2DM, 'pling/gateway/c2dm'
5
+ autoload :APN, 'pling/gateway/apn'
6
+
7
+ class << self
8
+ def discover(device)
9
+ device = Pling._convert(device, :device)
10
+ Pling.gateways.initialize!
11
+ Pling.gateways.detect do |gateway|
12
+ gateway.handles?(device)
13
+ end or raise(Pling::NoGatewayFound, "Could not find a gateway for #{device.class} with type :#{device.type}")
14
+ end
15
+ end
16
+ end
17
+ end