puller 0.0.0 → 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/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: