puller 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md ADDED
@@ -0,0 +1,4 @@
1
+ ## 0.1.0 (January 19, 2012)
2
+
3
+ * initial release
4
+
data/README.md CHANGED
@@ -1,29 +1,99 @@
1
1
  # Puller
2
2
 
3
- TODO: Write a gem description
3
+ Puller is a Ruby gem for use with Rails to publish and subscribe to messages through [Pusher](http://pusher.com/). It allows you to easily provide real-time updates through an open socket without tying up a Rails process.
4
4
 
5
- ## Installation
5
+ Its code and API design is heavily inspired by Ryan Bates' [PrivatePub](http://github.com/ryanb/private_pub).
6
6
 
7
- Add this line to your application's Gemfile:
8
7
 
9
- gem 'puller'
8
+ ## Setup
10
9
 
11
- And then execute:
10
+ Add the gem to the Gemfile of your Rails application and run the `bundle` command to install it.
12
11
 
13
- $ bundle
12
+ ```ruby
13
+ gem "puller"
14
+ ```
14
15
 
15
- Or install it yourself as:
16
16
 
17
- $ gem install puller
17
+ **In Rails 3.1** add the JavaScript file to your application.js file manifest.
18
+
19
+ ```javascript
20
+ //= require puller
21
+ ```
22
+
23
+
24
+ **In Rails 3.0** run the generator to create the initial files
25
+
26
+ ```
27
+ rails g puller:install
28
+ ```
29
+
30
+ and add the generated puller.js file to your layout.
31
+
32
+ ```rhtml
33
+ <%= javascript_include_tag "puller" %>
34
+ ```
35
+
36
+
37
+ It's not necessary to include pusher.js since that will be handled automatically for you.
38
+
39
+ Configure your Pusher credentials in an initializer file.
40
+
41
+ ```ruby
42
+ Pusher.app_id = 'my_pusher_app_id'
43
+ Pusher.key = 'my_pusher_api_key'
44
+ Pusher.secret = 'my_pusher_secret'
45
+ ```
46
+
47
+ Note: You can skip this step if you are on Heroku and have activated the Pusher addon for your app.
48
+
18
49
 
19
50
  ## Usage
20
51
 
21
- TODO: Write usage instructions here
52
+ Use the `subscribe_to` helper method on any page to subscribe to a channel/event.
53
+
54
+ ```rhtml
55
+ <%= subscribe_to "new-message" %>
56
+ ```
57
+
58
+ The above method subscribes to the "new-message" event in the default channel. The default channel name is
59
+ the parameterized form of your Rails' application name. To use a different channel, prepend your custom
60
+ channel name to the argument followed by a colon:
61
+
62
+ ```rhtml
63
+ <%= subscribe_to "my-channel:new-message" %>
64
+ ```
65
+
66
+ Use the `publish_to` helper method to send JavaScript to that channel/event. This is usually done in a JavaScript AJAX template (such as a create.js.erb file).
67
+
68
+ ```rhtml
69
+ <% publish_to "new-message" do %>
70
+ $("#chat").append("<%= j render(@message) %>");
71
+ <% end %>
72
+ ```
73
+
74
+ This JavaScript will be immediately evaluated on all clients who have subscribed to that channel. In this example they will see the new chat message appear in real-time without reloading the browser.
75
+
76
+
77
+ ## Alternative Usage
78
+
79
+ If you prefer to work through JSON instead of `.js.erb` templates, you can pass a hash to `publish_to` instead of a block and it will be converted `to_json` behind the scenes. This can be done anywhere (such as the controller).
80
+
81
+ ```ruby
82
+ Puller.publish_to "new-message", :chat_message => "Hello, world!"
83
+ ```
84
+
85
+ And then handle this through JavaScript on the client side.
86
+
87
+ ```javascript
88
+ Puller.subscribe("new-message", function(data, channel) {
89
+ $("#chat").append(data.chat_message);
90
+ });
91
+ ```
92
+
93
+ The Ruby `subscribe_to` helper call is still necessary with this approach to grant the user access to the channel. The JavaScript is just a callback for any custom behavior.
94
+
95
+
96
+ ## Development & Feedback
22
97
 
23
- ## Contributing
98
+ Questions or comments? Please use the [issue tracker](https://github.com/natekross/puller/issues). Tests can be run with `bundle` and `rake` commands.
24
99
 
25
- 1. Fork it
26
- 2. Create your feature branch (`git checkout -b my-new-feature`)
27
- 3. Commit your changes (`git commit -am 'Added some feature'`)
28
- 4. Push to the branch (`git push origin my-new-feature`)
29
- 5. Create new Pull Request
data/Rakefile CHANGED
@@ -1,3 +1,16 @@
1
1
  #!/usr/bin/env rake
2
2
  require "bundler/gem_tasks"
3
3
 
4
+ require "rubygems"
5
+ require "rake"
6
+ require "rspec/core/rake_task"
7
+ require "jasmine"
8
+ load "jasmine/tasks/jasmine.rake"
9
+
10
+ desc "Run RSpec"
11
+ RSpec::Core::RakeTask.new do |t|
12
+ t.verbose = false
13
+ end
14
+
15
+ task :default => [:spec, "jasmine:ci"]
16
+
@@ -0,0 +1,72 @@
1
+ function buildPuller(doc) {
2
+ var self = {
3
+ connecting: false,
4
+ pusher: null,
5
+ initCallbacks: [],
6
+ subscriptions: {},
7
+ subscriptionCallbacks: {},
8
+ channels: {},
9
+
10
+ fetchPusher: function(callback) {
11
+ if (self.pusher) {
12
+ callback(self.pusher);
13
+ } else {
14
+ self.initCallbacks.push(callback);
15
+
16
+ if (self.subscriptions.apiKey && !self.connecting) {
17
+ self.connecting = true;
18
+
19
+ var script = doc.createElement("script");
20
+ script.type = "text/javascript";
21
+ script.src = "http://js.pusher.com/1.11/pusher.min.js";
22
+ script.onload = self.connectToPusher;
23
+
24
+ doc.documentElement.appendChild(script);
25
+ }
26
+ }
27
+ },
28
+
29
+ connectToPusher: function() {
30
+ self.pusher = new Pusher(self.subscriptions.apiKey);
31
+
32
+ for (var i = 0, ii = self.initCallbacks.length; i < ii; i++) {
33
+ self.initCallbacks[i](self.pusher);
34
+ };
35
+ },
36
+
37
+ sign: function(options) {
38
+ if (!self.subscriptions.apiKey) {
39
+ self.subscriptions.apiKey = options.apiKey;
40
+ }
41
+
42
+ self.subscriptions[options.channel + ":" + options.event] = options;
43
+
44
+ self.fetchPusher(function(pusher) {
45
+ if (!self.channels[options.channel]) {
46
+ self.channels[options.channel] = pusher.subscribe(options.channel);
47
+ }
48
+
49
+ self.channels[options.channel].bind(options.event, self.handleResponse);
50
+ });
51
+ },
52
+
53
+ handleResponse: function(message) {
54
+ if (message.eval) {
55
+ eval(message.eval);
56
+ }
57
+
58
+ if (callback = self.subscriptionCallbacks[message.channel + ":" + message.event]) {
59
+ callback(message.data, message.channel, message.event);
60
+ }
61
+ },
62
+
63
+ subscribe: function(channel_event, callback) {
64
+ self.subscriptionCallbacks[channel_event] = callback;
65
+ }
66
+ };
67
+
68
+ return self;
69
+ }
70
+
71
+ var Puller = buildPuller(document);
72
+
@@ -0,0 +1,16 @@
1
+ module Puller
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+ def self.source_root
5
+ File.dirname(__FILE__) + "/templates"
6
+ end
7
+
8
+ def copy_files
9
+ if ::Rails.version < "3.1"
10
+ copy_file "../../../../app/assets/javascripts/puller.js", "public/javascripts/puller.js"
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+
File without changes
@@ -0,0 +1,19 @@
1
+ require "puller/view_helpers"
2
+
3
+ module Puller
4
+ class Engine < Rails::Engine
5
+ # Adds the ViewHelpers into ActionView::Base and sets
6
+ # Pusher's logger to Rails' logger.
7
+ initializer "puller.init" do
8
+ ActionView::Base.send :include, ViewHelpers
9
+ Pusher.logger = Rails.logger
10
+
11
+ Puller.default_channel = begin
12
+ app_name = Rails.application.class.name
13
+ app_name = app_name.split('::').first
14
+ app_name.parameterize
15
+ end
16
+ end
17
+ end
18
+ end
19
+
@@ -1,4 +1,4 @@
1
1
  module Puller
2
- VERSION = "0.0.0"
2
+ VERSION = "0.1.0"
3
3
  end
4
4
 
@@ -0,0 +1,18 @@
1
+ module Puller
2
+ module ViewHelpers
3
+ def publish_to(channel_event, data = nil, &block)
4
+ Puller.publish_to channel_event, data || capture(&block)
5
+ end
6
+
7
+ def subscribe_to(channel_event)
8
+ channel, event = Puller.channel_and_event_for(channel_event)
9
+
10
+ options = { :channel => channel, :event => event, :apiKey => Pusher.key }
11
+
12
+ content_tag "script", :type => "text/javascript" do
13
+ raw "Puller.sign(#{options.to_json});"
14
+ end
15
+ end
16
+ end
17
+ end
18
+
data/lib/puller.rb CHANGED
@@ -1,6 +1,35 @@
1
+ require "pusher"
2
+
1
3
  require "puller/version"
4
+ require "puller/engine" if defined? Rails
2
5
 
3
6
  module Puller
4
- # Your code goes here...
7
+ class << self
8
+ attr_writer :default_channel
9
+
10
+ def default_channel
11
+ @default_channel ||= "default"
12
+ end
13
+
14
+ def publish_to(channel_event, data)
15
+ channel, event = channel_and_event_for(channel_event)
16
+
17
+ message = { :channel => channel, :event => event }
18
+ message[data.kind_of?(String) ? :eval : :data] = data
19
+
20
+ Pusher[channel].trigger event, message
21
+ end
22
+
23
+ def channel_and_event_for(text)
24
+ channel, event = text.split(':', 1)
25
+
26
+ if event.nil?
27
+ event = channel
28
+ channel = default_channel
29
+ end
30
+
31
+ [channel, event]
32
+ end
33
+ end
5
34
  end
6
35
 
data/puller.gemspec CHANGED
@@ -17,5 +17,11 @@ Gem::Specification.new do |gem|
17
17
 
18
18
  gem.rubyforge_project = gem.name
19
19
  gem.required_rubygems_version = ">= 1.3.4"
20
+
21
+ gem.add_dependency "pusher", "~> 0.8.5"
22
+
23
+ gem.add_development_dependency "rake"
24
+ gem.add_development_dependency "rspec", "~> 2.8.0"
25
+ gem.add_development_dependency "jasmine", ">= 1.1.1"
20
26
  end
21
27
 
@@ -0,0 +1,109 @@
1
+ describe("Puller", function() {
2
+ var puller, doc;
3
+
4
+ beforeEach(function() {
5
+ Pusher = {}; // To simulate global Pusher object
6
+ doc = {};
7
+ puller = buildPuller(doc);
8
+ });
9
+
10
+ it("adds a subscription callback", function() {
11
+ puller.subscribe("hello", "callback");
12
+ expect(puller.subscriptionCallbacks["hello"]).toEqual("callback");
13
+ });
14
+
15
+ it("evaluates javascript in message response", function() {
16
+ puller.handleResponse({eval: 'self.subscriptions.foo = "bar"'});
17
+ expect(puller.subscriptions.foo).toEqual("bar");
18
+ });
19
+
20
+ it("triggers callback matching message channel in response", function() {
21
+ var called = false;
22
+ puller.subscribe("chan:test", function(data, channel, event) {
23
+ expect(data).toEqual("abcd");
24
+ expect(channel).toEqual("chan");
25
+ expect(event).toEqual("test");
26
+ called = true;
27
+ });
28
+ puller.handleResponse({channel: "chan", event: "test", data: "abcd"});
29
+ expect(called).toBeTruthy();
30
+ });
31
+
32
+ it("adds a pusher subscription with response handler when signing", function() {
33
+ var subscribeCalled = false,
34
+ bindCalled = false,
35
+ channel = {
36
+ bind: function(event, callback) {
37
+ expect(event).toEqual("someevent");
38
+ expect(callback).toEqual(puller.handleResponse);
39
+ bindCalled = true;
40
+ }
41
+ },
42
+ pusher = {
43
+ subscribe: function(channelName) {
44
+ expect(channelName).toEqual("somechannel");
45
+ subscribeCalled = true;
46
+
47
+ return channel;
48
+ }
49
+ };
50
+
51
+ spyOn(puller, 'fetchPusher').andCallFake(function(callback) {
52
+ callback(pusher);
53
+ });
54
+
55
+ var options = {apiKey: "someapikey", channel: "somechannel", event: "someevent"};
56
+ puller.sign(options);
57
+
58
+ expect(puller.subscriptions.apiKey).toEqual("someapikey");
59
+ expect(puller.subscriptions["somechannel:someevent"]).toEqual(options);
60
+
61
+ expect(subscribeCalled).toBeTruthy();
62
+ expect(bindCalled).toBeTruthy();
63
+
64
+ expect(puller.channels["somechannel"]).toEqual(channel);
65
+ });
66
+
67
+ it("triggers pusher callback function immediately when pusher is available", function() {
68
+ var called = false;
69
+ puller.pusher = "pusher";
70
+ puller.fetchPusher(function(pusher) {
71
+ expect(pusher).toEqual("pusher");
72
+ called = true;
73
+ });
74
+ expect(called).toBeTruthy();
75
+ });
76
+
77
+ it("adds initCallback when client and server aren't available", function() {
78
+ puller.fetchPusher("callback");
79
+ expect(puller.initCallbacks[0]).toEqual("callback");
80
+ });
81
+
82
+ it("adds a script tag loading pusher js when the server is present", function() {
83
+ script = {};
84
+ doc.createElement = function() { return script; };
85
+ doc.documentElement = {appendChild: jasmine.createSpy()};
86
+ puller.subscriptions.apiKey = "someapikey";
87
+ puller.fetchPusher("callback");
88
+ expect(puller.initCallbacks[0]).toEqual("callback");
89
+ expect(script.type).toEqual("text/javascript");
90
+ expect(script.src).toEqual("http://js.pusher.com/1.11/pusher.min.js");
91
+ expect(script.onload).toEqual(puller.connectToPusher);
92
+ expect(doc.documentElement.appendChild).toHaveBeenCalledWith(script);
93
+ });
94
+
95
+ it("connects to pusher server and executes callbacks", function() {
96
+ callback = jasmine.createSpy();
97
+ client = { foo: "bar" };
98
+ Pusher = function(key) {
99
+ expect(key).toEqual("key")
100
+ return client;
101
+ };
102
+ puller.subscriptions.apiKey = "key";
103
+ puller.initCallbacks.push(callback);
104
+ puller.connectToPusher();
105
+ expect(puller.pusher).toEqual(client);
106
+ expect(callback).toHaveBeenCalledWith(client);
107
+ });
108
+ });
109
+
@@ -0,0 +1,74 @@
1
+ # src_files
2
+ #
3
+ # Return an array of filepaths relative to src_dir to include before jasmine specs.
4
+ # Default: []
5
+ #
6
+ # EXAMPLE:
7
+ #
8
+ # src_files:
9
+ # - lib/source1.js
10
+ # - lib/source2.js
11
+ # - dist/**/*.js
12
+ #
13
+ src_files:
14
+ - app/assets/javascripts/puller.js
15
+
16
+ # stylesheets
17
+ #
18
+ # Return an array of stylesheet filepaths relative to src_dir to include before jasmine specs.
19
+ # Default: []
20
+ #
21
+ # EXAMPLE:
22
+ #
23
+ # stylesheets:
24
+ # - css/style.css
25
+ # - stylesheets/*.css
26
+ #
27
+ stylesheets:
28
+
29
+ # helpers
30
+ #
31
+ # Return an array of filepaths relative to spec_dir to include before jasmine specs.
32
+ # Default: ["helpers/**/*.js"]
33
+ #
34
+ # EXAMPLE:
35
+ #
36
+ # helpers:
37
+ # - helpers/**/*.js
38
+ #
39
+ helpers:
40
+
41
+ # spec_files
42
+ #
43
+ # Return an array of filepaths relative to spec_dir to include.
44
+ # Default: ["**/*[sS]pec.js"]
45
+ #
46
+ # EXAMPLE:
47
+ #
48
+ # spec_files:
49
+ # - **/*[sS]pec.js
50
+ #
51
+ spec_files:
52
+
53
+ # src_dir
54
+ #
55
+ # Source directory path. Your src_files must be returned relative to this path. Will use root if left blank.
56
+ # Default: project root
57
+ #
58
+ # EXAMPLE:
59
+ #
60
+ # src_dir: public
61
+ #
62
+ src_dir:
63
+
64
+ # spec_dir
65
+ #
66
+ # Spec directory path. Your spec_files must be returned relative to this path.
67
+ # Default: spec/javascripts
68
+ #
69
+ # EXAMPLE:
70
+ #
71
+ # spec_dir: spec/javascripts
72
+ #
73
+ spec_dir:
74
+
@@ -0,0 +1,24 @@
1
+ module Jasmine
2
+ class Config
3
+
4
+ # Add your overrides or custom config code here
5
+
6
+ end
7
+ end
8
+
9
+
10
+ # Note - this is necessary for rspec2, which has removed the backtrace
11
+ module Jasmine
12
+ class SpecBuilder
13
+ def declare_spec(parent, spec)
14
+ me = self
15
+ example_name = spec["name"]
16
+ @spec_ids << spec["id"]
17
+ backtrace = @example_locations[parent.description + " " + example_name]
18
+ parent.it example_name, {} do
19
+ me.report_spec(spec["id"])
20
+ end
21
+ end
22
+ end
23
+ end
24
+
@@ -0,0 +1,33 @@
1
+ $:.unshift(ENV['JASMINE_GEM_PATH']) if ENV['JASMINE_GEM_PATH'] # for gem testing purposes
2
+
3
+ require 'rubygems'
4
+ require 'jasmine'
5
+ jasmine_config_overrides = File.expand_path(File.join(File.dirname(__FILE__), 'jasmine_config.rb'))
6
+ require jasmine_config_overrides if File.exist?(jasmine_config_overrides)
7
+ if Jasmine::Dependencies.rspec2?
8
+ require 'rspec'
9
+ else
10
+ require 'spec'
11
+ end
12
+
13
+ jasmine_config = Jasmine::Config.new
14
+ spec_builder = Jasmine::SpecBuilder.new(jasmine_config)
15
+
16
+ should_stop = false
17
+
18
+ if Jasmine::Dependencies.rspec2?
19
+ RSpec.configuration.after(:suite) do
20
+ spec_builder.stop if should_stop
21
+ end
22
+ else
23
+ Spec::Runner.configure do |config|
24
+ config.after(:suite) do
25
+ spec_builder.stop if should_stop
26
+ end
27
+ end
28
+ end
29
+
30
+ spec_builder.start
31
+ should_stop = true
32
+ spec_builder.declare_suites
33
+
@@ -0,0 +1,8 @@
1
+ require "spec_helper"
2
+
3
+ describe Puller do
4
+ it "defaults default_channel to 'default'" do
5
+ Puller.default_channel.should == 'default'
6
+ end
7
+ end
8
+
@@ -0,0 +1,9 @@
1
+ require "rubygems"
2
+ require "bundler/setup"
3
+ require "pusher"
4
+
5
+ Bundler.require(:default)
6
+
7
+ RSpec.configure do |config|
8
+ end
9
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: puller
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,51 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
  date: 2012-01-19 00:00:00.000000000Z
13
- dependencies: []
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: pusher
16
+ requirement: &25369220 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 0.8.5
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *25369220
25
+ - !ruby/object:Gem::Dependency
26
+ name: rake
27
+ requirement: &25368580 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *25368580
36
+ - !ruby/object:Gem::Dependency
37
+ name: rspec
38
+ requirement: &25367700 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 2.8.0
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *25367700
47
+ - !ruby/object:Gem::Dependency
48
+ name: jasmine
49
+ requirement: &25365580 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: 1.1.1
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *25365580
14
58
  description: -- gem description --
15
59
  email: natekross@gmail.com
16
60
  executables: []
@@ -18,13 +62,25 @@ extensions: []
18
62
  extra_rdoc_files: []
19
63
  files:
20
64
  - .gitignore
65
+ - CHANGELOG.md
21
66
  - Gemfile
22
67
  - LICENSE
23
68
  - README.md
24
69
  - Rakefile
70
+ - app/assets/javascripts/puller.js
71
+ - lib/generators/puller/install_generator.rb
72
+ - lib/generators/puller/templates/.gitkeep
25
73
  - lib/puller.rb
74
+ - lib/puller/engine.rb
26
75
  - lib/puller/version.rb
76
+ - lib/puller/view_helpers.rb
27
77
  - puller.gemspec
78
+ - spec/javascripts/puller_spec.js
79
+ - spec/javascripts/support/jasmine.yml
80
+ - spec/javascripts/support/jasmine_config.rb
81
+ - spec/javascripts/support/jasmine_runner.rb
82
+ - spec/puller_spec.rb
83
+ - spec/spec_helper.rb
28
84
  homepage: http://github.com/natekross/puller
29
85
  licenses: []
30
86
  post_install_message:
@@ -37,6 +93,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
37
93
  - - ! '>='
38
94
  - !ruby/object:Gem::Version
39
95
  version: '0'
96
+ segments:
97
+ - 0
98
+ hash: 3142173162834286048
40
99
  required_rubygems_version: !ruby/object:Gem::Requirement
41
100
  none: false
42
101
  requirements: