action_cable_notifications 0.1.14 → 0.1.18

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.
@@ -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