action_cable_notifications 0.1.27 → 0.1.35

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
- SHA1:
3
- metadata.gz: bbf5459391f70a5ea26fcbe49d73d658ee0018e1
4
- data.tar.gz: 139cee45492e3dbac434fa59428784ee779ffc14
2
+ SHA256:
3
+ metadata.gz: 791d0cda5f750319e778b0f7e0c9c656721d4ad4a3b2dcc6bf7377a88e7d10d8
4
+ data.tar.gz: '085b106ff16bde873da19f9c0d800cb5dfcdb184c4bf6607c60872d156fdf0a2'
5
5
  SHA512:
6
- metadata.gz: f81b284758a85fb93d2c189ae4ec8b4f642ba4855309534017e0f3def93260851545d35a23a4ea5a1ba125c58c331c9df50cc29d9fa939e563786b272a1c9164
7
- data.tar.gz: dbdf1d02cc3bc63458ddc7e460f8e47a48d58031a2b6008ca22943b1fc1a84fbda856962241dba5e43ad3b8773a1d018a4954aaf2563248935758f9b4641f885
6
+ metadata.gz: 0d4270f9e8441719683eda7d7874b8f984c108999bcefa5ecf3df371a753c4172f53ea91ba441aa8147cf530126aeef5677d1168b675cd40a8e18e37ec1bcc66
7
+ data.tar.gz: 8f4c6acbecb415cd2e9f018ccc0fc17d1187a99ddd9cb77b00fe9aa5645378d693bda60a666707e21c5c6a3e538d5b4752d4f25df2f451b5663d7a5b07f13188
data/README.md CHANGED
@@ -19,9 +19,17 @@ class TestChannel < ApplicationCable::Channel
19
19
  include ActionCableNotifications::Channel
20
20
 
21
21
  def subscribed
22
+ # Config streaming for Customer model with default options
22
23
  stream_notifications_for Customer
23
24
  # Can have more than one ActiveRecord model streaming per channel
24
- stream_notifications_for Invoice, scope: { limit: 5, order: :id, select: [:id, :customer_id, :seller_id, :amount] }
25
+ stream_notifications_for Invoice,
26
+ model_options: {
27
+ scope: {
28
+ limit: 5,
29
+ order: :id,
30
+ select: [:id, :customer_id, :seller_id, :amount]
31
+ }
32
+ }
25
33
  end
26
34
 
27
35
  def unsubscribed
@@ -40,10 +48,14 @@ stream_notifications_for(model, options = {}, &block)
40
48
  * options: **(Hash)** - Options to be used for configuracion. Default options are:
41
49
  ```ruby
42
50
  {
43
- actions: [:create, :update, :destroy], # Controller actions to attach to
44
- broadcasting: model.model_name.collection, # Name of the pubsub stream
45
- params: params, # Params sent during subscription
46
- scope: :all # Default collection scope. Can be an Array or Hash
51
+ publication: model.model_name.collection, # Name of the pubsub stream
52
+ params: params, # Params sent when client subscribes
53
+ cache: false, # Turn off server-side cache of client-side data
54
+ model_options: {
55
+ actions: [:create, :update, :destroy], # Model callbacks to attach to
56
+ scope: :all # Default collection scope. Can be an ,Array or Hash
57
+ track_scope_changes: true # During model updates, checks if the changes affect scope inclusion of the resulting record
58
+ }
47
59
  }
48
60
  ```
49
61
 
@@ -4,7 +4,7 @@ class CableNotifications.Collection
4
4
  upstream = (command, params={}) ->
5
5
  if @sync
6
6
  cmd =
7
- collection: @tableName
7
+ publication: @publication
8
8
  command: command
9
9
  params: params
10
10
 
@@ -35,7 +35,7 @@ class CableNotifications.Collection
35
35
 
36
36
  # Public methods
37
37
  #######################################
38
- constructor: (@store, @name, @tableName, @callbacks) ->
38
+ constructor: (@store, @name, @publication=name, callbacks) ->
39
39
  # Data storage array
40
40
  @data = []
41
41
  # Channel used to sync with upstream collection
@@ -47,8 +47,21 @@ class CableNotifications.Collection
47
47
  # Stores records that needs to be tracked when inserted into the collection
48
48
  @trackedRecords = []
49
49
 
50
+ @callbacks = {}
51
+
52
+ @observeChanges(callbacks)
53
+
50
54
  @callbacks?.initialize?.call(this)
51
55
 
56
+ # Update the callback stack for collection
57
+ observeChanges: (new_callbacks) ->
58
+ _.each(new_callbacks, (cb, name) =>
59
+ old_cb = @callbacks?[name]
60
+ @callbacks[name] = () ->
61
+ cb.apply(this, arguments)
62
+ old_cb?.apply(this, arguments)
63
+ )
64
+
52
65
  # Sync collection to ActionCable Channel
53
66
  syncToChannel: (@channel) ->
54
67
  @sync = true
@@ -90,7 +103,7 @@ class CableNotifications.Collection
90
103
  record
91
104
 
92
105
  # Creates a new record
93
- create: (fields={}) ->
106
+ create: (fields={}, options={}) ->
94
107
  record = _.find(@data, {id: fields.id})
95
108
  if record
96
109
  console.warn("[create] Not expected to find an existing record with id #{fields.id}")
@@ -105,8 +118,9 @@ class CableNotifications.Collection
105
118
  if !@sync
106
119
  @data.push (fields)
107
120
  @callbacks?.create?.call(this, fields)
108
-
109
- upstream.call(this, "create", {fields: fields})
121
+ @callbacks?.changed?.call(this, @data) unless options.batching
122
+ else
123
+ upstream.call(this, "create", {fields: fields})
110
124
  fields
111
125
 
112
126
  # Update an existing record
@@ -114,23 +128,24 @@ class CableNotifications.Collection
114
128
  record = _.find(@data, selector)
115
129
  if !record
116
130
  if options.upsert
117
- @create(fields)
131
+ @create(fields, options)
118
132
  else
119
133
  console.warn("[update] Couldn't find a matching record:", selector)
120
134
  else
121
135
  if !@sync
122
136
  @callbacks?.update?.call(this, selector, fields, options)
123
137
  _.extend(record, fields)
124
-
125
- upstream.call(this, "update", {id: record.id, fields: fields})
138
+ @callbacks?.changed?.call(this, @data) unless options.batching
139
+ else
140
+ upstream.call(this, "update", {id: record.id, fields: fields})
126
141
  record
127
142
 
128
143
  # Update an existing record or inserts a new one if there is no match
129
- upsert: (selector={}, fields) ->
130
- @update(selector, fields, {upsert: true})
144
+ upsert: (selector={}, fields, options={}) ->
145
+ @update(selector, fields, _.extend(options, {upsert: true}))
131
146
 
132
147
  # Destroy an existing record
133
- destroy: (selector={}) ->
148
+ destroy: (selector={}, options={}) ->
134
149
  index = _.findIndex(@data, selector)
135
150
  if index < 0
136
151
  console.warn("[destroy] Couldn't find a matching record:", selector)
@@ -139,6 +154,7 @@ class CableNotifications.Collection
139
154
  if !@sync
140
155
  @data.splice(index, 1)
141
156
  @callbacks?.destroy?.call(this, selector)
142
-
143
- upstream.call(this, "destroy", {id: record.id})
157
+ @callbacks?.changed?.call(this, @data) unless options.batching
158
+ else
159
+ upstream.call(this, "destroy", {id: record.id})
144
160
  record
@@ -15,12 +15,15 @@ class CableNotifications.Store.DefaultCallbacks
15
15
  collection.update({id: packet.id}, packet.data)
16
16
 
17
17
  update_many: (packet, collection) ->
18
- _.each packet.data, (fields) ->
19
- collection.update({id: fields.id}, fields)
18
+ _.each packet.data, (fields, index, records) ->
19
+ collection.update({id: fields.id}, fields, {batching: index<records.length-1})
20
+
21
+ upsert: (packet, collection) ->
22
+ collection.upsert({id: packet.id}, packet.data)
20
23
 
21
24
  upsert_many: (packet, collection) ->
22
- _.each packet.data, (fields) ->
23
- collection.upsert({id: fields.id}, fields)
25
+ _.each packet.data, (fields, index, records) ->
26
+ collection.upsert({id: fields.id}, fields, {batching: index<records.length-1})
24
27
 
25
28
  destroy: (packet, collection) ->
26
29
  collection.destroy({id: packet.id})
@@ -16,10 +16,10 @@ class CableNotifications.Store
16
16
  # Then call original callback
17
17
  packetReceived = (channelInfo) ->
18
18
  (packet) ->
19
- if packet?.collection
19
+ if packet?.publication
20
20
  # Search if there is a collection in this Store that receives packets from the server
21
21
  collection = _.find(channelInfo.collections,
22
- {tableName: packet.collection})
22
+ {publication: packet.publication})
23
23
  if collection
24
24
  dispatchPacket.call(this, packet, collection)
25
25
  channelInfo.callbacks.received?.apply(channelInfo.channel, arguments)
@@ -56,12 +56,11 @@ class CableNotifications.Store
56
56
  #######################################
57
57
 
58
58
  # Register a new collection
59
- registerCollection: (name, channel, tableName, actions) ->
60
- tableName = name unless tableName
59
+ registerCollection: (name, channel, publication=name, actions) ->
61
60
  if @collections[name]
62
61
  console.warn "[registerCollection]: Collection '#{name}' already exists"
63
62
  else
64
- @collections[name] = new CableNotifications.Collection(this, name, tableName, actions)
63
+ @collections[name] = new CableNotifications.Collection(this, name, publication, actions)
65
64
  if channel
66
65
  @syncToChannel(channel, @collections[name])
67
66
 
@@ -6,10 +6,7 @@ module ActionCableNotifications
6
6
  extend ActiveSupport::Concern
7
7
 
8
8
  included do
9
- # class variables
10
- class_attribute :ActionCableNotifications
11
9
 
12
- self.ActionCableNotifications = {}
13
10
  end
14
11
 
15
12
  #
@@ -21,42 +18,46 @@ module ActionCableNotifications
21
18
  #
22
19
  # @param [Hash] data Contains command to be executed and its parameters
23
20
  # {
24
- # "collection": "model.model_name.collection"
21
+ # "publication": "model.model_name.name"
25
22
  # "command": "fetch"
26
23
  # "params": {}
27
24
  # }
28
25
  def action(data)
29
26
  data.deep_symbolize_keys!
30
27
 
31
- channel_options = self.ActionCableNotifications[data[:collection]]
28
+ publication = data[:publication]
29
+ channel_options = @ChannelPublications[publication]
32
30
  if channel_options
33
31
  model = channel_options[:model]
34
- broadcasting = channel_options[:broadcasting]
35
- model_options = model.ActionCableNotificationsOptions[broadcasting]
32
+ model_options = model.ChannelPublications[publication]
33
+ params = data[:params]
34
+ command = data[:command]
36
35
 
37
- params = {
36
+ action_params = {
37
+ publication: publication,
38
38
  model: model,
39
39
  model_options: model_options,
40
- params: data[:params],
41
- command: data[:command]
40
+ options: channel_options,
41
+ params: params,
42
+ command: command
42
43
  }
43
44
 
44
- case data[:command]
45
+ case command
45
46
  when "fetch"
46
- fetch(params)
47
+ fetch(action_params)
47
48
  when "create"
48
- create(params)
49
+ create(action_params)
49
50
  when "update"
50
- update(params)
51
+ update(action_params)
51
52
  when "destroy"
52
- destroy(params)
53
+ destroy(action_params)
53
54
  end
54
55
  else
55
56
  response = {
56
- collection: data[:collection],
57
+ publication: publication,
57
58
  msg: 'error',
58
- command: data[:command],
59
- error: "Collection '#{data[:collection]}' does not exist."
59
+ command: command,
60
+ error: "Stream for publication '#{publication}' does not exist in channel '#{self.channel_name}'."
60
61
  }
61
62
 
62
63
  # Send error notification to the client
@@ -65,8 +66,9 @@ module ActionCableNotifications
65
66
  end
66
67
 
67
68
  def initialize(*args)
68
- super
69
69
  @collections = {}
70
+ @ChannelPublications = {}
71
+ super
70
72
  end
71
73
 
72
74
  def subscribed
@@ -94,35 +96,45 @@ module ActionCableNotifications
94
96
  # @param [Hash] options Streaming options
95
97
  #
96
98
  def stream_notifications_for(model, options = {})
97
- # Default options
99
+
100
+ # Default publication options
98
101
  options = {
99
- broadcasting: model.model_name.collection,
100
- params: nil
101
- }.merge(options)
102
+ publication: model.model_name.name,
103
+ cache: false,
104
+ model_options: {},
105
+ scope: :all
106
+ }.merge(options).merge(params.deep_symbolize_keys)
102
107
 
103
108
  # These options cannot be overridden
104
109
  options[:model] = model
105
- options[:channel] = self
106
- model_name = model.model_name.collection
107
110
 
108
- # Sets channel options
109
- self.ActionCableNotifications[model_name] = options
111
+ publication = options[:publication]
110
112
 
111
- # Checks if model already includes notification callbacks
112
- if !model.respond_to? :ActionCableNotificationsOptions
113
- model.send('include', ActionCableNotifications::Model)
114
- end
113
+ # Checks if the publication already exists in the channel
114
+ if not @ChannelPublications.include?(publication)
115
+ # Sets channel options
116
+ @ChannelPublications[publication] = options
115
117
 
116
- # Sets broadcast options if they are not already present in the model
117
- if not model.ActionCableNotificationsOptions.key? options[:broadcasting]
118
- model.broadcast_notifications_from options[:broadcasting], options
119
- end
118
+ # Checks if model already includes notification callbacks
119
+ if !model.respond_to? :ChannelPublications
120
+ model.send('include', ActionCableNotifications::Model)
121
+ end
120
122
 
121
- # Start streaming
122
- stream_from options[:broadcasting], coder: ActiveSupport::JSON do |packet|
123
- transmit_packet(packet)
124
- end
123
+ # Sets broadcast options if they are not already present in the model
124
+ if not model.ChannelPublications.key? publication
125
+ model.broadcast_notifications_from publication, options[:model_options]
126
+ else # Reads options configuracion from model
127
+ options[:model_options] = model.ChannelPublications[publication]
128
+ end
125
129
 
130
+ # Start streaming
131
+ stream_from publication, coder: ActiveSupport::JSON do |packet|
132
+ packet.merge!({publication: publication})
133
+ transmit_packet(packet, options)
134
+ end
135
+ # XXX: Transmit initial data
136
+
137
+ end
126
138
  end
127
139
 
128
140
  #
@@ -130,10 +142,22 @@ module ActionCableNotifications
130
142
  #
131
143
  # @param [Hash] packet Packet with changes notifications
132
144
  #
133
- def transmit_packet(packet)
134
- packet = packet.as_json.deep_symbolize_keys!
135
- if update_cache(packet)
136
- transmit packet
145
+ def transmit_packet(packet, options={})
146
+ # Default options
147
+ options = {
148
+ cache: false
149
+ }.merge(options)
150
+
151
+ packet = packet.as_json.deep_symbolize_keys
152
+
153
+ if validate_packet(packet, options)
154
+ if options[:cache]==true
155
+ if update_cache(packet)
156
+ transmit packet
157
+ end
158
+ else
159
+ transmit packet
160
+ end
137
161
  end
138
162
  end
139
163
 
@@ -12,20 +12,36 @@ module ActionCableNotifications
12
12
  params = data[:params] || {}
13
13
 
14
14
  # Get results using provided parameters and model configured scope
15
- results = data[:model].
15
+ begin
16
+ temp_scope = data[:model_options][:scope].deep_dup || {}
17
+ if (data[:model_options][:scope].is_a? Hash) && (data[:model_options][:scope][:where].is_a? Hash)
18
+ #temp_scope = temp_scope.merge(params)
19
+ temp_scope[:where].each{|k,v| v.is_a?(Proc) ? temp_scope[:where][k]=v.call() : nil }
20
+ end
21
+ results = data[:model].
16
22
  select(params[:select] || []).
17
23
  limit(params[:limit]).
18
24
  where(params[:where] || {}).
19
- scoped_collection(data[:model_options][:scope]).
25
+ scoped_collection(temp_scope).
20
26
  to_a() rescue []
21
27
 
22
- response = { collection: data[:model].model_name.collection,
23
- msg: 'upsert_many',
24
- data: results
25
- }
28
+ response = {
29
+ publication: data[:publication],
30
+ msg: 'upsert_many',
31
+ data: results
32
+ }
33
+ rescue Exception => e
34
+ response = {
35
+ publication: data[:publication],
36
+ collection: data[:model].model_name.collection,
37
+ msg: 'error',
38
+ command: data[:command],
39
+ error: e.message
40
+ }
41
+ end
26
42
 
27
43
  # Send data to the client
28
- transmit_packet response
44
+ transmit_packet response, data[:options]
29
45
  end
30
46
 
31
47
  #
@@ -62,7 +78,7 @@ module ActionCableNotifications
62
78
  }
63
79
 
64
80
  # Send error notification to the client
65
- transmit response
81
+ transmit_packet response
66
82
  end
67
83
 
68
84
  end
@@ -99,7 +115,7 @@ module ActionCableNotifications
99
115
  }
100
116
 
101
117
  # Send error notification to the client
102
- transmit response
118
+ transmit_packet response
103
119
  end
104
120
 
105
121
  end
@@ -134,7 +150,7 @@ module ActionCableNotifications
134
150
  }
135
151
 
136
152
  # Send error notification to the client
137
- transmit response
153
+ transmit_packet response
138
154
  end
139
155
 
140
156
  end
@@ -1,7 +1,51 @@
1
+ require 'action_cable_notifications/hash_db.rb'
2
+
1
3
  module ActionCableNotifications
2
4
  module Channel
3
5
  module Cache
4
6
 
7
+ def initialize(*args)
8
+ super
9
+ @cache = HashDB::Base.new()
10
+ end
11
+
12
+ #
13
+ # Validates packet before transmitting the message
14
+ #
15
+ # @param [Hash] packet Packet to be transmitted
16
+ # @param [Hash] options Channels options used to validate the packet
17
+ #
18
+ # @return [Boolean] <description>
19
+ #
20
+ def validate_packet(packet, options = {})
21
+ options = {
22
+ }.merge(options)
23
+
24
+ if packet[:msg].in? ['upsert_many', 'create', 'update', 'destroy']
25
+ if packet[:msg].in? ['upsert_many']
26
+ data = packet[:data]
27
+ else
28
+ data = [(packet[:data] || {}).merge({id: packet[:id]})]
29
+ end
30
+
31
+ packet_validator = HashDB::Base.new(data)
32
+ data = packet_validator.scoped_collection(options[:scope]).data
33
+ if data.present?
34
+ if packet[:msg].in? ['upsert_many']
35
+ packet[:data] = data
36
+ else
37
+ packet[:data] = data.first
38
+ end
39
+ true
40
+ else
41
+ false
42
+ end
43
+ else
44
+ true
45
+ end
46
+
47
+ end
48
+
5
49
  #
6
50
  # Updates server side cache of client side collections
7
51
  # XXX compute cache diff before sending to clients
@@ -0,0 +1,111 @@
1
+ module HashDB
2
+ class Base
3
+
4
+ def initialize(data=nil)
5
+ if data.present?
6
+ @data = Array(data)
7
+ else
8
+ @data = []
9
+ end
10
+ end
11
+
12
+ def data
13
+ @data
14
+ end
15
+
16
+ def data=(data)
17
+ @data = data || []
18
+ end
19
+
20
+ def wrap(data)
21
+ HashDB::Base.new(data)
22
+ end
23
+
24
+ private :wrap
25
+
26
+ def all(options={})
27
+ if options.has_key?(:conditions)
28
+ where(options[:conditions])
29
+ else
30
+ wrap(@data ||= [])
31
+ end
32
+ end
33
+
34
+ def count
35
+ all.length
36
+ end
37
+
38
+ def where(options)
39
+ return @data if options.blank?
40
+
41
+ data = (@data || []).select do |record|
42
+ match_options?(record, options)
43
+ end
44
+
45
+ wrap(data)
46
+ end
47
+
48
+ def match_options?(record, options)
49
+ options.all? do |col, match|
50
+ if [Array, Range].include?(match.class)
51
+ match.include?(record[col])
52
+ else
53
+ record[col] == match
54
+ end
55
+ end
56
+ end
57
+
58
+ private :match_options?
59
+
60
+ def scoped_collection ( scope = :all )
61
+ scope = scope.to_a if scope.is_a? Hash
62
+ Array(scope).inject(self) do |o, a|
63
+ o.try(*a)
64
+ end
65
+ end
66
+
67
+ def select( fields=nil )
68
+ if fields.present?
69
+ wrap(@data.map{|v| v.slice(*(Array(fields).map(&:to_sym)))})
70
+ else
71
+ all
72
+ end
73
+ end
74
+
75
+ def limit( count=nil )
76
+ if count.present? and count>0
77
+ wrap(@data.slice(0,count))
78
+ else
79
+ all
80
+ end
81
+ end
82
+
83
+ def delete_all
84
+ @data = []
85
+ end
86
+
87
+ def first
88
+ @data.first
89
+ end
90
+
91
+ def last
92
+ @data.last
93
+ end
94
+
95
+ def find(id, * args)
96
+ case id
97
+ when nil
98
+ nil
99
+ when :all
100
+ all
101
+ when :first
102
+ all(*args).first
103
+ when Array
104
+ id.map { |i| find(i) }
105
+ else
106
+ where({id: id})
107
+ end
108
+ end
109
+
110
+ end
111
+ end
@@ -4,31 +4,44 @@ module ActionCableNotifications
4
4
 
5
5
  included do
6
6
  # Action cable notification options storage
7
- class_attribute :ActionCableNotificationsOptions
8
- self.ActionCableNotificationsOptions = {}
7
+ class_attribute :ChannelPublications
8
+ self.ChannelPublications = {}
9
9
 
10
10
  # Register Callbacks
11
+ before_update :prepare_update
11
12
  after_update :notify_update
12
13
  after_create :notify_create
13
14
  after_destroy :notify_destroy
14
- end
15
15
 
16
- module ClassMethods
16
+ def record_within_scope records
17
+ if records.respond_to?(:where)
18
+ found_record = records.where(id: self.id).first
19
+ elsif records.respond_to?(:detect) and (found_record = records.detect{|e| e["id"]==self.id})
20
+ found_record
21
+ else
22
+ nil
23
+ end
24
+ end
25
+
26
+ end
17
27
 
28
+ class_methods do
18
29
  #
19
30
  # Sets or removes notificacions options for Active Record model
20
31
  #
21
- # @param [sym] broadcasting Topic name to broadcast in
32
+ # @param [sym] publication Topic name to broadcast in
22
33
  # @param [hash] options Hash containing notification options
23
34
  #
24
- def broadcast_notifications_from ( broadcasting, options = {} )
35
+ def broadcast_notifications_from ( publication, options = {} )
25
36
  # Default options
26
37
  options = {
27
38
  actions: [:create, :update, :destroy],
28
- scope: :all # Default collection scope
39
+ track_scope_changes: false,
40
+ scope: :all, # Default collection scope
41
+ records: []
29
42
  }.merge(options)
30
43
 
31
- self.ActionCableNotificationsOptions[broadcasting.to_s] = options
44
+ self.ChannelPublications[publication.to_s] = options
32
45
  end
33
46
 
34
47
  #
@@ -47,19 +60,17 @@ module ActionCableNotifications
47
60
  #
48
61
  # Retrieves initial values to be sent to clients upon subscription
49
62
  #
50
- # @param [Sym] broadcasting Name of broadcasting stream
63
+ # @param [Sym] publication Name of publication stream
51
64
  #
52
65
  # @return [Hash] Hash containing the results in the following format:
53
66
  # {
54
- # collection: self.model_name.collection,
55
67
  # msg: 'add_collection',
56
68
  # data: self.scoped_collection(options[:scope])
57
69
  # }
58
- def notify_initial ( broadcasting )
59
- options = self.ActionCableNotificationsOptions[broadcasting.to_s]
70
+ def notify_initial ( publication )
71
+ options = self.ChannelPublications[publication.to_s]
60
72
  if options.present?
61
73
  {
62
- collection: self.model_name.collection,
63
74
  msg: 'upsert_many',
64
75
  data: self.scoped_collection(options[:scope])
65
76
  }
@@ -71,12 +82,13 @@ module ActionCableNotifications
71
82
  # Broadcast notifications when a new record is created
72
83
  #
73
84
  def notify_create
74
- self.ActionCableNotificationsOptions.each do |broadcasting, options|
85
+ self.ChannelPublications.each do |publication, options|
75
86
  if options[:actions].include? :create
76
- # Checks if record is within scope before broadcasting
77
- if self.class.scoped_collection(options[:scope]).where(id: self.id)
78
- ActionCable.server.broadcast broadcasting,
79
- collection: self.model_name.collection,
87
+ # Checks if records is within scope before broadcasting
88
+ records = self.class.scoped_collection(options[:scope])
89
+
90
+ if options[:scope]==:all or record_within_scope(records)
91
+ ActionCable.server.broadcast publication,
80
92
  msg: 'create',
81
93
  id: self.id,
82
94
  data: self
@@ -85,32 +97,81 @@ module ActionCableNotifications
85
97
  end
86
98
  end
87
99
 
100
+ def prepare_update
101
+ self.ChannelPublications.each do |publication, options|
102
+ if options[:actions].include? :update
103
+ if options[:scope]==:all
104
+ options[:records].push self
105
+ else
106
+ record = record_within_scope(self.class.scoped_collection(options[:scope]))
107
+ if record.present?
108
+ options[:records].push record
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+
88
115
  #
89
- # Broadcast notifications when a record is updated. Only changed
90
- # field will be sent.
116
+ # Broadcast notifications when a record is updated. Only changed fields will be sent
117
+ # if they are within configured scope
91
118
  #
92
119
  def notify_update
93
- changes = {}
94
- self.changes.each do |k,v|
95
- changes[k] = v[1]
120
+ # Get model changes
121
+ if self.respond_to?(:saved_changes) # For Rails >= 5.1
122
+ changes = self.saved_changes.transform_values(&:second)
123
+ else # For Rails < 5.1
124
+ changes = self.changes.transform_values(&:second)
96
125
  end
97
126
 
127
+ # Checks if there are changes in the model
98
128
  if !changes.empty?
99
- self.ActionCableNotificationsOptions.each do |broadcasting, options|
129
+ self.ChannelPublications.each do |publication, options|
100
130
  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
131
+ # Checks if previous record was within scope
132
+ record = record_within_scope(options[:records])
133
+ was_in_scope = record.present?
134
+
135
+ options[:records].delete(record) if was_in_scope
136
+
137
+ # Checks if current record is within scope
138
+ if options[:track_scope_changes]==true
139
+ is_in_scope = false
140
+ if options[:scope]==:all
141
+ record = self
142
+ is_in_scope = true
143
+ else
144
+ record = record_within_scope(self.class.scoped_collection(options[:scope]))
145
+ if record.present?
146
+ is_in_scope = true
147
+ end
148
+ end
149
+ else
150
+ is_in_scope = was_in_scope
151
+ end
152
+
153
+ # Broadcasts notifications about model changes
154
+ if is_in_scope
155
+ if was_in_scope
156
+ # Get model changes and applies them to the scoped collection record
157
+ changes.select!{|k,v| record.respond_to?(k)}
158
+
159
+ if !changes.empty?
160
+ ActionCable.server.broadcast publication,
161
+ msg: 'update',
162
+ id: self.id,
163
+ data: changes
164
+ end
165
+ else
166
+ ActionCable.server.broadcast publication,
167
+ msg: 'create',
168
+ id: record.id,
169
+ data: record
170
+ end
171
+ elsif was_in_scope # checks if needs to delete the record if its no longer in scope
172
+ ActionCable.server.broadcast publication,
173
+ msg: 'destroy',
174
+ id: self.id
114
175
  end
115
176
  end
116
177
  end
@@ -121,12 +182,11 @@ module ActionCableNotifications
121
182
  # Broadcast notifications when a record is destroyed.
122
183
  #
123
184
  def notify_destroy
124
- self.ActionCableNotificationsOptions.each do |broadcasting, options|
125
- if options[:actions].include? :destroy
185
+ self.ChannelPublications.each do |publication, options|
186
+ if options[:scope]==:all or options[:actions].include? :destroy
126
187
  # Checks if record is within scope before broadcasting
127
- if self.class.scoped_collection(options[:scope]).where(id: self.id)
128
- ActionCable.server.broadcast broadcasting,
129
- collection: self.model_name.collection,
188
+ if options[:scope]==:all or record_within_scope(self.class.scoped_collection(options[:scope])).present?
189
+ ActionCable.server.broadcast publication,
130
190
  msg: 'destroy',
131
191
  id: self.id
132
192
  end
@@ -1,3 +1,3 @@
1
1
  module ActionCableNotifications
2
- VERSION = '0.1.27'
2
+ VERSION = '0.1.35'
3
3
  end
metadata CHANGED
@@ -1,22 +1,19 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: action_cable_notifications
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.27
4
+ version: 0.1.35
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: 2017-01-09 00:00:00.000000000 Z
11
+ date: 2022-05-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: 5.0.0
20
17
  - - ">="
21
18
  - !ruby/object:Gem::Version
22
19
  version: 5.0.0.1
@@ -24,9 +21,6 @@ dependencies:
24
21
  prerelease: false
25
22
  version_requirements: !ruby/object:Gem::Requirement
26
23
  requirements:
27
- - - "~>"
28
- - !ruby/object:Gem::Version
29
- version: 5.0.0
30
24
  - - ">="
31
25
  - !ruby/object:Gem::Version
32
26
  version: 5.0.0.1
@@ -34,14 +28,14 @@ dependencies:
34
28
  name: lodash-rails
35
29
  requirement: !ruby/object:Gem::Requirement
36
30
  requirements:
37
- - - "~>"
31
+ - - ">="
38
32
  - !ruby/object:Gem::Version
39
33
  version: 4.15.0
40
34
  type: :runtime
41
35
  prerelease: false
42
36
  version_requirements: !ruby/object:Gem::Requirement
43
37
  requirements:
44
- - - "~>"
38
+ - - ">="
45
39
  - !ruby/object:Gem::Version
46
40
  version: 4.15.0
47
41
  - !ruby/object:Gem::Dependency
@@ -86,6 +80,20 @@ dependencies:
86
80
  - - ">="
87
81
  - !ruby/object:Gem::Version
88
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: awesome_print
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
89
97
  description: Rails engine that provides Automatic realtime notification broadcast
90
98
  for ActiveRecord models changes using Action Cable and websockets
91
99
  email:
@@ -118,6 +126,7 @@ files:
118
126
  - lib/action_cable_notifications/channel_actions.rb
119
127
  - lib/action_cable_notifications/channel_cache.rb
120
128
  - lib/action_cable_notifications/engine.rb
129
+ - lib/action_cable_notifications/hash_db.rb
121
130
  - lib/action_cable_notifications/model.rb
122
131
  - lib/action_cable_notifications/version.rb
123
132
  - lib/tasks/action_cable_notifications_tasks.rake
@@ -140,8 +149,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
140
149
  - !ruby/object:Gem::Version
141
150
  version: '0'
142
151
  requirements: []
143
- rubyforge_project:
144
- rubygems_version: 2.5.1
152
+ rubygems_version: 3.0.8
145
153
  signing_key:
146
154
  specification_version: 4
147
155
  summary: Automatic realtime notification broadcast for ActiveRecord models changes