action_cable_notifications 0.1.14 → 0.1.18

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,192 @@
1
+ require 'action_cable_notifications/channel_actions.rb'
2
+
3
+ module ActionCableNotifications
4
+ module Channel
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ # class variables
9
+ class_attribute :ActionCableNotifications
10
+
11
+ self.ActionCableNotifications = {}
12
+ end
13
+
14
+ #
15
+ # Public methods
16
+ ################################################################
17
+
18
+ #
19
+ # Process actions sent from the client
20
+ #
21
+ # @param [Hash] data Contains command to be executed and its parameters
22
+ # {
23
+ # "collection": "model.model_name.collection"
24
+ # "command": "fetch"
25
+ # "params": {}
26
+ # }
27
+ def action(data)
28
+ data.deep_symbolize_keys!
29
+
30
+ channel_options = self.ActionCableNotifications[data[:collection]]
31
+ model = channel_options[:model]
32
+ broadcasting = channel_options[:broadcasting]
33
+ model_options = model.ActionCableNotificationsOptions[broadcasting]
34
+
35
+ params = {
36
+ model: model,
37
+ model_options: model_options,
38
+ params: data[:params]
39
+ }
40
+
41
+ case data[:command]
42
+ when "fetch"
43
+ fetch(params)
44
+ when "create"
45
+ create(params)
46
+ when "update"
47
+ update(params)
48
+ when "destroy"
49
+ destroy(params)
50
+ end
51
+ end
52
+
53
+ def initialize(*args)
54
+ super
55
+ @collections = {}
56
+ end
57
+
58
+ def subscribed
59
+ # XXX Check if this is new connection or a reconection of a
60
+ # previously active client
61
+ end
62
+
63
+ def unsubscribed
64
+ stop_all_streams
65
+ end
66
+
67
+ #
68
+ # Private methods
69
+ ################################################################
70
+
71
+ private
72
+
73
+ include ActionCableNotifications::Channel::Actions
74
+
75
+ #
76
+ # Streams notification for ActiveRecord model changes
77
+ #
78
+ # @param [ActiveRecord::Base] model Model to watch for changes
79
+ # @param [Hash] options Streaming options
80
+ #
81
+ def stream_notifications_for(model, options = {})
82
+ # Default options
83
+ options = {
84
+ }.merge(options)
85
+
86
+ # These options cannot be overridden
87
+ options[:model] = model
88
+ options[:channel] = self
89
+ options[:broadcasting] = model.model_name.collection
90
+
91
+ # Sets channel options
92
+ self.ActionCableNotifications[options[:broadcasting]] = options
93
+
94
+ # Checks if model already includes notification callbacks
95
+ if !model.respond_to? :ActionCableNotificationsOptions
96
+ model.send('include', ActionCableNotifications::Model)
97
+ end
98
+
99
+ # Sets broadcast options if they are not already present in the model
100
+ if not model.ActionCableNotificationsOptions.key? options[:broadcasting]
101
+ model.broadcast_notifications_from options[:broadcasting], options
102
+ end
103
+
104
+ # Start streaming
105
+ stream_from options[:broadcasting], coder: ActiveSupport::JSON do |packet|
106
+ transmit_packet(packet)
107
+ end
108
+
109
+ end
110
+
111
+ #
112
+ # Transmits packets to connected client
113
+ #
114
+ # @param [Hash] packet Packet with changes notifications
115
+ #
116
+ def transmit_packet(packet)
117
+ packet = packet.as_json.deep_symbolize_keys!
118
+ if update_cache(packet)
119
+ transmit packet
120
+ end
121
+ end
122
+
123
+ #
124
+ # Updates server side cache of client side collections
125
+ # XXX compute cache diff before sending to clients
126
+ #
127
+ def update_cache(packet)
128
+ updated = false
129
+
130
+ # Check if collection already exists
131
+ new_collection = false
132
+ if @collections[packet[:collection]].nil?
133
+ @collections[packet[:collection]] = []
134
+ new_collection = true
135
+ end
136
+
137
+ collection = @collections[packet[:collection]]
138
+
139
+ case packet[:msg]
140
+ when 'upsert_many'
141
+ if new_collection
142
+ packet[:data].each do |record|
143
+ collection.push record
144
+ end
145
+ updated = true
146
+ else
147
+ packet[:data].each do |record|
148
+ current_record = collection.find{|c| c[:id]==record[:id]}
149
+ if current_record
150
+ new_record = current_record.merge(record)
151
+ if new_record != current_record
152
+ current_record.merge!(record)
153
+ updated = true
154
+ end
155
+ else
156
+ collection.push record
157
+ updated = true
158
+ end
159
+ end
160
+ end
161
+
162
+ when 'create'
163
+ record = collection.find{|c| c[:id]==packet[:id]}
164
+ if !record
165
+ @collections[packet[:collection]].push packet[:data]
166
+ updated = true
167
+ end
168
+
169
+ when 'update'
170
+ record = @collections[packet[:collection]].find{|c| c[:id]==packet[:id]}
171
+ if record
172
+ record.merge!(packet[:data])
173
+ updated = true
174
+ end
175
+
176
+ when 'destroy'
177
+ index = @collections[packet[:collection]].find_index{|c| c.id==packet[:id]}
178
+ if index
179
+ @collections[packet[:collection]].delete_at(index)
180
+ updated = true
181
+ end
182
+
183
+ else
184
+ updated = true
185
+ end
186
+
187
+ updated
188
+
189
+ end
190
+
191
+ end
192
+ end
@@ -0,0 +1,144 @@
1
+ module ActionCableNotifications
2
+ module Channel
3
+ module Actions
4
+ #
5
+ # Fetch records from the DB and send them to the client
6
+ #
7
+ # @param [Hash] selector Specifies conditions that the registers should match
8
+ #
9
+ def fetch(data)
10
+ # XXX: Check if the client is allowed to call the method
11
+
12
+ params = data[:params] || {}
13
+
14
+ # Get results using provided parameters and model configured scope
15
+ results = data[:model].
16
+ select(params[:select]).
17
+ limit(params[:limit]).
18
+ where(params[:where] || {}).
19
+ scoped_collection(data[:model_options][:scope]).
20
+ to_a() rescue []
21
+
22
+ response = { collection: data[:model].model_name.collection,
23
+ msg: 'upsert_many',
24
+ data: results
25
+ }
26
+
27
+ # Send data to the client
28
+ transmit_packet response
29
+ end
30
+
31
+ #
32
+ # Creates one record in the DB
33
+ #
34
+ def create(data)
35
+ # XXX: Check if the client is allowed to call the method
36
+
37
+ params = data[:params] || {}
38
+ fields = params[:fields].except(:id)
39
+
40
+ error = nil
41
+
42
+ if fields.present?
43
+ begin
44
+ record = data[:model].create(fields)
45
+
46
+ if !record.persisted?
47
+ error = true
48
+ end
49
+ rescue Exception => e
50
+ error = e.message
51
+ end
52
+ else
53
+ error = "No fields were provided"
54
+ end
55
+
56
+ if error
57
+ response = {
58
+ collection: data[:model].model_name.collection,
59
+ msg: 'error',
60
+ cmd: 'create',
61
+ error: error || record.errors.full_messages
62
+ }
63
+
64
+ # Send error notification to the client
65
+ transmit response
66
+ end
67
+
68
+ end
69
+
70
+
71
+ #
72
+ # Update one record from the DB
73
+ #
74
+ def update(data)
75
+ # XXX: Check if the client is allowed to call the method
76
+
77
+ params = data[:params] || {}
78
+
79
+ record = data[:model].find(params[:id]) rescue nil
80
+
81
+ error = nil
82
+
83
+ if record.present?
84
+ begin
85
+ record.update_attributes(params[:fields])
86
+ rescue Exception => e
87
+ error = e.message
88
+ end
89
+ else
90
+ error = "There is no record with id: #{params[:id]}"
91
+ end
92
+
93
+ if error
94
+ response = {
95
+ collection: data[:model].model_name.collection,
96
+ msg: 'error',
97
+ cmd: 'update',
98
+ error: error || record.errors.full_messages
99
+ }
100
+
101
+ # Send error notification to the client
102
+ transmit response
103
+ end
104
+
105
+ end
106
+
107
+ #
108
+ # Remove records from the DB
109
+ #
110
+ def destroy(data)
111
+ # XXX: Check if the client is allowed to call the method
112
+
113
+ params = data[:params] || {}
114
+
115
+ record = data[:model].find(params[:id]) rescue nil
116
+
117
+ error = nil
118
+
119
+ if record.present?
120
+ begin
121
+ record.destroy
122
+ rescue Exception => e
123
+ error = e.message
124
+ end
125
+ else
126
+ error = "There is no record with id: #{params[:id]}"
127
+ end
128
+
129
+ if error
130
+ response = { collection: data[:model].model_name.collection,
131
+ msg: 'error',
132
+ cmd: 'destroy',
133
+ error: error || record.errors.full_messages
134
+ }
135
+
136
+ # Send error notification to the client
137
+ transmit response
138
+ end
139
+
140
+ end
141
+
142
+ end
143
+ end
144
+ end
@@ -1,6 +1,6 @@
1
1
  require 'lodash-rails'
2
- require 'action_cable_notifications/callbacks.rb'
3
- require 'action_cable_notifications/streams.rb'
2
+ require 'action_cable_notifications/model.rb'
3
+ require 'action_cable_notifications/channel.rb'
4
4
 
5
5
  module ActionCableNotifications
6
6
  class Engine < ::Rails::Engine
@@ -1,5 +1,5 @@
1
1
  module ActionCableNotifications
2
- module Callbacks
2
+ module Model
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
@@ -10,7 +10,7 @@ module ActionCableNotifications
10
10
  # Register Callbacks
11
11
  after_update :notify_update
12
12
  after_create :notify_create
13
- before_destroy :notify_destroy
13
+ after_destroy :notify_destroy
14
14
  end
15
15
 
16
16
  module ClassMethods
@@ -40,7 +40,8 @@ module ActionCableNotifications
40
40
  # @return [ActiveRecordRelation] Results fetched from the database
41
41
  #
42
42
  def scoped_collection ( scope = :all )
43
- Array(scope).inject(self) {|o, a| o.try(*a)}
43
+ scope = scope.to_a if scope.is_a? Hash
44
+ Array(scope).inject(self) {|o, a| o.try(*a)} rescue nil
44
45
  end
45
46
 
46
47
  #
@@ -59,7 +60,7 @@ module ActionCableNotifications
59
60
  if options.present?
60
61
  {
61
62
  collection: self.model_name.collection,
62
- msg: 'update_many',
63
+ msg: 'upsert_many',
63
64
  data: self.scoped_collection(options[:scope])
64
65
  }
65
66
  end
@@ -94,21 +95,23 @@ module ActionCableNotifications
94
95
  changes[k] = v[1]
95
96
  end
96
97
 
97
- self.ActionCableNotificationsOptions.each do |broadcasting, options|
98
- if options[:actions].include? :update
99
- # Checks if record is within scope before broadcasting
100
- if self.class.scoped_collection(options[:scope]).where(id: self.id)
101
- # XXX: Performance required. For small data sets this should be
102
- # fast enough, but for large data sets this could be slow. As
103
- # clients should have a limited subset of the dataset loaded at a
104
- # time, caching the results already sent to clients in server memory
105
- # should not have a big impact in memory usage but can improve
106
- # performance for large data sets where only a sub
107
- ActionCable.server.broadcast broadcasting,
108
- collection: self.model_name.collection,
109
- msg: 'update',
110
- id: self.id,
111
- data: changes
98
+ if !changes.empty?
99
+ self.ActionCableNotificationsOptions.each do |broadcasting, options|
100
+ if options[:actions].include? :update
101
+ # Checks if record is within scope before broadcasting
102
+ if self.class.scoped_collection(options[:scope]).where(id: self.id)
103
+ # XXX: Performance required. For small data sets this should be
104
+ # fast enough, but for large data sets this could be slow. As
105
+ # clients should have a limited subset of the dataset loaded at a
106
+ # time, caching the results already sent to clients in server memory
107
+ # should not have a big impact in memory usage but can improve
108
+ # performance for large data sets where only a sub
109
+ ActionCable.server.broadcast broadcasting,
110
+ collection: self.model_name.collection,
111
+ msg: 'update',
112
+ id: self.id,
113
+ data: changes
114
+ end
112
115
  end
113
116
  end
114
117
  end
@@ -1,3 +1,3 @@
1
1
  module ActionCableNotifications
2
- VERSION = '0.1.14'
2
+ VERSION = '0.1.18'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: action_cable_notifications
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.14
4
+ version: 0.1.18
5
5
  platform: ruby
6
6
  authors:
7
7
  - ByS Sistemas de Control
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-09-29 00:00:00.000000000 Z
11
+ date: 2016-10-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -58,6 +58,34 @@ dependencies:
58
58
  - - ">="
59
59
  - !ruby/object:Gem::Version
60
60
  version: '0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: pry
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: pry-byebug
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
61
89
  description: Rails engine that provides Automatic realtime notification broadcast
62
90
  for ActiveRecord models changes using Action Cable and websockets
63
91
  email:
@@ -76,6 +104,7 @@ files:
76
104
  - app/assets/javascripts/action_cable_notifications/collection.coffee
77
105
  - app/assets/javascripts/action_cable_notifications/default_callbacks.coffee
78
106
  - app/assets/javascripts/action_cable_notifications/store.coffee
107
+ - app/assets/javascripts/action_cable_notifications/tracker.js
79
108
  - app/assets/stylesheets/action_cable_notifications/application.css
80
109
  - app/controllers/action_cable_notifications/application_controller.rb
81
110
  - app/helpers/action_cable_notifications/application_helper.rb
@@ -85,9 +114,10 @@ files:
85
114
  - app/views/layouts/action_cable_notifications/application.html.erb
86
115
  - config/routes.rb
87
116
  - lib/action_cable_notifications.rb
88
- - lib/action_cable_notifications/callbacks.rb
117
+ - lib/action_cable_notifications/channel.rb
118
+ - lib/action_cable_notifications/channel_actions.rb
89
119
  - lib/action_cable_notifications/engine.rb
90
- - lib/action_cable_notifications/streams.rb
120
+ - lib/action_cable_notifications/model.rb
91
121
  - lib/action_cable_notifications/version.rb
92
122
  - lib/tasks/action_cable_notifications_tasks.rake
93
123
  homepage: https://github.com/bys-control/action_cable_notifications