entangled 0.0.1 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b7dd7732a7af0fdbc6a792eb242f507817adcfde
4
- data.tar.gz: 0091ccff6cdad24ac350dace118ae33a5331bc5a
3
+ metadata.gz: 5061458f7fad47437868c5e5c445d05c1e25060a
4
+ data.tar.gz: 4a5ea5580591c4d01aba2686a1392d1257bd818f
5
5
  SHA512:
6
- metadata.gz: f64204d586309f97651845b5a642a333a4f88d943608326d84063560b4f2ed180304433a7caa5810cb5ddb2eed9c296ab5e1f4c535ea6d05219215ed02385e93
7
- data.tar.gz: 8573a37652353e3b32807bb653040036b2981927a74fa4565a9b51897f24d37ae3996b4720b689b4a791ad59e6933f624d0fe4d23d17b92e4bd9fee98f23832b
6
+ metadata.gz: eb26c2be5a2e95619dfae839f8929672fdc6e403cef4025212a1a992eac53fab21d81074b04f7ec1fc40a7caee96e83f244d70675da242ee3835d602a20e899a
7
+ data.tar.gz: 83a79396d556515b2a4124e15160fe513f2d12f87ef0ac0236f383a16633cdb01744b228817b4fa1d42a59b72db72d3ef3398f2eeddf1d22d81715ad03bac3c5
data/README.md CHANGED
@@ -1,15 +1,24 @@
1
1
  # Entangled
2
+ Services like Firebase are great because they provide real time data binding between client and server. But they come at a price: You give up control over your backend. Wouldn't it be great to have real time functionality but still keep your beloved Rails backend? That's where Entangled comes in.
2
3
 
3
- TODO: Write a gem description
4
+ Entangled is a layer behind your controllers and models that pushes updates to clients subscribed to certain channels in real time. For example, if you display a list of five messages on a page, if anyone adds a sixth message, everyone who is currently looking at that page will instantly see that sixth message being added to the list.
4
5
 
5
- ## Installation
6
+ The idea is that real time data binding should be the default, not an add-on. Entangled aims at making real time features as easy to implement as possible, while at the same time making your restful controllers thinner.
6
7
 
8
+ ## Installation
7
9
  Add this line to your application's Gemfile:
8
10
 
9
11
  ```ruby
10
12
  gem 'entangled'
11
13
  ```
12
14
 
15
+ Note that Redis and Puma are required as well. Redis is needed to build the channels clients subscribe to, Puma is needed to handle websockets concurrently.
16
+
17
+ ```ruby
18
+ gem 'redis'
19
+ gem 'puma'
20
+ ```
21
+
13
22
  And then execute:
14
23
 
15
24
  $ bundle
@@ -19,13 +28,121 @@ Or install it yourself as:
19
28
  $ gem install entangled
20
29
 
21
30
  ## Usage
31
+ Entangled is needed in three parts of your app. Given the example of a `MessagesController` and a `Message` model for a chat app, you will need:
22
32
 
23
- TODO: Write usage instructions here
33
+ ### Routes
34
+ Add the following to your routes file:
24
35
 
25
- ## Contributing
36
+ ```ruby
37
+ sockets_for :messages
38
+ ```
39
+
40
+ Replace `messages` with your resource name.
41
+
42
+ Under the hood, this creates the following routes:
43
+
44
+ ```ruby
45
+ get '/messages', to: 'messages#index', as: :messages
46
+ get '/messages/create', to: 'messages#create', as: :create_message
47
+ get '/messages/:id', to: 'messages#show', as: :message
48
+ get '/messages/:id/destroy', to: 'messages#destroy', as: :destroy_message
49
+ get '/messages/:id/update', to: 'messages#update', as: :update_message
50
+ ```
51
+
52
+ Note that Websockets don't speak HTTP, so only GET requests are available. That's why these routes deviate slightly from restful routes. Also note that there are no `edit` and `new` actions, since an Entangled controller is only concerned with rendering data, not views.
53
+
54
+ ### Model
55
+ Add the following to the top inside your model (e.g., a `Message` model):
56
+
57
+ ```ruby
58
+ class Message < ActiveRecord::Base
59
+ include Entangled::Model
60
+ entangle
61
+ end
62
+ ```
63
+
64
+ This will create the callbacks needed to push changes to data to all clients who are subscribed. This is essentially where the data binding is set up.
65
+
66
+ ### Controller
67
+ Your controllers will be a little more lightweight than in a standard restful Rails app. A restful-style controller is expected and should look like this:
26
68
 
27
- 1. Fork it ( https://github.com/[my-github-username]/entangled/fork )
69
+ ```ruby
70
+ class MessagesController < ApplicationController
71
+ include Entangled::Controller
72
+
73
+ def index
74
+ broadcast do
75
+ @messages = Message.all
76
+ end
77
+ end
78
+
79
+ def show
80
+ broadcast do
81
+ @message = Message.find(params[:id])
82
+ end
83
+ end
84
+
85
+ def create
86
+ broadcast do
87
+ Message.create(message_params)
88
+ end
89
+ end
90
+
91
+ def update
92
+ broadcast do
93
+ Message.find(params[:id]).update(message_params)
94
+ end
95
+ end
96
+
97
+ def destroy
98
+ broadcast do
99
+ Message.find(params[:id]).destroy
100
+ end
101
+ end
102
+
103
+ private
104
+ def message_params
105
+ # params logic here
106
+ end
107
+ end
108
+ ```
109
+
110
+ Note the following:
111
+
112
+ - All methods are wrapped in a new `broadcast` block needed to send messages to connected clients
113
+ - The `index` method will expect an instance variable with the same name as your controller in the plural form (e.g. `@messages` in a `MessagesController`)
114
+ - The `show` method will expect an instance variable with the singular name of your controller (e.g. `@message` in a `MessagesController`)
115
+ - Instance variables only need to be assigned in `index` and `show` since these are the only methods that should be concerned with sending data to clients. All other methods only publish updates to the data clients are subscribed to through the callbacks added to the model, so no instance variables are needed
116
+ - Data sent to clients arrives as stringified JSON
117
+ - Strong parameters are expected
118
+
119
+ ## Server
120
+ Remember to run Redis whenever you run your server:
121
+
122
+ ```shell
123
+ $ redis-server
124
+ ```
125
+
126
+ Otherwise the channels won't work.
127
+
128
+ ### Database
129
+ Depending on your app's settings, you might have to increase the pool size in your database.yml configuration file, since every new socket will open a new connection to your database.
130
+
131
+ ### The Client
132
+ You will need to configure your client to create Websockets and understand incoming requests on those sockets. If you use Angular for your frontend, you can use [this library](https://github.com/so-entangled/angular). The use of Angular as counterpart of this gem is highly recommended, since its inherent two way data binding complements the real time functionality of this gem nicely.
133
+
134
+ ## Planning Your Infrastructure
135
+ This gem is best used for Rails apps that serve as APIs only and are not concerned with rendering views. A frontend separate from your Rails app, such as Angular with Grunt, is recommended.
136
+
137
+ ## Limitations
138
+ The gem rely's heavily on convention over configuration and currently only works with restful style controllers as shown above. More customization will be available soon.
139
+
140
+ ## Contributing
141
+ 1. Fork it ( https://github.com/so-entangled/rails/fork )
28
142
  2. Create your feature branch (`git checkout -b my-new-feature`)
29
143
  3. Commit your changes (`git commit -am 'Add some feature'`)
30
144
  4. Push to the branch (`git push origin my-new-feature`)
31
145
  5. Create a new Pull Request
146
+
147
+ ## Credits
148
+ Thanks to [Ilias Tsangaris](https://github.com/iliastsangaris) for inspiring the name "Entanglement" based on [Quantum Entanglement](http://en.wikipedia.org/wiki/Quantum_entanglement) where pairs or groups of particles always react to changes as a whole, i.e. changes to one particle will result in immediate change of all particles in the group.
Binary file
Binary file
Binary file
Binary file
Binary file
data/entangled.gemspec CHANGED
@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_development_dependency "bundler", "~> 1.7"
22
22
  spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency 'rspec', '~> 3.2'
23
24
  spec.add_dependency 'tubesock', '~> 0.2'
24
25
  spec.add_dependency 'rails', '~> 4.2'
25
26
  end
data/lib/entangled.rb CHANGED
@@ -1,269 +1,26 @@
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
1
+ require 'entangled/version'
2
+ require 'entangled/model'
3
+ require 'entangled/controller'
4
+ require 'action_dispatch/routing'
5
+ require 'active_support/concern'
6
+
7
+ module ActionDispatch::Routing
8
+ class Mapper
9
+ def sockets_for(resource, &block)
10
+ resources = resource.to_s.underscore.pluralize.to_sym
11
+ resource = resource.to_s.underscore.singularize.to_sym
12
+
13
+ get :"/#{resources}", to: "#{resources}#index", as: resources
14
+ get :"/#{resources}/create", to: "#{resources}#create", as: :"create_#{resource}"
15
+ get :"/#{resources}/:id", to: "#{resources}#show", as: resource
16
+ get :"/#{resources}/:id/destroy", to: "#{resources}#destroy", as: :"destroy_#{resource}"
17
+ get :"/#{resources}/:id/update", to: "#{resources}#update", as: :"update_#{resource}"
18
+
19
+ if block_given?
20
+ namespace resources do
21
+ yield
22
+ end
23
+ end
24
+ end
268
25
  end
269
26
  end
@@ -0,0 +1,154 @@
1
+ require 'tubesock'
2
+
3
+ module Entangled
4
+ module Controller
5
+ include Tubesock::Hijack
6
+
7
+ module ClassMethods
8
+
9
+ end
10
+
11
+ module InstanceMethods
12
+ private
13
+
14
+ # The plural name of the resource, inferred from the
15
+ # controller's name. For example, if it's the TacosController,
16
+ # the resources_name will be "tacos". This is used to
17
+ # infer the instance variable name for collections assigned
18
+ # in the controller action
19
+ def resources_name
20
+ controller_name
21
+ end
22
+
23
+ # The singular name of the resource, inferred from the
24
+ # resources_name. This is used to infer the instance
25
+ # variable name for a single record assigned in the controller
26
+ # action
27
+ def resource_name
28
+ resources_name.singularize
29
+ end
30
+
31
+ # Broadcast events to every connected client
32
+ def broadcast(&block)
33
+ # Use hijack to handle sockets
34
+ hijack do |tubesock|
35
+ # Assuming restful controllers, the behavior of
36
+ # this method has to change depending on the action
37
+ # it's being used in
38
+ case action_name
39
+
40
+ # If the controller action is 'index', a collection
41
+ # of records should be broadcast
42
+ when 'index'
43
+ yield
44
+
45
+ # The following code will run if an instance
46
+ # variable with the plural resource name has been
47
+ # assigned in yield. For example, if a
48
+ # TacosController's index action looked something
49
+ # like this:
50
+
51
+ # def index
52
+ # broadcast do
53
+ # @tacos = Taco.all
54
+ # end
55
+ # end
56
+
57
+ # ...then @tacos will be broadcast to all connected
58
+ # clients. The variable name, in this example,
59
+ # has to be "@tacos"
60
+ if instance_variable_get(:"@#{resources_name}")
61
+ redis_thread = Thread.new do
62
+ Redis.new.subscribe resources_name do |on|
63
+ on.message do |channel, message|
64
+ tubesock.send_data message
65
+ end
66
+
67
+ # Broadcast collection to all connected clients
68
+ tubesock.send_data({
69
+ resources: instance_variable_get(:"@#{resources_name}")
70
+ }.to_json)
71
+ end
72
+ end
73
+
74
+ # When client disconnects, kill the thread
75
+ tubesock.onclose do
76
+ redis_thread.kill
77
+ end
78
+ end
79
+
80
+ # If the controller's action name is 'show', a single record
81
+ # should be broadcast
82
+ when 'show'
83
+ yield
84
+
85
+ # The following code will run if an instance variable
86
+ # with the singular resource name has been assigned in
87
+ # yield. For example, if a TacosController's show action
88
+ # looked something like this:
89
+
90
+ # def show
91
+ # broadcast do
92
+ # @taco = Taco.find(params[:id])
93
+ # end
94
+ # end
95
+
96
+ # ...then @taco will be broadcast to all connected clients.
97
+ # The variable name, in this example, has to be "@taco"
98
+ if instance_variable_get(:"@#{resource_name}")
99
+ redis_thread = Thread.new do
100
+ Redis.new.subscribe "#{resources_name}/#{instance_variable_get(:"@#{resource_name}").id}" do |on|
101
+ on.message do |channel, message|
102
+ tubesock.send_data message
103
+ end
104
+
105
+ # Broadcast single resource to all connected clients
106
+ tubesock.send_data({ resource: instance_variable_get(:"@#{resource_name}") }.to_json)
107
+ end
108
+ end
109
+
110
+ # When client disconnects, kill the thread
111
+ tubesock.onclose do
112
+ redis_thread.kill
113
+ end
114
+ end
115
+
116
+ # If the controller's action name is 'create', a record should be
117
+ # created. Before yielding, the params hash has to be prepared
118
+ # with attributes sent to the socket. The actual publishing
119
+ # happens in the model's callback
120
+ when 'create'
121
+ tubesock.onmessage do |m|
122
+ params[resource_name.to_sym] = JSON.parse(m).symbolize_keys
123
+ yield
124
+ end
125
+
126
+ # If the controller's action name is 'update', a record should be
127
+ # updated. Before yielding, the params hash has to be prepared
128
+ # with attributes sent to the socket. The default attributes
129
+ # id, created_at, and updated_at should not be included in params.
130
+ when 'update'
131
+ tubesock.onmessage do |m|
132
+ params[resource_name.to_sym] = JSON.parse(m).except('id', 'created_at', 'updated_at', 'webSocketUrl').symbolize_keys
133
+ yield
134
+ end
135
+
136
+ # For every other controller action, simply wrap whatever is
137
+ # yielded in the tubesock block to execute it in the context
138
+ # of the socket. The delete action is automatically covered
139
+ # by this, and other custom action can be added through this.
140
+ else
141
+ tubesock.onmessage do |m|
142
+ yield
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
148
+
149
+ def self.included(receiver)
150
+ receiver.extend ClassMethods
151
+ receiver.send :include, InstanceMethods
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,115 @@
1
+ module Entangled
2
+ module Model
3
+ module ClassMethods
4
+ # Create after_ callbacks for options
5
+ def entangle(options = {})
6
+
7
+ # If :only is specified, the options can either
8
+ # be an array or a symbol
9
+ if options[:only].present?
10
+
11
+ # If it is a symbol, something like only: :create
12
+ # was passed in, and we need to create a hook
13
+ # only for that one option
14
+ if options[:only].is_a?(Symbol)
15
+ create_hook options[:only]
16
+
17
+ # If it is an array, something like only: [:create, :update]
18
+ # was passed in, and we need to create hook for each
19
+ # of these options
20
+ elsif options[:only].is_a?(Array)
21
+ options[:only].each { |option| create_hook option }
22
+ end
23
+
24
+ # Instead of :only, :except can be specified; similarly,
25
+ # the options can either be an array or a symbol
26
+ elsif options[:except].present?
27
+
28
+ # If it is a symbol, it has to be taken out of the default
29
+ # options. A callback has to be defined for each of the
30
+ # remaining options
31
+ if options[:except].is_a?(Symbol)
32
+ (default_options - [options[:except]]).each do |option|
33
+ create_hook option
34
+ end
35
+
36
+ # If it is an array, it also has to be taen out of the
37
+ # default options. A callback then also has to be defined
38
+ # for each of the remaining options
39
+ elsif options[:except].is_a?(Array)
40
+ (default_options - options[:except]).each do |option|
41
+ create_hook option
42
+ end
43
+ end
44
+ else
45
+
46
+ # If neither :only nor :except is specified, simply create
47
+ # a callback for each default option
48
+ default_options.each { |option| create_hook option }
49
+ end
50
+ end
51
+
52
+ # By default, model updates will be published after_create,
53
+ # after_update, and after_destroy. This behavior can be
54
+ # modified by passing :only or :except options to the
55
+ # entangle class method
56
+ def default_options
57
+ [:create, :update, :destroy]
58
+ end
59
+
60
+ # The inferred channel name. For example, if the class name
61
+ # is DeliciousTaco, the inferred channel name is "delicious_tacos"
62
+ def inferred_channel_name
63
+ name.underscore.pluralize
64
+ end
65
+
66
+ # Creates callbacks in the extented model
67
+ def create_hook(name)
68
+ send :"after_#{name}", proc { publish(name) }
69
+ end
70
+ end
71
+
72
+ module InstanceMethods
73
+ private
74
+
75
+ # Publishes to client. Whoever is subscribed
76
+ # to the model's channel or the record's channel
77
+ # gets the message
78
+ def publish(action)
79
+ Redis.new.publish(
80
+ self.class.inferred_channel_name,
81
+ json(action)
82
+ )
83
+
84
+ Redis.new.publish(
85
+ inferred_channel_name_for_single_record,
86
+ json(action)
87
+ )
88
+ end
89
+
90
+ # The inferred channel name for a single record
91
+ # containing the inferred channel name from the class
92
+ # and the record's id. For example, if it's a
93
+ # DeliciousTaco with the id 1, the inferred channel
94
+ # name for the single record is "delicious_tacos/1"
95
+ def inferred_channel_name_for_single_record
96
+ "#{self.class.inferred_channel_name}/#{id}"
97
+ end
98
+
99
+ # JSON containing the type of action (:create, :update
100
+ # or :destroy) and the record itself. This is eventually
101
+ # broadcast to the client
102
+ def json(action)
103
+ {
104
+ action: action,
105
+ resource: self
106
+ }.to_json
107
+ end
108
+ end
109
+
110
+ def self.included(receiver)
111
+ receiver.extend ClassMethods
112
+ receiver.send :include, InstanceMethods
113
+ end
114
+ end
115
+ end
@@ -1,3 +1,3 @@
1
1
  module Entangled
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.6"
3
3
  end
@@ -0,0 +1,2 @@
1
+ require 'rspec'
2
+ require 'entangled'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: entangled
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dennis Charles Hackethal
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-12 00:00:00.000000000 Z
11
+ date: 2015-02-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.2'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.2'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: tubesock
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -79,9 +93,17 @@ files:
79
93
  - LICENSE.txt
80
94
  - README.md
81
95
  - Rakefile
96
+ - entangled-0.0.1.gem
97
+ - entangled-0.0.2.gem
98
+ - entangled-0.0.3.gem
99
+ - entangled-0.0.4.gem
100
+ - entangled-0.0.5.gem
82
101
  - entangled.gemspec
83
102
  - lib/entangled.rb
103
+ - lib/entangled/controller.rb
104
+ - lib/entangled/model.rb
84
105
  - lib/entangled/version.rb
106
+ - spec/spec_helper.rb
85
107
  homepage: https://github.com/so-entangled/rails
86
108
  licenses:
87
109
  - MIT
@@ -102,8 +124,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
102
124
  version: '0'
103
125
  requirements: []
104
126
  rubyforge_project:
105
- rubygems_version: 2.4.5
127
+ rubygems_version: 2.2.2
106
128
  signing_key:
107
129
  specification_version: 4
108
130
  summary: Makes Rails real time through websockets.
109
- test_files: []
131
+ test_files:
132
+ - spec/spec_helper.rb