pushable 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2013 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,73 @@
1
+ # Pushable
2
+
3
+ [Ah, push it, pu-pu-push it real good!](http://www.youtube.com/watch?v=vCadcBR95oU)
4
+
5
+ ![Salt-n-Pepper](http://1.bp.blogspot.com/_1e1s_bYTxyE/TEZUEini5hI/AAAAAAAABIc/QzKyLsLJCOs/s1600/Salt-n-Pepa_l.jpg)
6
+
7
+ Push changes in your ActiveModels to Backbone.js clients' models and collections.
8
+
9
+ Overview
10
+ --------
11
+
12
+ At this point there are three ActiveModel events that can be pushed out to the Faye server, creation, updat-uhh-ion, and deletion. They can pushed to two channels, collection channels and instance channels. For example if you had a family model and it has_many member models you might have a Backbone family collection, when a new member was born, you're family Backbone collection would be notified via the collection channel, but other families don't need to be notified of the new member. So you can scope a model's collection channel by defining a collection_channel method in both the ActiveModel and the Backbone collection. Similarly you could customize the instance channel used with instance_channel methods. As usual the code speaks for itself:
13
+
14
+ class Family < ActiveRecord::Base
15
+ attr_accessible :name
16
+ has_many :members
17
+ end
18
+
19
+ class Member < ActiveRecord::Base
20
+ attr_accessible :name, :family_id
21
+ belongs_to :family
22
+
23
+ push :create => :collections,
24
+ :update => [ :collections, :instances ],
25
+ :destroy => [ :collections, :instances ]
26
+
27
+ def collection_channel
28
+ "family_#{family_id}_members"
29
+ end
30
+ end
31
+
32
+ class Family extends Backbone.PushableCollection
33
+ channel: -> "family_#{@id}_members"
34
+
35
+ url: ->
36
+ if @id
37
+ '/families/' + @id + '/members.json'
38
+
39
+ model: Member
40
+
41
+ initialize: (members, options) -> # members is actually options if not initialized with members
42
+ try
43
+ @id = (members || options).id
44
+ catch TypeError
45
+ throw "MissingFamilyID"
46
+ super()
47
+
48
+ class Member extends Backbone.PushableModel
49
+ url: ->
50
+ if @isNew()
51
+ "/families/#{@family_id}/members.json"
52
+ else
53
+ "/members/#{@id}.json"
54
+
55
+ channel: -> "member_#{@id}"
56
+
57
+ Starting the Faye Server
58
+ ------------------------
59
+ Start a Faye server instance with...
60
+
61
+ rake faye:start
62
+
63
+ This rake task supports the following options:
64
+ * `verbose=true`
65
+ * `port=9292` 9292 is the default
66
+ * `key=server.key` For an SSL key
67
+ * `cert=server.crt` For an SSL certificate
68
+
69
+ Todo
70
+ ----
71
+
72
+ * Write some specs
73
+ * Flesh the docs out more, an example app even
data/Rakefile ADDED
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'Pushable'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+
24
+
25
+
26
+ Bundler::GemHelper.install_tasks
27
+
28
+ task :default => :test
data/lib/pushable.rb ADDED
@@ -0,0 +1,66 @@
1
+ require 'net/http'
2
+
3
+ module Pushable
4
+ require 'pushable/railtie' if defined?(Rails)
5
+
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ after_create :push_create
10
+ after_update :push_update
11
+ after_destroy :push_destroy
12
+ end
13
+
14
+ [:create, :update, :destroy].each do |event|
15
+ define_method "push_#{event.to_s}" do
16
+ Array.wrap(self.class.pushes[event]).each do |channel|
17
+ ActionPusher.new.push event, evaluate(channel), self
18
+ end
19
+ end
20
+ end
21
+
22
+ def collection_channel
23
+ self.class.name.underscore.pluralize
24
+ end
25
+
26
+ def instance_channel
27
+ "#{self.class.name.underscore}_#{id}"
28
+ end
29
+
30
+ def evaluate(channel)
31
+ case channel
32
+ when :collections
33
+ collection_channel
34
+ when :instances
35
+ instance_channel
36
+ when Symbol #references association
37
+ associate = send(channel)
38
+ "#{associate.class.name.underscore}_#{associate.id}_#{self.class.name.underscore.pluralize}"
39
+ when String
40
+ eval '"' + channel + '"'
41
+ when Proc
42
+ evaluate channel.call(self)
43
+ end
44
+ end
45
+
46
+ module ClassMethods
47
+ attr_accessor :pushes
48
+
49
+ def pushable
50
+ @pushes = { create: :collections,
51
+ update: [ :collections, :instances ],
52
+ destroy: [ :collections, :instances ] } #default events and channels
53
+ end
54
+
55
+ def push(options={})
56
+ @pushes = options
57
+ end
58
+ end
59
+
60
+ module Rails
61
+ module Faye
62
+ mattr_accessor :address
63
+ self.address = 'http://localhost:9292'
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,47 @@
1
+ class ActionPusher < AbstractController::Base
2
+ include AbstractController::Rendering
3
+ include AbstractController::Helpers
4
+ include AbstractController::Translation
5
+ include AbstractController::AssetPaths
6
+ include Rails.application.routes.url_helpers
7
+ helper ApplicationHelper
8
+ include ApplicationHelper
9
+ self.view_paths = "app/views"
10
+
11
+ NET_HTTP_EXCEPTIONS = [Timeout::Error, Errno::ETIMEDOUT, Errno::EINVAL,
12
+ Errno::ECONNRESET, Errno::ECONNREFUSED, EOFError,
13
+ Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError,
14
+ Net::ProtocolError]
15
+
16
+
17
+ def push(event, channel, object)
18
+ url = URI.parse("#{Pushable::Rails::Faye.address}/faye")
19
+ req = Net::HTTP::Post.new(url.path)
20
+
21
+ req.form_data = { message: { channel: "/sync/#{channel}",
22
+ data: { object: render_for_push(object),
23
+ event: event } }.to_json }
24
+ req.basic_auth url.user, url.password if url.user
25
+
26
+ http = Net::HTTP.new(url.host, url.port)
27
+ if url.scheme == "https"
28
+ http.use_ssl = true
29
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
30
+ end
31
+
32
+ http.start do |https|
33
+ https.request(req)
34
+ end
35
+ rescue *NET_HTTP_EXCEPTIONS => exception
36
+ ::Rails.logger.error("")
37
+ ::Rails.logger.error("ActionPusher encountered an exception:")
38
+ ::Rails.logger.error(exception.class.name)
39
+ ::Rails.logger.error(exception.message)
40
+ ::Rails.logger.error(exception.backtrace.join("\n"))
41
+ ::Rails.logger.error("")
42
+ end
43
+
44
+ def render_for_push(object)
45
+ render_to_hash(partial: "#{object.class.name.underscore.pluralize}/#{object.class.name.underscore}", object: object)
46
+ end
47
+ end
@@ -0,0 +1,70 @@
1
+ require 'net/http'
2
+
3
+ module Pushable
4
+ module ActsAsPushable
5
+ extend ActiveSupport::Concern
6
+
7
+ def self.included(base)
8
+ base.send :extend, ClassMethods
9
+ end
10
+
11
+ included do
12
+ after_create :push_create
13
+ after_update :push_update
14
+ after_destroy :push_destroy
15
+ end
16
+
17
+ [:create, :update, :destroy].each do |event|
18
+ define_method "push_#{event.to_s}" do
19
+ Array.wrap(self.class.pushes[event]).each do |channel|
20
+ ActionPusher.new.push event, evaluate(channel), self
21
+ end
22
+ end
23
+ end
24
+
25
+ def collection_channel
26
+ self.class.name.underscore.pluralize
27
+ end
28
+
29
+ def instance_channel
30
+ "#{self.class.name.underscore}_#{id}"
31
+ end
32
+
33
+ def evaluate(channel)
34
+ case channel
35
+ when :collections
36
+ collection_channel
37
+ when :instances
38
+ instance_channel
39
+ when Symbol #references association
40
+ associate = send(channel)
41
+ "#{associate.class.name.underscore}_#{associate.id}_#{self.class.name.underscore.pluralize}"
42
+ when String
43
+ eval '"' + channel + '"'
44
+ when Proc
45
+ evaluate channel.call(self)
46
+ end
47
+ end
48
+
49
+ module ClassMethods
50
+ def push(options={})
51
+ @pushes = options
52
+ end
53
+
54
+ def pushes
55
+ @pushes ||= { create: :collections,
56
+ update: [ :collections, :instances ],
57
+ destroy: :collections } #default events and channels
58
+ end
59
+ end
60
+
61
+ module Rails
62
+ module Faye
63
+ mattr_accessor :address
64
+ self.address = 'https://localhost:9292'
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ ActiveModel::Base.send :include, Pushable::ActsAsPushable
@@ -0,0 +1,12 @@
1
+ require 'pushable'
2
+ require 'rails'
3
+
4
+ module Pushable
5
+ class Railtie < Rails::Railtie
6
+ railtie_name :pushable
7
+
8
+ rake_tasks do
9
+ load "tasks/faye.rake"
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ module Pushable
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,20 @@
1
+ namespace "faye" do
2
+ desc "Start the Faye server"
3
+ task :start do
4
+ require 'faye'
5
+
6
+ if ENV['verbose']
7
+ Faye::Logging.log_level = :info
8
+ Faye.logger = lambda { |m| puts m }
9
+ end
10
+
11
+ bayeux = Faye::RackAdapter.new :mount => '/faye', :timeout => 25
12
+
13
+ port = ENV['port'] || 9292
14
+ if ENV['key'] and ENV['cert']
15
+ bayeux.listen port, :key => ENV['key'], :cert => ENV['cert']
16
+ else
17
+ bayeux.listen port
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :pushable do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,157 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pushable
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Bill Transue
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2013-05-22 00:00:00 -04:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rails
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ~>
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 3
29
+ - 2
30
+ - 12
31
+ version: 3.2.12
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: faye
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 0
43
+ version: "0"
44
+ type: :runtime
45
+ version_requirements: *id002
46
+ - !ruby/object:Gem::Dependency
47
+ name: thin
48
+ prerelease: false
49
+ requirement: &id003 !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ segments:
54
+ - 0
55
+ version: "0"
56
+ type: :runtime
57
+ version_requirements: *id003
58
+ - !ruby/object:Gem::Dependency
59
+ name: sqlite3
60
+ prerelease: false
61
+ requirement: &id004 !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ segments:
66
+ - 0
67
+ version: "0"
68
+ type: :development
69
+ version_requirements: *id004
70
+ - !ruby/object:Gem::Dependency
71
+ name: rake
72
+ prerelease: false
73
+ requirement: &id005 !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ segments:
78
+ - 0
79
+ version: "0"
80
+ type: :development
81
+ version_requirements: *id005
82
+ - !ruby/object:Gem::Dependency
83
+ name: rspec-rails
84
+ prerelease: false
85
+ requirement: &id006 !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ segments:
90
+ - 0
91
+ version: "0"
92
+ type: :development
93
+ version_requirements: *id006
94
+ - !ruby/object:Gem::Dependency
95
+ name: debugger
96
+ prerelease: false
97
+ requirement: &id007 !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ segments:
102
+ - 0
103
+ version: "0"
104
+ type: :development
105
+ version_requirements: *id007
106
+ description: Push ActiveModel changes to Backbone Collections and Models though a Faye Pub/Sub server.
107
+ email:
108
+ - transue@gmail.com
109
+ executables: []
110
+
111
+ extensions: []
112
+
113
+ extra_rdoc_files: []
114
+
115
+ files:
116
+ - lib/pushable/action_pusher.rb
117
+ - lib/pushable/acts_as_pushable.rb
118
+ - lib/pushable/railtie.rb
119
+ - lib/pushable/version.rb
120
+ - lib/pushable.rb
121
+ - lib/tasks/faye.rake
122
+ - lib/tasks/pushable_tasks.rake
123
+ - MIT-LICENSE
124
+ - Rakefile
125
+ - README.md
126
+ has_rdoc: true
127
+ homepage: https://github.com/billy-ran-away/pushable
128
+ licenses: []
129
+
130
+ post_install_message:
131
+ rdoc_options: []
132
+
133
+ require_paths:
134
+ - lib
135
+ required_ruby_version: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ segments:
140
+ - 0
141
+ version: "0"
142
+ required_rubygems_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ segments:
147
+ - 0
148
+ version: "0"
149
+ requirements: []
150
+
151
+ rubyforge_project:
152
+ rubygems_version: 1.3.6
153
+ signing_key:
154
+ specification_version: 3
155
+ summary: Ah, push it pu-pu-push it real good. Your ActiveModels' changes to Backbone.js clients that is.
156
+ test_files: []
157
+