pling 0.0.1 → 0.1.0

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