entangled 0.0.1

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b7dd7732a7af0fdbc6a792eb242f507817adcfde
4
+ data.tar.gz: 0091ccff6cdad24ac350dace118ae33a5331bc5a
5
+ SHA512:
6
+ metadata.gz: f64204d586309f97651845b5a642a333a4f88d943608326d84063560b4f2ed180304433a7caa5810cb5ddb2eed9c296ab5e1f4c535ea6d05219215ed02385e93
7
+ data.tar.gz: 8573a37652353e3b32807bb653040036b2981927a74fa4565a9b51897f24d37ae3996b4720b689b4a791ad59e6933f624d0fe4d23d17b92e4bd9fee98f23832b
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in entangled.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Dennis Charles Hackethal
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # Entangled
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'entangled'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install entangled
20
+
21
+ ## Usage
22
+
23
+ TODO: Write usage instructions here
24
+
25
+ ## Contributing
26
+
27
+ 1. Fork it ( https://github.com/[my-github-username]/entangled/fork )
28
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
29
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
30
+ 4. Push to the branch (`git push origin my-new-feature`)
31
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/entangled.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'entangled/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "entangled"
8
+ spec.version = Entangled::VERSION
9
+ spec.authors = ["Dennis Charles Hackethal"]
10
+ spec.email = ["dennis.hackethal@gmail.com"]
11
+ spec.summary = %q{Makes Rails real time through websockets.}
12
+ spec.description = %q{Makes Rails real time through websockets. Check out the JavaScript counterpart for the front end.}
13
+ spec.homepage = "https://github.com/so-entangled/rails"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.7"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_dependency 'tubesock', '~> 0.2'
24
+ spec.add_dependency 'rails', '~> 4.2'
25
+ end
data/lib/entangled.rb ADDED
@@ -0,0 +1,269 @@
1
+ require "entangled/version"
2
+ require 'tubesock'
3
+
4
+ module Entangled
5
+ module Model
6
+ module ClassMethods
7
+ # Create after_ callbacks for options
8
+ def entangle(options = {})
9
+
10
+ # If :only is specified, the options can either
11
+ # be an array or a symbol
12
+ if options[:only].present?
13
+
14
+ # If it is a symbol, something like only: :create
15
+ # was passed in, and we need to create a hook
16
+ # only for that one option
17
+ if options[:only].is_a?(Symbol)
18
+ create_hook options[:only]
19
+
20
+ # If it is an array, something like only: [:create, :update]
21
+ # was passed in, and we need to create hook for each
22
+ # of these options
23
+ elsif options[:only].is_a?(Array)
24
+ options[:only].each { |option| create_hook option }
25
+ end
26
+
27
+ # Instead of :only, :except can be specified; similarly,
28
+ # the options can either be an array or a symbol
29
+ elsif options[:except].present?
30
+
31
+ # If it is a symbol, it has to be taken out of the default
32
+ # options. A callback has to be defined for each of the
33
+ # remaining options
34
+ if options[:except].is_a?(Symbol)
35
+ (default_options - [options[:except]]).each do |option|
36
+ create_hook option
37
+ end
38
+
39
+ # If it is an array, it also has to be taen out of the
40
+ # default options. A callback then also has to be defined
41
+ # for each of the remaining options
42
+ elsif options[:except].is_a?(Array)
43
+ (default_options - options[:except]).each do |option|
44
+ create_hook option
45
+ end
46
+ end
47
+ else
48
+
49
+ # If neither :only nor :except is specified, simply create
50
+ # a callback for each default option
51
+ default_options.each { |option| create_hook option }
52
+ end
53
+ end
54
+
55
+ # By default, model updates will be published after_create,
56
+ # after_update, and after_destroy. This behavior can be
57
+ # modified by passing :only or :except options to the
58
+ # entangle class method
59
+ def default_options
60
+ [:create, :update, :destroy]
61
+ end
62
+
63
+ # The inferred channel name. For example, if the class name
64
+ # is DeliciousTaco, the inferred channel name is "delicious_tacos"
65
+ def inferred_channel_name
66
+ name.underscore.pluralize
67
+ end
68
+
69
+ # Creates callbacks in the extented model
70
+ def create_hook(name)
71
+ send :"after_#{name}", proc { publish(name) }
72
+ end
73
+ end
74
+
75
+ module InstanceMethods
76
+ private
77
+
78
+ # Publishes to client. Whoever is subscribed
79
+ # to the model's channel or the record's channel
80
+ # gets the message
81
+ def publish(action)
82
+ Redis.new.publish(
83
+ self.class.inferred_channel_name,
84
+ json(action)
85
+ )
86
+
87
+ Redis.new.publish(
88
+ inferred_channel_name_for_single_record,
89
+ json(action)
90
+ )
91
+ end
92
+
93
+ # The inferred channel name for a single record
94
+ # containing the inferred channel name from the class
95
+ # and the record's id. For example, if it's a
96
+ # DeliciousTaco with the id 1, the inferred channel
97
+ # name for the single record is "delicious_tacos/1"
98
+ def inferred_channel_name_for_single_record
99
+ "#{self.class.inferred_channel_name}/#{id}"
100
+ end
101
+
102
+ # JSON containing the type of action (:create, :update
103
+ # or :destroy) and the record itself. This is eventually
104
+ # broadcast to the client
105
+ def json(action)
106
+ {
107
+ action: action,
108
+ resource: self
109
+ }.to_json
110
+ end
111
+ end
112
+
113
+ def self.included(receiver)
114
+ receiver.extend ClassMethods
115
+ receiver.send :include, InstanceMethods
116
+ end
117
+ end
118
+
119
+ module Controller
120
+ include Tubesock::Hijack
121
+
122
+ module ClassMethods
123
+
124
+ end
125
+
126
+ module InstanceMethods
127
+ private
128
+
129
+ # The plural name of the resource, inferred from the
130
+ # controller's name. For example, if it's the TacosController,
131
+ # the resources_name will be "tacos". This is used to
132
+ # infer the instance variable name for collections assigned
133
+ # in the controller action
134
+ def resources_name
135
+ controller_name
136
+ end
137
+
138
+ # The singular name of the resource, inferred from the
139
+ # resources_name. This is used to infer the instance
140
+ # variable name for a single record assigned in the controller
141
+ # action
142
+ def resource_name
143
+ resources_name.singularize
144
+ end
145
+
146
+ # Broadcast events to every connected client
147
+ def broadcast(&block)
148
+ # Use hijack to handle sockets
149
+ hijack do |tubesock|
150
+ # Assuming restful controllers, the behavior of
151
+ # this method has to change depending on the action
152
+ # it's being used in
153
+ case action_name
154
+
155
+ # If the controller action is 'index', a collection
156
+ # of records should be broadcast
157
+ when 'index'
158
+ yield
159
+
160
+ # The following code will run if an instance
161
+ # variable with the plural resource name has been
162
+ # assigned in yield. For example, if a
163
+ # TacosController's index action looked something
164
+ # like this:
165
+
166
+ # def index
167
+ # broadcast do
168
+ # @tacos = Taco.all
169
+ # end
170
+ # end
171
+
172
+ # ...then @tacos will be broadcast to all connected
173
+ # clients. The variable name, in this example,
174
+ # has to be "@tacos"
175
+ if instance_variable_get(:"@#{resources_name}")
176
+ redis_thread = Thread.new do
177
+ Redis.new.subscribe resources_name do |on|
178
+ on.message do |channel, message|
179
+ tubesock.send_data message
180
+ end
181
+
182
+ # Broadcast collection to all connected clients
183
+ tubesock.send_data({
184
+ resources: instance_variable_get(:"@#{resources_name}")
185
+ }.to_json)
186
+ end
187
+ end
188
+
189
+ # When client disconnects, kill the thread
190
+ tubesock.onclose do
191
+ redis_thread.kill
192
+ end
193
+ end
194
+
195
+ # If the controller's action name is 'show', a single record
196
+ # should be broadcast
197
+ when 'show'
198
+ yield
199
+
200
+ # The following code will run if an instance variable
201
+ # with the singular resource name has been assigned in
202
+ # yield. For example, if a TacosController's show action
203
+ # looked something like this:
204
+
205
+ # def show
206
+ # broadcast do
207
+ # @taco = Taco.find(params[:id])
208
+ # end
209
+ # end
210
+
211
+ # ...then @taco will be broadcast to all connected clients.
212
+ # The variable name, in this example, has to be "@taco"
213
+ if instance_variable_get(:"@#{resource_name}")
214
+ redis_thread = Thread.new do
215
+ Redis.new.subscribe "#{resources_name}/#{instance_variable_get(:"@#{resource_name}").id}" do |on|
216
+ on.message do |channel, message|
217
+ tubesock.send_data message
218
+ end
219
+
220
+ # Broadcast single resource to all connected clients
221
+ tubesock.send_data({ resource: instance_variable_get(:"@#{resource_name}") }.to_json)
222
+ end
223
+ end
224
+
225
+ # When client disconnects, kill the thread
226
+ tubesock.onclose do
227
+ redis_thread.kill
228
+ end
229
+ end
230
+
231
+ # If the controller's action name is 'create', a record should be
232
+ # created. Before yielding, the params hash has to be prepared
233
+ # with attributes sent to the socket. The actual publishing
234
+ # happens in the model's callback
235
+ when 'create'
236
+ tubesock.onmessage do |m|
237
+ params[resource_name.to_sym] = JSON.parse(m).symbolize_keys
238
+ yield
239
+ end
240
+
241
+ # If the controller's action name is 'update', a record should be
242
+ # updated. Before yielding, the params hash has to be prepared
243
+ # with attributes sent to the socket. The default attributes
244
+ # id, created_at, and updated_at should not be included in params.
245
+ when 'update'
246
+ tubesock.onmessage do |m|
247
+ params[resource_name.to_sym] = JSON.parse(m).except('id', 'created_at', 'updated_at', 'webSocketUrl').symbolize_keys
248
+ yield
249
+ end
250
+
251
+ # For every other controller action, simply wrap whatever is
252
+ # yielded in the tubesock block to execute it in the context
253
+ # of the socket. The delete action is automatically covered
254
+ # by this, and other custom action can be added through this.
255
+ else
256
+ tubesock.onmessage do |m|
257
+ yield
258
+ end
259
+ end
260
+ end
261
+ end
262
+ end
263
+
264
+ def self.included(receiver)
265
+ receiver.extend ClassMethods
266
+ receiver.send :include, InstanceMethods
267
+ end
268
+ end
269
+ end
@@ -0,0 +1,3 @@
1
+ module Entangled
2
+ VERSION = "0.0.1"
3
+ end
metadata ADDED
@@ -0,0 +1,109 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: entangled
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Dennis Charles Hackethal
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-02-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: tubesock
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.2'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rails
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '4.2'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '4.2'
69
+ description: Makes Rails real time through websockets. Check out the JavaScript counterpart
70
+ for the front end.
71
+ email:
72
+ - dennis.hackethal@gmail.com
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - Gemfile
79
+ - LICENSE.txt
80
+ - README.md
81
+ - Rakefile
82
+ - entangled.gemspec
83
+ - lib/entangled.rb
84
+ - lib/entangled/version.rb
85
+ homepage: https://github.com/so-entangled/rails
86
+ licenses:
87
+ - MIT
88
+ metadata: {}
89
+ post_install_message:
90
+ rdoc_options: []
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ requirements: []
104
+ rubyforge_project:
105
+ rubygems_version: 2.4.5
106
+ signing_key:
107
+ specification_version: 4
108
+ summary: Makes Rails real time through websockets.
109
+ test_files: []