salesforce_streamer 1.2.2 → 2.1.1

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
  SHA256:
3
- metadata.gz: 88a8ec9604b29ed48acfcf8ac1435fcacf199a7813eba8db8937038874132128
4
- data.tar.gz: 5b7c06be3a4467d41845e76578c95a9bb197a10017ee893eb2acc516b26ffbb0
3
+ metadata.gz: 4a92bbc0eaffa8152b8cdcc6b8eb35248a8fe1e996bc73410dc1ed4043acde08
4
+ data.tar.gz: '0787479213ab7bc07c07a6cc5d8c9034f5593aee613a28529e25dd51e8c3d9c2'
5
5
  SHA512:
6
- metadata.gz: 36d5ba79e4af94533266bc9ac52b5110b6a4f6305db9c60a6498b7218d7a5e8c7f48b9db008b582c56877845bb9a498618141da4af0c3e1b77a8173a0640733d
7
- data.tar.gz: d06777105574d0ff430888059ab1d1f61164f520da6b3b5a1c6408e983de3fe22d27fabf575e46a2d7f7ce14936e7af8423b8c7b433a296183a73da8716cd0e9
6
+ metadata.gz: 95e9b7ba4133fe7c7840e413677cff3121b6d4c94c9f1037b4fd91fe1a111dd6a1ac350075b987d68cd1fbcb72b733fbed3bd087353b6a075789a8fd15c014cb
7
+ data.tar.gz: 573892b3222c3240fb8d171b32f03f446bae3a0ff2184aa7bbbdf71f940ecc961cd84a634dda824a9ecb42acb13446e44b8d0d8f4f4dacb4cae0f1c555658cf0
@@ -9,10 +9,3 @@ update_configs:
9
9
  allowed_updates:
10
10
  - match:
11
11
  update_type: "all"
12
- automerged_updates:
13
- - match:
14
- dependency_type: "development"
15
- update_type: "all"
16
- - match:
17
- dependency_type: "production"
18
- update_type: "all"
@@ -0,0 +1,99 @@
1
+ # Changelog
2
+
3
+ Sorted so that the most recent change logs are near the top. Only significant
4
+ changes are logged in the change log.
5
+
6
+ ## 2020-08-17 Scott Serok [scott@renofi.com](mailto:scott@renofi.com)
7
+
8
+ v2.1 changes the expected interface of `Configuration#replay_adapter`.
9
+
10
+ Normally this breaking change would require a major version bump, but since the
11
+ functionality today is quiet broken we can call this a major bug fix.
12
+
13
+ The `config.replay_adapter` should be an object that has an interface like Hash.
14
+ It must respond to `[]` and `[]=`. By default the adapter is an empty hash. If
15
+ you want your push topic replayId to persist between restarts, then you should
16
+ implement a class with an appropriate interface.
17
+
18
+ ```ruby
19
+ class MyReplayAdapter
20
+ def [](channel)
21
+ MyPersistence.get(channel)
22
+ end
23
+
24
+ def []=(channel, replay_id)
25
+ MyPersistence.set(channel, replay_id)
26
+ end
27
+ end
28
+ ```
29
+
30
+ This change was sparked by a misunderstanding of the
31
+ `Restforce::Concerns::Streaming::ReplayExtension` replay handlers.
32
+ SalesforceStreamer can eliminate some complexity and fix a bug by delegating the
33
+ responsibility of maintaining the current replayId to that ReplayExtension. The
34
+ object will be used on each request/response cycle to record and read the latest
35
+ replayId as long as the object assigned to `config.replay_adapter` responds to
36
+ `[]` and `[]=`.
37
+
38
+ ## 2020-08-04 Scott Serok [scott@renofi.com](mailto:scott@renofi.com)
39
+
40
+ v2.0 is released as a major simplification of this library. There are 2
41
+ significant differences from a user's perspective.
42
+
43
+ 1. The YAML configuration requires minor edits to be compatible in v2.0.
44
+ 2. The built-in Redis persistance of the replayId field has been removed. You
45
+ should add a custom middleware and configure the new replay_adapter option.
46
+
47
+ ### PushTopic configuration changes
48
+
49
+ After upgrading to v2, the YAML configuration should be modified. Shift the
50
+ nested "salesforce" block to the left and remove the "salesforce" key.
51
+
52
+ Before v2:
53
+
54
+ name: "TopicName"
55
+ handler: "MyConstant"
56
+ salesforce:
57
+ query: "SELECT Id FROM Lead"
58
+
59
+ As of v2:
60
+
61
+ name: "TopicName"
62
+ handler: "MyConstant"
63
+ query: "SELECT Id FROM Lead"
64
+
65
+ ### Redis Persistance removed
66
+
67
+ The original intention of this library is to manage PushTopic definitions
68
+ and run an event machine that subscribes to the Salesforce Streaming API based
69
+ on those PushTopics.
70
+
71
+ The addition of managing the Replay ID adds unecessary complexity that can be
72
+ incorporated through customization, and so it's been removed. You might use a
73
+ recent commit of v1 of this library for reference how to implement Redis as the
74
+ persistence layer.
75
+
76
+ To record the replayId on every message we can add a piece of middleware
77
+
78
+ class RecordReplayIdMiddleware
79
+ def initialize(handler)
80
+ @handler = handler
81
+ end
82
+
83
+ def call(message)
84
+ @handler.call(message)
85
+ replay_id = message['event']['replayId']
86
+ topic_name = message['topic']
87
+ MyStore.record_replay_id(replay_id, topic_name)
88
+ end
89
+ end
90
+ SalesforceStreamer.config.use_middleware RecordReplayIdMiddleware
91
+
92
+ To retrieve the replayId before subscribing to a PushTopic,
93
+ configure an adapter that returns an integer.
94
+
95
+ SalesforceStreamer.config.replay_adapter = proc { |topic|
96
+ (MyStore.fetch_replay_id(topic.name) || -1).to_i
97
+ }
98
+
99
+ This will be used to set the replayId value when subscribing to the PushTopic.
@@ -1,9 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- salesforce_streamer (1.2.2)
5
- faye (= 0.8.9)
6
- restforce (~> 4.2)
4
+ salesforce_streamer (2.1.1)
5
+ dry-initializer (~> 3.0)
6
+ faye (~> 1.4)
7
+ restforce (>= 4.2, < 6.0)
7
8
 
8
9
  GEM
9
10
  remote: https://rubygems.org/
@@ -12,15 +13,14 @@ GEM
12
13
  public_suffix (>= 2.0.2, < 5.0)
13
14
  ast (2.4.1)
14
15
  byebug (11.1.3)
15
- codecov (0.2.0)
16
- colorize
16
+ codecov (0.2.9)
17
17
  json
18
18
  simplecov
19
- colorize (0.8.1)
20
19
  cookiejar (0.3.3)
21
20
  diff-lcs (1.4.4)
22
21
  docile (1.3.2)
23
- em-http-request (1.1.6)
22
+ dry-initializer (3.0.3)
23
+ em-http-request (1.1.7)
24
24
  addressable (>= 2.3.4)
25
25
  cookiejar (!= 0.3.1)
26
26
  em-socksify (>= 0.3)
@@ -29,24 +29,26 @@ GEM
29
29
  em-socksify (0.3.2)
30
30
  eventmachine (>= 1.0.0.beta.4)
31
31
  eventmachine (1.2.7)
32
- faraday (1.0.0)
32
+ faraday (1.0.1)
33
33
  multipart-post (>= 1.2, < 3)
34
34
  faraday_middleware (1.0.0)
35
35
  faraday (~> 1.0)
36
- faye (0.8.9)
36
+ faye (1.4.0)
37
37
  cookiejar (>= 0.3.0)
38
- em-http-request (>= 0.3.0)
38
+ em-http-request (>= 1.1.6)
39
39
  eventmachine (>= 0.12.0)
40
- faye-websocket (>= 0.4.0)
40
+ faye-websocket (>= 0.11.0)
41
+ multi_json (>= 1.0.0)
41
42
  rack (>= 1.0.0)
42
- yajl-ruby (>= 1.0.0)
43
+ websocket-driver (>= 0.5.1)
43
44
  faye-websocket (0.11.0)
44
45
  eventmachine (>= 0.12.0)
45
46
  websocket-driver (>= 0.5.1)
46
47
  hashie (4.1.0)
47
48
  http_parser.rb (0.6.0)
48
49
  json (2.3.1)
49
- jwt (2.2.1)
50
+ jwt (2.2.2)
51
+ multi_json (1.15.0)
50
52
  multipart-post (2.1.1)
51
53
  parallel (1.19.2)
52
54
  parser (2.7.1.4)
@@ -56,11 +58,10 @@ GEM
56
58
  rainbow (3.0.0)
57
59
  rake (13.0.1)
58
60
  regexp_parser (1.7.1)
59
- restforce (4.2.2)
60
- faraday (>= 0.9.0, <= 1.0)
61
- faraday_middleware (>= 0.8.8, <= 1.0)
61
+ restforce (5.0.1)
62
+ faraday (>= 0.9.0, <= 2.0)
63
+ faraday_middleware (>= 0.8.8, <= 2.0)
62
64
  hashie (>= 1.2.0, < 5.0)
63
- json (>= 1.7.5)
64
65
  jwt (>= 1.5.6)
65
66
  rexml (3.2.4)
66
67
  rspec (3.9.0)
@@ -76,23 +77,23 @@ GEM
76
77
  diff-lcs (>= 1.2.0, < 2.0)
77
78
  rspec-support (~> 3.9.0)
78
79
  rspec-support (3.9.3)
79
- rubocop (0.88.0)
80
+ rubocop (0.90.0)
80
81
  parallel (~> 1.10)
81
82
  parser (>= 2.7.1.1)
82
83
  rainbow (>= 2.2.2, < 4.0)
83
84
  regexp_parser (>= 1.7)
84
85
  rexml
85
- rubocop-ast (>= 0.1.0, < 1.0)
86
+ rubocop-ast (>= 0.3.0, < 1.0)
86
87
  ruby-progressbar (~> 1.7)
87
88
  unicode-display_width (>= 1.4.0, < 2.0)
88
- rubocop-ast (0.1.0)
89
- parser (>= 2.7.0.1)
90
- rubocop-performance (1.7.0)
89
+ rubocop-ast (0.3.0)
90
+ parser (>= 2.7.1.4)
91
+ rubocop-performance (1.7.1)
91
92
  rubocop (>= 0.82.0)
92
- rubocop-rspec (1.42.0)
93
- rubocop (>= 0.87.0)
93
+ rubocop-rspec (1.43.2)
94
+ rubocop (~> 0.87)
94
95
  ruby-progressbar (1.10.1)
95
- simplecov (0.18.5)
96
+ simplecov (0.19.0)
96
97
  docile (~> 1.1)
97
98
  simplecov-html (~> 0.11)
98
99
  simplecov-html (0.12.2)
@@ -100,7 +101,6 @@ GEM
100
101
  websocket-driver (0.7.3)
101
102
  websocket-extensions (>= 0.1.0)
102
103
  websocket-extensions (0.1.5)
103
- yajl-ruby (1.4.1)
104
104
 
105
105
  PLATFORMS
106
106
  ruby
data/README.md CHANGED
@@ -20,33 +20,11 @@ And then execute:
20
20
 
21
21
  ## Usage
22
22
 
23
- ### Middleware
24
-
25
- You can initialize the streamer server with any number of middleware classes.
26
- When a message is received by a PushTopic subscription, the chain of middleware
27
- classes are executed before the message handler is called.
28
-
29
- ```ruby
30
- # config/initializers/streamer.rb
31
- class MySimpleMiddleware
32
- def initialize(handler)
33
- @handler = handler
34
- end
35
- def call(message)
36
- @handler.call(message)
37
- end
38
- end
39
-
40
- SalesforceStreamer.config.use_middleware MySimpleMiddleware
41
- ```
42
-
43
23
  ### Configure Push Topics
44
24
 
45
- Create a YAML file to configure your server subscriptions. The configuration
46
- for each subscription must have a nested `salesforce:` key. These settings will
47
- be synced to your Salesforce instance when the `-x` flag is set on the command
48
- line. For more information about the `replay:` and `notify_fields_for` options
49
- please see the Salesforce Streaming API reference documentation.
25
+ Create a YAML file to configure your PushTopic subscriptions. When streamer
26
+ starts up it will check for any differences between Salesforce PushTopics and
27
+ this yaml and update any differences when `config.manage_topics = true`.
50
28
 
51
29
  ```yaml
52
30
  # config/streamer.yml
@@ -55,12 +33,11 @@ base: &DEFAULT
55
33
  accounts:
56
34
  handler: "AccountChangeHandler"
57
35
  replay: -1
58
- salesforce:
59
- name: "AllAccounts"
60
- api_version: "41.0"
61
- description: "Sync Accounts"
62
- notify_for_fields: "Referenced"
63
- query: "Select Id, Name From Account"
36
+ name: "AllAccounts"
37
+ api_version: "49.0"
38
+ description: "Sync Accounts"
39
+ notify_for_fields: "Referenced"
40
+ query: "Select Id, Name From Account"
64
41
 
65
42
  development:
66
43
  <<: *DEFAULT
@@ -72,14 +49,6 @@ production:
72
49
  <<: *DEFAULT
73
50
  ```
74
51
 
75
- It's important to note that the way push topics are managed is by the Salesforce
76
- name attribute. This should uniquely identify each push topic. It is not
77
- recommended to change the name of your push topic definitions; otherwise, the
78
- push topic manager will not find a push topic in Salesforce resulting in the
79
- creation of a brand new push topic. If the push topic manager identifies a
80
- difference in any of the other Salesforce attributes, then it will update the
81
- push topic in Salesforce before starting the streaming server.
82
-
83
52
  ### Define Message Handlers
84
53
 
85
54
  Define your handlers somewhere in your project. They must respond to either
@@ -89,14 +58,17 @@ Define your handlers somewhere in your project. They must respond to either
89
58
  # lib/account_change_handler.rb
90
59
  # Handle account changes inline
91
60
  class AccountChangeHandler
92
- def self.call(message)
93
- puts message
61
+ class << self
62
+ def call(message)
63
+ puts message
64
+ end
94
65
  end
95
66
  end
96
67
 
97
68
  # Handle account changes asynchronously
98
69
  class AccountChangeHandler
99
70
  include Sidekiq::Worker
71
+
100
72
  def perform(message)
101
73
  puts message
102
74
  end
@@ -114,13 +86,13 @@ Configure the `SalesforceStreamer` module.
114
86
  ```ruby
115
87
  # config/initializers/salesforce_streamer.rb
116
88
 
117
- require 'redis'
118
- require 'connection_pool'
119
-
120
- SalesforceStreamer.config.redis_connection = ConnectionPool.new(size: 5, timeout: 5) { Redis.new }
121
- SalesforceStreamer.config.logger = Logger.new(STDERR, level: 'INFO')
122
- SalesforceStreamer.config.exception_adapter = proc { |e| puts e }
123
- SalesforceStreamer.config.manage_topics = true
89
+ SalesforceStreamer.configure do |config|
90
+ config.logger = Logger.new(STDERR, level: 'INFO')
91
+ config.exception_adapter = proc { |e| puts e }
92
+ config.replay_adapter = MyReplayAdapter
93
+ config.use_middleware AfterMessageReceived
94
+ config.manage_topics = true
95
+ end
124
96
  ```
125
97
 
126
98
  ### Launch The Streamer
@@ -159,6 +131,48 @@ By default, the executable will load the YAML based on the `RACK_ENV` environmen
159
131
  variable, or default to `:development` if not set. You can override this by
160
132
  setting the `config.environment = :integration`
161
133
 
134
+ ### Message Handling Middleware
135
+
136
+ You can initialize the streamer server with any number of middleware classes.
137
+ When a message is received by a PushTopic subscription, the chain of middleware
138
+ classes are executed before the message handler is called.
139
+
140
+ ```ruby
141
+ # config/initializers/streamer.rb
142
+ class MySimpleMiddleware
143
+ def initialize(handler)
144
+ @handler = handler
145
+ end
146
+
147
+ def call(message)
148
+ @handler.call(message)
149
+ end
150
+ end
151
+
152
+ SalesforceStreamer.config.use_middleware MySimpleMiddleware
153
+ ```
154
+
155
+ ### ReplayAdapter
156
+
157
+ The `config.replay_adapter` should be an object that has an interface like Hash.
158
+ It must respond to `[]` and `[]=`. By default the adapter is an empty hash. If
159
+ you want your push topic replayId to persist between restarts, then you should
160
+ implement a class with an appropriate interface.
161
+
162
+ ```ruby
163
+ class MyReplayAdapter
164
+ def [](channel)
165
+ Persistence.get(channel)
166
+ end
167
+
168
+ def []=(channel, replay_id)
169
+ Persistence.set(channel, replay_id)
170
+ end
171
+ end
172
+ ```
173
+
174
+ This adapter will be used directly by `Restforce::ReplayExtension`.
175
+
162
176
  ## Development
163
177
 
164
178
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
data/Rakefile CHANGED
@@ -5,4 +5,4 @@ require 'rubocop/rake_task'
5
5
  RSpec::Core::RakeTask.new(:spec)
6
6
  RuboCop::RakeTask.new
7
7
 
8
- task default: %i[rubocop:auto_correct spec]
8
+ task default: %i[spec rubocop:auto_correct]
@@ -4,16 +4,15 @@ require 'optparse'
4
4
  require 'restforce'
5
5
  require 'yaml'
6
6
  require 'forwardable'
7
+ require 'dry-initializer'
7
8
 
8
9
  require 'salesforce_streamer/configuration'
9
10
  require 'salesforce_streamer/errors'
10
11
  require 'salesforce_streamer/replay_id_error_extension'
11
12
  require 'salesforce_streamer/log'
12
13
  require 'salesforce_streamer/push_topic'
13
- require 'salesforce_streamer/topic_manager'
14
+ require 'salesforce_streamer/salesforce_topic_manager'
14
15
  require 'salesforce_streamer/salesforce_client'
15
- require 'salesforce_streamer/replay_persistence'
16
- require 'salesforce_streamer/redis_replay'
17
16
  require 'salesforce_streamer/server'
18
17
  require 'salesforce_streamer/version'
19
18
  require 'salesforce_streamer/launcher'
@@ -60,9 +59,7 @@ module SalesforceStreamer
60
59
  Configuration.instance
61
60
  end
62
61
 
63
- class RedisConnectionError < StandardError
64
- def initialize
65
- super 'SalesforceStreamer.config.redis_connection not set'
66
- end
62
+ def self.configure
63
+ yield Configuration.instance
67
64
  end
68
65
  end
@@ -32,7 +32,7 @@ module SalesforceStreamer
32
32
  end
33
33
 
34
34
  o.on '-v', '--verbose LEVEL', 'Set the log level (default no logging)' do |arg|
35
- @config.logger = Logger.new(STDERR, level: arg)
35
+ @config.logger = Logger.new($stderr, level: arg)
36
36
  end
37
37
 
38
38
  o.on '-V', '--version', 'Print the version information' do
@@ -1,22 +1,27 @@
1
1
  module SalesforceStreamer
2
2
  # Manages server configuration.
3
3
  class Configuration
4
- attr_accessor :environment, :logger, :require_path, :config_file, :manage_topics,
5
- :server, :exception_adapter, :persistence_adapter, :redis_connection, :middleware
4
+ attr_accessor :environment, :logger, :require_path, :config_file,
5
+ :manage_topics, :exception_adapter, :replay_adapter
6
+ attr_reader :middleware
6
7
 
7
8
  class << self
8
9
  attr_writer :instance
9
- end
10
10
 
11
- def self.instance
12
- @instance ||= new
11
+ def configure
12
+ yield instance
13
+ end
14
+
15
+ def instance
16
+ @instance ||= new
17
+ end
13
18
  end
14
19
 
15
20
  def initialize
16
21
  @environment = ENV['RACK_ENV'] || :development
17
22
  @logger = Logger.new(IO::NULL)
18
23
  @exception_adapter = proc { |exc| fail(exc) }
19
- @persistence_adapter = RedisReplay.new
24
+ @replay_adapter = Hash.new { |hash, key| hash[key] = -1 }
20
25
  @manage_topics = false
21
26
  @config_file = './config/streamer.yml'
22
27
  @require_path = './config/environment'
@@ -27,12 +32,17 @@ module SalesforceStreamer
27
32
  @manage_topics
28
33
  end
29
34
 
35
+ # adds a setup proc to the middleware array
30
36
  def use_middleware(klass, *args, &block)
31
- middleware << proc { |app| klass.new(app, *args, &block) }
37
+ @middleware << [klass, args, block]
32
38
  end
33
39
 
34
- def middleware_chain_for(app)
35
- middleware.reduce(app) { |memo, middleware| middleware.call(memo) }
40
+ # returns a ready to use chain of middleware
41
+ def middleware_runner(last_handler)
42
+ @middleware.reduce(last_handler) do |next_handler, current_handler|
43
+ klass, args, block = current_handler
44
+ klass.new(next_handler, *args, &block)
45
+ end
36
46
  end
37
47
 
38
48
  def push_topic_data
@@ -3,13 +3,13 @@ module SalesforceStreamer
3
3
 
4
4
  class MissingCLIFlagError < StandardError
5
5
  def initialize(flag)
6
- super 'Missing required command line flag: ' + flag.to_s
6
+ super "Missing required command line flag: #{flag}"
7
7
  end
8
8
  end
9
9
 
10
10
  class NilQueryError < StandardError
11
11
  def initialize(name)
12
- super 'Query not defined for ' + name.to_s
12
+ super "Query not defined for #{name}"
13
13
  end
14
14
  end
15
15
 
@@ -21,7 +21,13 @@ module SalesforceStreamer
21
21
 
22
22
  class PushTopicNameTooLongError < StandardError
23
23
  def initialize(name)
24
- super 'PushTopic name: ' + name.to_s + ' (' + name.size.to_s + '/25)'
24
+ super "PushTopic name: #{name} (#{name.size}/25)"
25
+ end
26
+ end
27
+
28
+ class UnprocessableHandlerError < StandardError
29
+ def initialize(constant)
30
+ super "#{constant} does not repond to .call or .perform_async"
25
31
  end
26
32
  end
27
33
  end
@@ -5,14 +5,14 @@ module SalesforceStreamer
5
5
  class Launcher
6
6
  def initialize
7
7
  load_server_configuration
8
- @manager = TopicManager.new push_topics: @push_topics
8
+ @manager = SalesforceTopicManager.new push_topics: @push_topics
9
9
  @server = Server.new push_topics: @push_topics
10
10
  end
11
11
 
12
12
  # Manages each PushTopic configured and starts the Streaming API listener.
13
13
  def run
14
14
  Log.info 'Launching Streamer Services'
15
- @manager.run
15
+ @manager.upsert_topics!
16
16
  @server.push_topics = @manager.push_topics
17
17
  @server.run
18
18
  end
@@ -36,7 +36,7 @@ module SalesforceStreamer
36
36
  @push_topics = []
37
37
  Configuration.instance.push_topic_data.each_value do |topic_data|
38
38
  Log.debug topic_data.to_s
39
- @push_topics << PushTopic.new(data: topic_data)
39
+ @push_topics << PushTopic.new(topic_data.transform_keys(&:to_sym))
40
40
  end
41
41
  end
42
42
  end
@@ -1,38 +1,29 @@
1
1
  module SalesforceStreamer
2
2
  # Models the PushTopic object for both Restforce and Streamer
3
3
  class PushTopic
4
- attr_accessor :id
5
- attr_reader :name, :description, :notify_for_fields, :query,
6
- :handler, :handler_constant, :api_version
4
+ extend Dry::Initializer
7
5
 
8
- def initialize(data:)
9
- @handler = data['handler']
10
- @static_replay = data.dig('replay')&.to_i || -1
11
- @name = data.dig('salesforce', 'name')
12
- @api_version = data.dig('salesforce', 'api_version') || '41.0'
13
- @description = data.dig('salesforce', 'description') || @name
14
- @notify_for_fields = data.dig('salesforce', 'notify_for_fields') || 'Referenced'
15
- @query = strip_spaces(data.dig('salesforce', 'query'))
16
- validate!
17
- end
6
+ option :name
7
+ option :query, proc { |str| str.gsub(/\s+/, ' ') }
8
+ option :handler, proc { |str| prepare_handler_proc Object.const_get(str) }
9
+ option :replay, proc(&:to_i), default: proc { -1 }
10
+ option :api_version, proc(&:to_s), default: proc { '49.0' }
11
+ option :notify_for_fields, default: proc { 'Referenced' }
12
+ option :id, optional: true
13
+ option :description, optional: true
18
14
 
19
- def replay
20
- ReplayPersistence.retrieve(name) || @static_replay
21
- end
15
+ attr_writer :id
22
16
 
23
17
  def handle(message)
24
18
  message['topic'] = @name
25
- handle_chain.call(message)
26
- ReplayPersistence.record @name, message.dig('event', 'replayId')
19
+ message_middleware.call(message)
27
20
  rescue StandardError => e
28
21
  Log.error e
29
22
  Configuration.instance.exception_adapter.call e
30
23
  end
31
24
 
32
- def to_s
33
- "PushTopic id=#{id} name=#{name} handler=#{handler} " \
34
- "replay=#{replay} notify_for_fields=#{notify_for_fields} " \
35
- "description=#{description} api_version=#{api_version} query=#{query}"
25
+ def attributes
26
+ self.class.dry_initializer.public_attributes self
36
27
  end
37
28
 
38
29
  private
@@ -40,29 +31,33 @@ module SalesforceStreamer
40
31
  def validate!
41
32
  fail(PushTopicNameTooLongError, @name) if @name.size > 25
42
33
 
43
- @handler_constant = Object.const_get(@handler)
34
+ @handler = Object.const_get(@handler)
44
35
  true
45
36
  rescue NameError, TypeError => e
46
- message = 'handler=' + @handler.to_s + ' exception=' + e.to_s
37
+ message = "handler=#{@handler} exception=#{e}"
47
38
  raise(PushTopicHandlerMissingError, message)
48
39
  end
49
40
 
50
- def handle_chain
51
- Configuration.instance.middleware_chain_for(handler_proc)
41
+ def message_middleware
42
+ Configuration.instance.middleware_runner(handler)
52
43
  end
53
44
 
54
- def handler_proc
55
- if handler_constant.respond_to? :perform_async
56
- proc { |message| handler_constant.perform_async message }
57
- else
58
- handler_constant
59
- end
60
- end
45
+ class << self
46
+ def strip_spaces(str)
47
+ fail(NilQueryError, @name) unless str
61
48
 
62
- def strip_spaces(str)
63
- fail(NilQueryError, @name) unless str
49
+ str.gsub(/\s+/, ' ')
50
+ end
64
51
 
65
- str.gsub(/\s+/, ' ')
52
+ def prepare_handler_proc(constant)
53
+ if constant.respond_to? :call
54
+ constant
55
+ elsif constant.respond_to? :perform_async
56
+ proc { |message| constant.perform_async message }
57
+ else
58
+ fail(UnprocessableHandlerError, constant)
59
+ end
60
+ end
66
61
  end
67
62
  end
68
63
  end
@@ -8,10 +8,8 @@ module SalesforceStreamer
8
8
  @client.authenticate!
9
9
  end
10
10
 
11
- def subscribe(*args)
12
- @client.subscribe(args) do
13
- yield
14
- end
11
+ def subscribe(*args, &block)
12
+ @client.subscribe(args, &block)
15
13
  end
16
14
 
17
15
  # Returns nil or an instance of Restforce::SObject
@@ -1,5 +1,5 @@
1
1
  module SalesforceStreamer
2
- class TopicManager
2
+ class SalesforceTopicManager
3
3
  attr_reader :push_topics
4
4
 
5
5
  def initialize(push_topics:)
@@ -7,10 +7,11 @@ module SalesforceStreamer
7
7
  @client = SalesforceClient.new
8
8
  end
9
9
 
10
- def run
11
- Log.info 'Running Topic Manager'
10
+ def upsert_topics!
11
+ Log.info 'Starting to upsert PushTopic definitions into Salesforce'
12
12
  @push_topics.each do |push_topic|
13
- Log.debug push_topic.to_s
13
+ Log.info push_topic.name
14
+ Log.debug push_topic.attributes.to_json
14
15
  upsert(push_topic) if diff?(push_topic)
15
16
  end
16
17
  end
@@ -29,12 +30,12 @@ module SalesforceStreamer
29
30
  return true unless push_topic.notify_for_fields.eql?(hashie.NotifyForFields)
30
31
  return true unless push_topic.api_version.to_s.eql?(hashie.ApiVersion.to_s)
31
32
 
32
- Log.debug 'No differences detected'
33
+ Log.info 'No differences detected'
33
34
  false
34
35
  end
35
36
 
36
37
  def upsert(push_topic)
37
- Log.info "Upsert PushTopic #{push_topic.name}"
38
+ Log.info "Upserting PushTopic"
38
39
  if Configuration.instance.manage_topics?
39
40
  @client.upsert_push_topic(push_topic)
40
41
  else
@@ -34,11 +34,9 @@ module SalesforceStreamer
34
34
  def start_em
35
35
  EM.run do
36
36
  @push_topics.map do |topic|
37
- client.subscribe topic.name, replay: topic.replay.to_i do |message|
38
- replay_id = message.dig('event', 'replayId')
39
- Log.info "Message #{replay_id} received from topic #{topic.name}"
37
+ client.subscribe topic.name, replay: Configuration.instance.replay_adapter do |message|
38
+ Log.info "Message #{message.dig('event', 'replayId')} received from topic #{topic.name}"
40
39
  topic.handle message
41
- topic.id = replay_id
42
40
  end
43
41
  end
44
42
  end
@@ -1,3 +1,3 @@
1
1
  module SalesforceStreamer
2
- VERSION = '1.2.2'.freeze
2
+ VERSION = '2.1.1'.freeze
3
3
  end
@@ -14,6 +14,7 @@ Gem::Specification.new do |spec|
14
14
 
15
15
  spec.metadata['homepage_uri'] = spec.homepage
16
16
  spec.metadata['source_code_uri'] = spec.homepage
17
+ spec.metadata['documentation_uri'] = 'https://www.rubydoc.info/gems/salesforce_streamer'
17
18
 
18
19
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
19
20
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(bin/|spec/|\.rub)}) }
@@ -24,8 +25,9 @@ Gem::Specification.new do |spec|
24
25
 
25
26
  spec.required_ruby_version = '>= 2.6'
26
27
 
27
- spec.add_dependency 'faye', '0.8.9'
28
- spec.add_dependency 'restforce', '~> 4.2'
28
+ spec.add_dependency 'dry-initializer', '~> 3.0'
29
+ spec.add_dependency 'faye', '~> 1.4'
30
+ spec.add_dependency 'restforce', '>= 4.2', '< 6.0'
29
31
 
30
32
  spec.add_development_dependency 'byebug'
31
33
  spec.add_development_dependency 'codecov'
metadata CHANGED
@@ -1,44 +1,64 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: salesforce_streamer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.2
4
+ version: 2.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scott Serok
8
8
  - RenoFi Engineering Team
9
- autorequire:
9
+ autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2020-08-03 00:00:00.000000000 Z
12
+ date: 2020-09-02 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: dry-initializer
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '3.0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '3.0'
14
28
  - !ruby/object:Gem::Dependency
15
29
  name: faye
16
30
  requirement: !ruby/object:Gem::Requirement
17
31
  requirements:
18
- - - '='
32
+ - - "~>"
19
33
  - !ruby/object:Gem::Version
20
- version: 0.8.9
34
+ version: '1.4'
21
35
  type: :runtime
22
36
  prerelease: false
23
37
  version_requirements: !ruby/object:Gem::Requirement
24
38
  requirements:
25
- - - '='
39
+ - - "~>"
26
40
  - !ruby/object:Gem::Version
27
- version: 0.8.9
41
+ version: '1.4'
28
42
  - !ruby/object:Gem::Dependency
29
43
  name: restforce
30
44
  requirement: !ruby/object:Gem::Requirement
31
45
  requirements:
32
- - - "~>"
46
+ - - ">="
33
47
  - !ruby/object:Gem::Version
34
48
  version: '4.2'
49
+ - - "<"
50
+ - !ruby/object:Gem::Version
51
+ version: '6.0'
35
52
  type: :runtime
36
53
  prerelease: false
37
54
  version_requirements: !ruby/object:Gem::Requirement
38
55
  requirements:
39
- - - "~>"
56
+ - - ">="
40
57
  - !ruby/object:Gem::Version
41
58
  version: '4.2'
59
+ - - "<"
60
+ - !ruby/object:Gem::Version
61
+ version: '6.0'
42
62
  - !ruby/object:Gem::Dependency
43
63
  name: byebug
44
64
  requirement: !ruby/object:Gem::Requirement
@@ -137,7 +157,7 @@ dependencies:
137
157
  - - ">="
138
158
  - !ruby/object:Gem::Version
139
159
  version: '0'
140
- description:
160
+ description:
141
161
  email:
142
162
  - scott@renofi.com
143
163
  - engineering@renofi.com
@@ -155,6 +175,7 @@ files:
155
175
  - ".gitignore"
156
176
  - ".rspec"
157
177
  - ".travis.yml"
178
+ - CHANGELOG.md
158
179
  - Gemfile
159
180
  - Gemfile.lock
160
181
  - LICENSE.txt
@@ -169,12 +190,10 @@ files:
169
190
  - lib/salesforce_streamer/launcher.rb
170
191
  - lib/salesforce_streamer/log.rb
171
192
  - lib/salesforce_streamer/push_topic.rb
172
- - lib/salesforce_streamer/redis_replay.rb
173
193
  - lib/salesforce_streamer/replay_id_error_extension.rb
174
- - lib/salesforce_streamer/replay_persistence.rb
175
194
  - lib/salesforce_streamer/salesforce_client.rb
195
+ - lib/salesforce_streamer/salesforce_topic_manager.rb
176
196
  - lib/salesforce_streamer/server.rb
177
- - lib/salesforce_streamer/topic_manager.rb
178
197
  - lib/salesforce_streamer/version.rb
179
198
  - salesforce_streamer.gemspec
180
199
  homepage: https://github.com/renofi/salesforce_streamer
@@ -183,7 +202,8 @@ licenses:
183
202
  metadata:
184
203
  homepage_uri: https://github.com/renofi/salesforce_streamer
185
204
  source_code_uri: https://github.com/renofi/salesforce_streamer
186
- post_install_message:
205
+ documentation_uri: https://www.rubydoc.info/gems/salesforce_streamer
206
+ post_install_message:
187
207
  rdoc_options: []
188
208
  require_paths:
189
209
  - lib
@@ -199,7 +219,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
199
219
  version: '0'
200
220
  requirements: []
201
221
  rubygems_version: 3.1.2
202
- signing_key:
222
+ signing_key:
203
223
  specification_version: 4
204
224
  summary: A wrapper around the Restforce Streaming API with a built-in PushTopic manager.
205
225
  test_files: []
@@ -1,55 +0,0 @@
1
- module SalesforceStreamer
2
- class RedisReplay
3
- class << self
4
- def redis_connection
5
- @redis_connection ||= Configuration.instance.redis_connection || fail(RedisConnectionError)
6
- end
7
-
8
- attr_writer :redis_connection
9
- end
10
-
11
- def connection
12
- if RedisReplay.redis_connection.respond_to?(:with)
13
- RedisReplay.redis_connection.with do |conn|
14
- yield(conn)
15
- end
16
- else
17
- yield RedisReplay.redis_connection
18
- end
19
- end
20
-
21
- # Saves the value to a key with expiration
22
- def record(key, value)
23
- return unless key && value
24
-
25
- key = namespaced_key(key)
26
- value = Integer(value)
27
- connection { |c| c.setex key, SECONDS_TO_EXPIRE, value }
28
- rescue StandardError => e
29
- Configuration.instance.exception_adapter.call e
30
- nil
31
- end
32
-
33
- def retrieve(key)
34
- return unless key
35
-
36
- key = namespaced_key(key)
37
- value = connection { |c| c.get key }
38
- Integer(value) if value
39
- rescue StandardError => e
40
- Configuration.instance.exception_adapter.call e
41
- nil
42
- end
43
-
44
- private
45
-
46
- def namespaced_key(key)
47
- NAMESPACE + key.to_s
48
- end
49
-
50
- NAMESPACE = 'SalesforceStreamer:'.freeze
51
- SECONDS_TO_EXPIRE = 24 * 60 * 60 # 24 hours
52
- START = 0
53
- STOP = 0
54
- end
55
- end
@@ -1,18 +0,0 @@
1
- module SalesforceStreamer
2
- # Store values for a given key in a sorted sequence
3
- # Retrieves the highest value given a key
4
- class ReplayPersistence
5
- class << self
6
- def record(key, value)
7
- Log.debug { "Recording #{key}=#{value}" }
8
- Configuration.instance.persistence_adapter&.record key, value
9
- end
10
-
11
- def retrieve(key)
12
- Configuration.instance.persistence_adapter&.retrieve(key).tap do |v|
13
- Log.debug { "Retrieved for #{key} #{v || 'nil'}" }
14
- end
15
- end
16
- end
17
- end
18
- end