pub_sub_model_sync 0.3.0 → 0.4.2.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 04acaa2b5befa05fd52978367afdb24c6a1a5858e10441620a2860a57ea9cd55
4
- data.tar.gz: 25297748dd5e1099f9f251d73f7f94c0680e9c04820c3396143f9b3ca17170ad
3
+ metadata.gz: 30ddd2acf4ff439f66a35b747794b606dbe7949c31fe3f9bf064e87e718c8e48
4
+ data.tar.gz: 95724a2190dd0cdb8d9a4ef4e841c90462cd53794dbf5cde7c04e2699171f55a
5
5
  SHA512:
6
- metadata.gz: dea58e39fd6d77599000a932f7db942052d5309069c8adb0e571fcb2b7be4a5d4429048371b5bac51c18ff9f016dc88fc0e85ea73ca04bc93f36536c580ea544
7
- data.tar.gz: cead04daf416641b5d900d4c8c6a13509c49fa8256dd343552c70b578f5af3a3ba39465069999688418e4de60ba4f9a62b4e40e3b54c0c55dd89dcf366f28ab2
6
+ metadata.gz: 417260220e8c7c89a0fd37a23814d4b5436f29218bd212541b11e9ec1f815cb283fc8f1a1d579dd23ee5ada60c8138cc6a53515ddea2a0ec7dd75c5ee59c2be3
7
+ data.tar.gz: ded28709d868ec234553ac1d47927d48423f909126fc1a4b119d5cb98801181927883ae1b0b7e01c979b0225bafbd6685f2f7b9d843722da673e3d63760b8057
@@ -6,28 +6,45 @@ on:
6
6
  - master
7
7
  pull_request:
8
8
 
9
-
10
9
  jobs:
11
10
  build:
12
11
  name: Tests and Code Style
13
12
  runs-on: ubuntu-latest
13
+ strategy:
14
+ matrix:
15
+ ruby: [2.4, 2.5, 2.6]
16
+ rails: [4, 5, 6]
17
+ include:
18
+ - ruby: 2.7
19
+ rails: 6
20
+ exclude: # rails 6 requires ruby >= 2.5
21
+ - ruby: 2.4
22
+ rails: 6
14
23
 
15
24
  steps:
16
25
  - uses: actions/checkout@v2
17
- - name: Set up Ruby 2.6
26
+ - name: Set up Ruby
18
27
  uses: actions/setup-ruby@v1
19
28
  with:
20
- ruby-version: 2.6.x
21
-
29
+ ruby-version: ${{ matrix.ruby }}
22
30
  - name: Install sqlite3
23
31
  run: sudo apt-get install libsqlite3-dev
24
32
 
25
- - name: Bundle install
33
+ - name: Install bundler
34
+ env:
35
+ GEMFILE_PATH: gemfiles/Gemfile_${{ matrix.rails }}
36
+ RAILS_V: ${{ matrix.rails }}
26
37
  run: |
27
- gem install bundler
28
- bundle install --jobs 4 --retry 3
38
+ rm -f Gemfile.lock && rm -f Gemfile
39
+ cp $GEMFILE_PATH ./Gemfile
40
+ bundler_v='2.1.4'
41
+ if [ $RAILS_V = "4" ]; then bundler_v="1.16.6"; fi
42
+ gem install bundler -v "~> $bundler_v"
43
+ bundle _${bundler_v}_ install --jobs 4 --retry 3
44
+
29
45
  - name: Tests (rspec)
30
46
  run: |
31
47
  bundle exec rspec
48
+
32
49
  - name: Code style (Rubocop)
33
50
  run: bundle exec rubocop
@@ -1,17 +1,19 @@
1
1
  # This is the configuration used to check the rubocop source code.
2
2
 
3
3
  AllCops:
4
+ TargetRubyVersion: 2.4
4
5
  Exclude:
5
6
  - 'spec/spec_helper.rb'
6
7
  - 'Gemfile'
7
8
  - 'Rakefile'
8
9
  - 'bin/*'
9
- TargetRubyVersion: 2.3
10
10
 
11
11
  Metrics/BlockLength:
12
12
  Exclude:
13
13
  - 'spec/**/*.rb'
14
14
 
15
+ Layout/LineLength:
16
+ Max: 80
15
17
 
16
18
  Style/SymbolArray:
17
19
  Exclude:
@@ -1,5 +1,28 @@
1
1
  # Change Log
2
2
 
3
+ # 0.4.2.1 (August 20, 2020)
4
+ - Improve ```ps_subscriber_changed?``` to run validations and check for changes
5
+
6
+ # 0.4.2 (May 12, 2020)
7
+ - chore: remove typo
8
+
9
+ # 0.4.1 (May 12, 2020)
10
+ - chore: improve log messages
11
+ - feat: do not update model if no changes
12
+ - feat: skip publisher after updating if no changes
13
+
14
+
15
+ # 0.4.0 (May 06, 2020)
16
+ - rename as_klass to from_klass and as_action to from_action for subscribers
17
+ - refactor subscribers to be independent
18
+ - refactor message_publisher to use publisher
19
+ - rename publisher into message_publisher
20
+ - reformat publisher to reuse connector
21
+
22
+ # 0.3.1 (May 05, 2020)
23
+ - improve rabbit service to use sleep instead of block ("Block is not recommended for production")
24
+ - improve message ID
25
+
3
26
  # 0.3.0 (April 29, 2020)
4
27
  - Support for multiple identifiers when syncing
5
28
  - Add klass.ps_find_model method for a custom model finder
@@ -1,8 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pub_sub_model_sync (0.3.0)
5
- activesupport
4
+ pub_sub_model_sync (0.4.2.1)
6
5
  rails
7
6
 
8
7
  GEM
@@ -123,7 +122,7 @@ GEM
123
122
  concurrent-ruby (~> 1.0)
124
123
  jaro_winkler (1.5.4)
125
124
  jwt (2.2.1)
126
- loofah (2.5.0)
125
+ loofah (2.6.0)
127
126
  crass (~> 1.0.2)
128
127
  nokogiri (>= 1.5.9)
129
128
  mail (2.7.1)
@@ -132,21 +131,21 @@ GEM
132
131
  mimemagic (~> 0.3.2)
133
132
  memoist (0.16.2)
134
133
  method_source (1.0.0)
135
- mimemagic (0.3.4)
134
+ mimemagic (0.3.5)
136
135
  mini_mime (1.0.2)
137
136
  mini_portile2 (2.4.0)
138
137
  minitest (5.14.0)
139
138
  multi_json (1.14.1)
140
139
  multipart-post (2.1.1)
141
140
  nio4r (2.5.2)
142
- nokogiri (1.10.9)
141
+ nokogiri (1.10.10)
143
142
  mini_portile2 (~> 2.4.0)
144
143
  os (1.0.1)
145
144
  parallel (1.19.1)
146
145
  parser (2.7.0.4)
147
146
  ast (~> 2.4.0)
148
147
  public_suffix (4.0.3)
149
- rack (2.2.2)
148
+ rack (2.2.3)
150
149
  rack-test (1.1.0)
151
150
  rack (>= 1.0, < 3)
152
151
  rails (6.0.2.2)
@@ -208,7 +207,7 @@ GEM
208
207
  faraday (~> 0.9)
209
208
  jwt (>= 1.5, < 3.0)
210
209
  multi_json (~> 1.10)
211
- sprockets (4.0.0)
210
+ sprockets (4.0.2)
212
211
  concurrent-ruby (~> 1.0)
213
212
  rack (> 1, < 3)
214
213
  sprockets-rails (3.2.1)
@@ -221,9 +220,9 @@ GEM
221
220
  tzinfo (1.2.7)
222
221
  thread_safe (~> 0.1)
223
222
  unicode-display_width (1.6.1)
224
- websocket-driver (0.7.1)
223
+ websocket-driver (0.7.3)
225
224
  websocket-extensions (>= 0.1.0)
226
- websocket-extensions (0.1.4)
225
+ websocket-extensions (0.1.5)
227
226
  zeitwerk (2.3.0)
228
227
 
229
228
  PLATFORMS
data/README.md CHANGED
@@ -61,6 +61,9 @@ And then execute: $ bundle install
61
61
  ```
62
62
  Note: Publishers do not need todo this
63
63
 
64
+ - Check the service status with:
65
+ ```PubSubModelSync::MessagePublisher.publish_data('Test message', {sample_value: 10}, :create)```
66
+
64
67
  ## Examples
65
68
  ```ruby
66
69
  # App 1 (Publisher)
@@ -86,7 +89,7 @@ User.create(name: 'test user', email: 'sample@gmail.com') # Review your App 2 to
86
89
  User.new(name: 'test user').ps_perform_sync(:create) # similar to above to perform sync on demand
87
90
 
88
91
  User.ps_class_publish({ msg: 'Hello' }, action: :greeting) # User.greeting method (Class method) will be called in App2
89
- PubSubModelSync::Publisher.new.publish_data(User, { msg: 'Hello' }, :greeting) # similar to above when not included publisher concern
92
+ PubSubModelSync::MessagePublisher.publish_data(User, { msg: 'Hello' }, :greeting) # similar to above when not included publisher concern
90
93
  ```
91
94
 
92
95
  ## Advanced Example
@@ -110,15 +113,15 @@ end
110
113
  class User < ActiveRecord::Base
111
114
  self.table_name = 'subscriber_users'
112
115
  include PubSubModelSync::SubscriberConcern
113
- ps_subscribe(%i[name], actions: %i[update], as_klass: 'Client', id: %i[client_id email])
114
- ps_class_subscribe(:greeting, as_action: :custom_greeting, as_klass: 'CustomUser')
116
+ ps_subscribe(%i[name], actions: %i[update], from_klass: 'Client', id: %i[client_id email])
117
+ ps_class_subscribe(:greeting, from_action: :custom_greeting, from_klass: 'CustomUser')
115
118
  alias_attribute :full_name, :name
116
119
 
117
120
  def self.greeting(data)
118
121
  puts 'Class message called through custom_greeting'
119
122
  end
120
123
 
121
- # def self.ps_find_model(data, settings)
124
+ # def self.ps_find_model(data)
122
125
  # where(email: data[:email], ...).first_or_initialize
123
126
  # end
124
127
  end
@@ -126,42 +129,39 @@ end
126
129
 
127
130
  Note: Be careful with collision of names
128
131
  ```
129
- class User
130
- # ps_publish %i[name_data:name name:key] # key will be replaced with name_data
131
- ps_publish %i[name_data:name key_data:key] # use alias to avoid collision
132
-
133
- def key_data
134
- name
135
- end
136
- end
132
+ # ps_publish %i[name_data:name name:key] # key will be replaced with name_data
133
+ ps_publish %i[name_data:name key_data:key] # use alias to avoid collision
137
134
  ```
138
135
 
139
136
  ## API
140
137
  ### Subscribers
141
- - Permit to configure class level listeners
142
- ```ps_class_subscribe(action_name, as_action: nil, as_klass: nil)```
143
- * as_action: (Optional) Source method name
144
- * as_klass: (Optional) Source class name
138
+ - Permit to configure class level subscriptions
139
+ ```ps_class_subscribe(action_name, from_action: nil, from_klass: nil)```
140
+ * from_action: (Optional) Source method name
141
+ * from_klass: (Optional) Source class name
145
142
 
146
- - Permit to configure instance level listeners (CRUD)
147
- ```ps_subscribe(attrs, as_klass: nil, actions: nil, id: nil)```
143
+ - Permit to configure instance level subscriptions (CRUD)
144
+ ```ps_subscribe(attrs, from_klass: nil, actions: nil, id: nil)```
148
145
  * attrs: (Array/Required) Array of all attributes to be synced
149
- * as_klass: (String/Optional) Source class name (Instead of the model class name, will use this value)
146
+ * from_klass: (String/Optional) Source class name (Instead of the model class name, will use this value)
150
147
  * actions: (Array/Optional, default: create/update/destroy) permit to customize action names
151
148
  * id: (Sym|Array/Optional, default: id) Attr identifier(s) to find the corresponding model
152
149
 
153
150
  - Permit to configure a custom model finder
154
- ```ps_find_model(data, settings)```
151
+ ```ps_find_model(data)```
155
152
  * data: (Hash) Data received from sync
156
- * settings: (Hash(:klass, :action)) Class and action name from sync
157
153
  Must return an existent or a new model object
158
154
 
159
155
  - Get crud subscription configured for the class
160
156
  ```User.ps_subscriber(action_name)```
161
157
  * action_name (default :create, :sym): can be :create, :update, :destroy
162
158
 
163
- - Inspect all configured listeners
164
- ```PubSubModelSync::Config.listeners```
159
+ - Inspect all configured subscribers
160
+ ```PubSubModelSync::Config.subscribers```
161
+
162
+ - Permit to customize the way to detect if the subscribed model was changed (Only for update action).
163
+ ```.ps_subscriber_changed?(data)```
164
+ By default: ```model.changed?```
165
165
 
166
166
  ### Publishers
167
167
  - Permit to configure crud publishers
@@ -172,6 +172,7 @@ end
172
172
 
173
173
  - Permit to cancel sync called after create/update/destroy (Before initializing sync service)
174
174
  ```model.ps_skip_callback?(action)```
175
+ Default: False
175
176
  Note: Return true to cancel sync
176
177
 
177
178
  - Callback called before preparing data for sync (Permit to stop sync)
@@ -198,7 +199,7 @@ end
198
199
  * as_klass: (optional, :string) Custom class name (Default current model name)
199
200
 
200
201
  - Publish a class level notification (Same as above: on demand call)
201
- ```PubSubModelSync::Publisher.new.publish_data(Klass_name, data, action_name)```
202
+ ```PubSubModelSync::MessagePublisher.publish_data(Klass_name, data, action_name)```
202
203
  * klass_name: (required, Class) same class name as defined in ps_class_subscribe(...)
203
204
  * data: (required, :hash) message value to deliver
204
205
  * action_name: (required, :sim) same action name as defined in ps_class_subscribe(...)
@@ -254,23 +255,29 @@ end
254
255
 
255
256
  # Publisher
256
257
  it 'publish model action' do
257
- publisher = PubSubModelSync::Publisher
258
+ publisher = PubSubModelSync::MessagePublisher
258
259
  data = { name: 'hello'}
259
260
  action = :create
260
261
  User.ps_class_publish(data, action: action)
261
262
  user = User.create(name: 'name', email: 'email')
262
- expect_any_instance_of(publisher).to receive(:publish_model).with(user, :create, anything)
263
+ expect(publisher).to receive(:publish_model).with(user, :create, anything)
263
264
  end
264
265
 
265
266
  it 'publish class message' do
266
- publisher = PubSubModelSync::Publisher
267
+ publisher = PubSubModelSync::MessagePublisher
267
268
  data = {msg: 'hello'}
268
269
  action = :greeting
269
270
  User.ps_class_publish(data, action: action)
270
- expect_any_instance_of(publisher).to receive(:publish_data).with('User', data, action)
271
+ expect(publisher).to receive(:publish_data).with('User', data, action)
271
272
  end
272
273
  ```
273
274
 
275
+ ## TODO
276
+ - Hooks/callbacks when message processed or failed
277
+ - Add alias attributes when subscribing (similar to publisher)
278
+ - Add flag ```model.ps_processing``` to indicate that the current transaction is being processed by pub/sub
279
+
280
+
274
281
  ## Contributing
275
282
 
276
283
  Bug reports and pull requests are welcome on GitHub at https://github.com/owen2345/pub_sub_model_sync. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
@@ -0,0 +1,16 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem 'rubocop'
4
+ gem 'bunny' # rabbit-mq
5
+ gem 'google-cloud-pubsub' # google pub/sub
6
+ gem 'ruby-kafka' # kafka pub/sub
7
+ gem 'rails', '~> 4'
8
+ gem 'bundler'
9
+ gem 'sqlite3', '1.3.13'
10
+
11
+ group :test do
12
+ gem 'database_cleaner-active_record'
13
+ end
14
+
15
+ # Specify your gem's dependencies in pub_sub_model_sync.gemspec
16
+ gemspec
@@ -0,0 +1,14 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem 'rubocop'
4
+ gem 'bunny' # rabbit-mq
5
+ gem 'google-cloud-pubsub' # google pub/sub
6
+ gem 'ruby-kafka' # kafka pub/sub
7
+ gem 'rails', '~> 5'
8
+
9
+ group :test do
10
+ gem 'database_cleaner-active_record'
11
+ end
12
+
13
+ # Specify your gem's dependencies in pub_sub_model_sync.gemspec
14
+ gemspec
@@ -0,0 +1,14 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem 'rubocop'
4
+ gem 'bunny' # rabbit-mq
5
+ gem 'google-cloud-pubsub' # google pub/sub
6
+ gem 'ruby-kafka' # kafka pub/sub
7
+ gem 'rails', '~> 6'
8
+
9
+ group :test do
10
+ gem 'database_cleaner-active_record'
11
+ end
12
+
13
+ # Specify your gem's dependencies in pub_sub_model_sync.gemspec
14
+ gemspec
@@ -6,12 +6,15 @@ require 'active_support'
6
6
  require 'pub_sub_model_sync/railtie'
7
7
  require 'pub_sub_model_sync/config'
8
8
  require 'pub_sub_model_sync/subscriber_concern'
9
- require 'pub_sub_model_sync/publisher'
9
+ require 'pub_sub_model_sync/message_publisher'
10
10
  require 'pub_sub_model_sync/publisher_concern'
11
11
  require 'pub_sub_model_sync/runner'
12
12
  require 'pub_sub_model_sync/connector'
13
13
  require 'pub_sub_model_sync/message_processor'
14
14
 
15
+ require 'pub_sub_model_sync/publisher'
16
+ require 'pub_sub_model_sync/subscriber'
17
+
15
18
  require 'pub_sub_model_sync/service_base'
16
19
  require 'pub_sub_model_sync/service_google'
17
20
  require 'pub_sub_model_sync/service_rabbit'
@@ -2,7 +2,7 @@
2
2
 
3
3
  module PubSubModelSync
4
4
  class Config
5
- cattr_accessor(:listeners) { [] }
5
+ cattr_accessor(:subscribers) { [] }
6
6
  cattr_accessor(:publishers) { [] }
7
7
  cattr_accessor(:service_name) { :google }
8
8
  cattr_accessor :logger
@@ -2,91 +2,39 @@
2
2
 
3
3
  module PubSubModelSync
4
4
  class MessageProcessor
5
- attr_accessor :data, :settings, :message_id
5
+ attr_accessor :data, :klass, :action
6
6
 
7
7
  # @param data (Hash): any hash value to deliver
8
8
  def initialize(data, klass, action)
9
9
  @data = data
10
- @settings = { klass: klass, action: action }
11
- @message_id = [klass, action, Time.current.to_i].join('-')
10
+ @klass = klass
11
+ @action = action
12
12
  end
13
13
 
14
14
  def process
15
- @failed = false
16
- log "processing message: #{[data, settings]}"
17
- listeners = filter_listeners
18
- return log 'Skipped: No listeners' unless listeners.any?
19
-
20
- eval_message(listeners)
21
- log 'processed message' unless @failed
15
+ subscribers = filter_subscribers
16
+ subscribers.each { |subscriber| run_subscriber(subscriber) }
22
17
  end
23
18
 
24
19
  private
25
20
 
26
- def eval_message(listeners)
27
- listeners.each do |listener|
28
- if listener[:direct_mode]
29
- call_class_listener(listener)
30
- else
31
- call_listener(listener)
32
- end
33
- end
34
- end
35
-
36
- def call_class_listener(listener)
37
- model_class = listener[:klass].constantize
38
- model_class.send(listener[:action], data)
21
+ def run_subscriber(subscriber)
22
+ subscriber.eval_message(data)
23
+ log "processed message with: #{[klass, action, data]}"
39
24
  rescue => e
40
- log("Error listener (#{listener}): #{e.message}", :error)
41
- @failed = true
42
- end
43
-
44
- # support for: create, update, destroy
45
- def call_listener(listener)
46
- model = find_model(listener)
47
- if settings[:action].to_sym == :destroy
48
- model.destroy!
49
- else
50
- populate_model(model, listener)
51
- model.save!
52
- end
53
- rescue => e
54
- log("Error listener (#{listener}): #{e.message}", :error)
55
- @failed = true
56
- end
57
-
58
- def find_model(listener)
59
- model_class = listener[:klass].constantize
60
- if model_class.respond_to?(:ps_find_model)
61
- return model_class.ps_find_model(data, settings)
62
- end
63
-
64
- model_class.where(model_identifiers(listener)).first_or_initialize
65
- end
66
-
67
- def model_identifiers(listener)
68
- identifiers = listener[:settings][:id]
69
- identifiers = [identifiers] unless identifiers.is_a?(Array)
70
- identifiers.map { |key| [key, data[key.to_sym]] }.to_h
71
- end
72
-
73
- def populate_model(model, listener)
74
- values = data.slice(*listener[:settings][:attrs])
75
- values.each do |attr, value|
76
- model.send("#{attr}=", value)
77
- end
25
+ info = [klass, action, data, e.message, e.backtrace]
26
+ log("error processing message: #{info}", :error)
78
27
  end
79
28
 
80
- def filter_listeners
81
- listeners = PubSubModelSync::Config.listeners
82
- listeners.select do |listener|
83
- listener[:as_klass].to_s == settings[:klass].to_s &&
84
- listener[:as_action].to_s == settings[:action].to_s
29
+ def filter_subscribers
30
+ PubSubModelSync::Config.subscribers.select do |subscriber|
31
+ subscriber.settings[:from_klass].to_s == klass.to_s &&
32
+ subscriber.settings[:from_action].to_s == action.to_s
85
33
  end
86
34
  end
87
35
 
88
36
  def log(message, kind = :info)
89
- PubSubModelSync::Config.log "(ID: #{message_id}) #{message}", kind
37
+ PubSubModelSync::Config.log message, kind
90
38
  end
91
39
  end
92
40
  end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PubSubModelSync
4
+ class MessagePublisher
5
+ class << self
6
+ delegate :publish, to: :connector
7
+
8
+ def connector
9
+ @connector ||= PubSubModelSync::Connector.new
10
+ end
11
+
12
+ def publish_data(klass, data, action)
13
+ attrs = { klass: klass.to_s, action: action.to_sym }
14
+ publish(data, attrs)
15
+ end
16
+
17
+ # @param model: ActiveRecord model
18
+ # @param action: (Sym) Action name
19
+ # @param publisher: (Publisher, optional) Publisher to be used
20
+ def publish_model(model, action, publisher = nil)
21
+ return if model.ps_skip_sync?(action)
22
+
23
+ publisher ||= model.class.ps_publisher(action)
24
+ payload = publisher.payload(model, action)
25
+ res_before = model.ps_before_sync(action, payload[:data])
26
+ return if res_before == :cancel
27
+
28
+ publish(payload[:data], payload[:attrs])
29
+ model.ps_after_sync(action, payload[:data])
30
+ end
31
+ end
32
+ end
33
+ end
@@ -2,42 +2,24 @@
2
2
 
3
3
  module PubSubModelSync
4
4
  class Publisher
5
- attr_accessor :connector
6
- delegate :publish, to: :connector
7
-
8
- def initialize
9
- @connector = PubSubModelSync::Connector.new
10
- end
11
-
12
- def publish_data(klass, data, action)
13
- attributes = self.class.build_attrs(klass, action)
14
- publish(data, attributes)
5
+ attr_accessor :attrs, :actions, :klass, :as_klass
6
+ def initialize(attrs, klass, actions = nil, as_klass = nil)
7
+ @attrs = attrs
8
+ @klass = klass
9
+ @actions = actions || %i[create update destroy]
10
+ @as_klass = as_klass || klass
15
11
  end
16
12
 
17
- # @param custom_settings (Hash): { attrs: [], as_klass: nil }
18
- def publish_model(model, action, custom_settings = {})
19
- return if model.ps_skip_sync?(action)
20
-
21
- settings = model.class.ps_publisher(action).merge(custom_settings)
22
- attributes = build_model_attrs(model, action, settings)
23
- data = build_model_data(model, settings[:attrs])
24
- res_before = model.ps_before_sync(action, data)
25
- return if res_before == :cancel
26
-
27
- publish(data.symbolize_keys, attributes)
28
- model.ps_after_sync(action, data)
29
- end
30
-
31
- def self.build_attrs(klass, action)
32
- { klass: klass.to_s, action: action.to_sym }
13
+ def payload(model, action)
14
+ { data: payload_data(model), attrs: payload_attrs(model, action) }
33
15
  end
34
16
 
35
17
  private
36
18
 
37
- def build_model_data(model, model_props)
38
- source_props = model_props.map { |prop| prop.to_s.split(':').first }
19
+ def payload_data(model)
20
+ source_props = @attrs.map { |prop| prop.to_s.split(':').first }
39
21
  data = model.as_json(only: source_props, methods: source_props)
40
- aliased_props = model_props.select { |prop| prop.to_s.include?(':') }
22
+ aliased_props = @attrs.select { |prop| prop.to_s.include?(':') }
41
23
  aliased_props.each do |prop|
42
24
  source, target = prop.to_s.split(':')
43
25
  data[target] = data.delete(source)
@@ -45,13 +27,8 @@ module PubSubModelSync
45
27
  data.symbolize_keys
46
28
  end
47
29
 
48
- def build_model_attrs(model, action, settings)
49
- as_klass = (settings[:as_klass] || model.class.name).to_s
50
- self.class.build_attrs(as_klass, action)
51
- end
52
-
53
- def log(msg)
54
- PubSubModelSync::Config.log(msg)
30
+ def payload_attrs(model, action)
31
+ { klass: (as_klass || model.class.name).to_s, action: action.to_sym }
55
32
  end
56
33
  end
57
34
  end
@@ -11,6 +11,7 @@ module PubSubModelSync
11
11
  false
12
12
  end
13
13
 
14
+ # TODO: make it using respond_to?(:ps_skip_sync?)
14
15
  # before preparing data to sync
15
16
  def ps_skip_sync?(_action)
16
17
  false
@@ -23,49 +24,49 @@ module PubSubModelSync
23
24
  def ps_after_sync(_action, _data); end
24
25
 
25
26
  # To perform sync on demand
26
- # @param custom_settings (Hash): { attrs: [], as_klass: nil }
27
- def ps_perform_sync(action = :create, custom_settings = {})
28
- service = self.class.ps_publisher_service
29
- model_settings = self.class.ps_publisher(action) || {}
30
- service.publish_model(self, action, model_settings.merge(custom_settings))
27
+ # @param attrs (Array, optional): custom attrs to be used
28
+ # @param as_klass (Array, optional): custom klass name to be used
29
+ # @param publisher (Publisher, optional): custom publisher object
30
+ def ps_perform_sync(action = :create, attrs: nil, as_klass: nil,
31
+ publisher: nil)
32
+ publisher ||= self.class.ps_publisher(action).dup
33
+ publisher.attrs = attrs if attrs
34
+ publisher.as_klass = as_klass if as_klass
35
+ PubSubModelSync::MessagePublisher.publish_model(self, action, publisher)
31
36
  end
32
37
 
33
38
  module ClassMethods
34
39
  # Permit to configure to publish crud actions (:create, :update, :destroy)
35
40
  def ps_publish(attrs, actions: %i[create update destroy], as_klass: nil)
36
- as_klass ||= name
41
+ klass = PubSubModelSync::Publisher
42
+ publisher = klass.new(attrs, name, actions, as_klass)
43
+ PubSubModelSync::Config.publishers << publisher
37
44
  actions.each do |action|
38
- info = { klass: name, action: action, attrs: attrs,
39
- as_klass: as_klass }
40
- PubSubModelSync::Config.publishers << info
41
- ps_register_callback(action.to_sym, info)
45
+ ps_register_callback(action.to_sym, publisher)
42
46
  end
43
47
  end
44
48
 
45
49
  # On demand class level publisher
46
50
  def ps_class_publish(data, action:, as_klass: nil)
47
51
  as_klass = (as_klass || name).to_s
48
- ps_publisher_service.publish_data(as_klass, data, action.to_sym)
52
+ klass = PubSubModelSync::MessagePublisher
53
+ klass.publish_data(as_klass, data, action.to_sym)
49
54
  end
50
55
 
51
56
  # Publisher info for specific action
52
57
  def ps_publisher(action = :create)
53
- PubSubModelSync::Config.publishers.find do |listener|
54
- listener[:klass] == name && listener[:action] == action
58
+ PubSubModelSync::Config.publishers.find do |publisher|
59
+ publisher.klass == name && publisher.actions.include?(action)
55
60
  end
56
61
  end
57
62
 
58
- def ps_publisher_service
59
- PubSubModelSync::Publisher.new
60
- end
61
-
62
63
  private
63
64
 
64
- def ps_register_callback(action, info)
65
+ def ps_register_callback(action, publisher)
65
66
  after_commit(on: action) do |model|
66
67
  unless model.ps_skip_callback?(action)
67
- service = model.class.ps_publisher_service
68
- service.publish_model(model, action.to_sym, info)
68
+ klass = PubSubModelSync::MessagePublisher
69
+ klass.publish_model(model, action.to_sym, publisher)
69
70
  end
70
71
  end
71
72
  end
@@ -19,7 +19,7 @@ module PubSubModelSync
19
19
  private
20
20
 
21
21
  # @param payload (String JSON): '{"data":{}, "attributes":{..}}'
22
- # refer: PubSubModelSync::Publisher (.publish_model | .publish_data)
22
+ # refer: PubSubModelSync::MessagePublisher(.publish_model | .publish_data)
23
23
  def perform_message(payload)
24
24
  data, attrs = parse_message_payload(payload)
25
25
  args = [data, attrs[:klass], attrs[:action]]
@@ -29,10 +29,12 @@ module PubSubModelSync
29
29
  end
30
30
 
31
31
  def publish(data, attributes)
32
- log("Publishing message: #{[data, attributes]}")
33
-
32
+ log("Publishing message: #{[attributes, data]}")
34
33
  payload = { data: data, attributes: attributes }.to_json
35
34
  topic.publish(payload, { SERVICE_KEY => true })
35
+ rescue => e
36
+ info = [attributes, data, e.message, e.backtrace]
37
+ log("Error publishing: #{info}", :error)
36
38
  end
37
39
 
38
40
  def stop
@@ -29,12 +29,12 @@ module PubSubModelSync
29
29
  end
30
30
 
31
31
  def publish(data, attributes)
32
- log("Publishing: #{[data, attributes]}")
32
+ log("Publishing: #{[attributes, data]}")
33
33
  payload = { data: data, attributes: attributes }
34
34
  producer.produce(payload.to_json, message_settings)
35
35
  producer.deliver_messages
36
36
  rescue => e
37
- info = [data, attributes, e.message, e.backtrace]
37
+ info = [attributes, data, e.message, e.backtrace]
38
38
  log("Error publishing: #{info}", :error)
39
39
  end
40
40
 
@@ -19,7 +19,8 @@ module PubSubModelSync
19
19
  log('Listener starting...')
20
20
  subscribe_to_queue
21
21
  log('Listener started')
22
- queue.subscribe(block: true, manual_ack: false, &method(:process_message))
22
+ queue.subscribe(subscribe_settings, &method(:process_message))
23
+ loop { sleep 5 }
23
24
  rescue PubSubModelSync::Runner::ShutDown
24
25
  raise
25
26
  rescue => e
@@ -27,7 +28,7 @@ module PubSubModelSync
27
28
  end
28
29
 
29
30
  def publish(data, attributes)
30
- log("Publishing: #{[data, attributes]}")
31
+ log("Publishing: #{[attributes, data]}")
31
32
  deliver_data(data, attributes)
32
33
  # TODO: max retry
33
34
  rescue Timeout::Error => e
@@ -35,7 +36,7 @@ module PubSubModelSync
35
36
  initialize
36
37
  retry
37
38
  rescue => e
38
- info = [data, attributes, e.message, e.backtrace]
39
+ info = [attributes, data, e.message, e.backtrace]
39
40
  log("Error publishing: #{info}", :error)
40
41
  end
41
42
 
@@ -50,6 +51,10 @@ module PubSubModelSync
50
51
  { routing_key: queue.name, type: SERVICE_KEY }
51
52
  end
52
53
 
54
+ def subscribe_settings
55
+ { manual_ack: false }
56
+ end
57
+
53
58
  def process_message(_delivery_info, meta_info, payload)
54
59
  return unless meta_info[:type] == SERVICE_KEY
55
60
 
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PubSubModelSync
4
+ class Subscriber
5
+ attr_accessor :klass, :action, :attrs, :settings
6
+
7
+ # @param settings: (Hash) { id: :id, direct_mode: false,
8
+ # from_klass: klass, from_action: action }
9
+ def initialize(klass, action, attrs: nil, settings: {})
10
+ def_settings = { id: :id, direct_mode: false,
11
+ from_klass: klass, from_action: action }
12
+ @klass = klass
13
+ @action = action
14
+ @attrs = attrs
15
+ @settings = def_settings.merge(settings)
16
+ end
17
+
18
+ def eval_message(message)
19
+ if settings[:direct_mode]
20
+ run_class_message(message)
21
+ else
22
+ run_model_message(message)
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def run_class_message(message)
29
+ model_class = klass.constantize
30
+ model_class.send(action, message)
31
+ end
32
+
33
+ # support for: create, update, destroy
34
+ def run_model_message(message)
35
+ model = find_model(message)
36
+ if action == :destroy
37
+ model.destroy!
38
+ else
39
+ populate_model(model, message)
40
+ return if action == :update && !model.ps_subscriber_changed?(message)
41
+
42
+ model.save!
43
+ end
44
+ end
45
+
46
+ def find_model(message)
47
+ model_class = klass.constantize
48
+ if model_class.respond_to?(:ps_find_model)
49
+ return model_class.ps_find_model(message)
50
+ end
51
+
52
+ model_class.where(model_identifiers(message)).first_or_initialize
53
+ end
54
+
55
+ def model_identifiers(message)
56
+ identifiers = Array(settings[:id])
57
+ identifiers.map { |key| [key, message[key.to_sym]] }.to_h
58
+ end
59
+
60
+ def populate_model(model, message)
61
+ values = message.slice(*attrs)
62
+ values.each do |attr, value|
63
+ model.send("#{attr}=", value)
64
+ end
65
+ end
66
+ end
67
+ end
@@ -6,38 +6,41 @@ module PubSubModelSync
6
6
  base.extend(ClassMethods)
7
7
  end
8
8
 
9
+ # check if model was changed to skip nonsense .update!()
10
+ def ps_subscriber_changed?(_data)
11
+ validate
12
+ changed?
13
+ end
14
+
9
15
  module ClassMethods
10
- def ps_subscribe(attrs, as_klass: nil, actions: nil, id: :id)
16
+ def ps_subscribe(attrs, actions: nil, from_klass: name, id: :id)
17
+ settings = { id: id, from_klass: from_klass }
11
18
  actions ||= %i[create update destroy]
12
- settings = { attrs: attrs, id: id }
13
19
  actions.each do |action|
14
- add_ps_subscriber(as_klass, action, action, false, settings)
20
+ add_ps_subscriber(action, attrs, settings)
15
21
  end
16
22
  end
17
23
 
18
- def ps_class_subscribe(action, as_action: nil, as_klass: nil)
19
- add_ps_subscriber(as_klass, action, as_action, true, {})
24
+ def ps_class_subscribe(action, from_action: nil, from_klass: nil)
25
+ settings = { direct_mode: true }
26
+ settings[:from_action] = from_action if from_action
27
+ settings[:from_klass] = from_klass if from_klass
28
+ add_ps_subscriber(action, nil, settings)
20
29
  end
21
30
 
22
31
  def ps_subscriber(action = :create)
23
- PubSubModelSync::Config.listeners.find do |listener|
24
- listener[:klass] == name && listener[:action] == action
32
+ PubSubModelSync::Config.subscribers.find do |subscriber|
33
+ subscriber.klass == name && subscriber.action == action
25
34
  end
26
35
  end
27
36
 
28
37
  private
29
38
 
30
- # @param settings (Hash): { id:, attrs: }
31
- def add_ps_subscriber(as_klass, action, as_action, direct_mode, settings)
32
- listener = {
33
- klass: name,
34
- as_klass: (as_klass || name).to_s,
35
- action: action.to_sym,
36
- as_action: (as_action || action).to_sym,
37
- direct_mode: direct_mode,
38
- settings: settings
39
- }
40
- PubSubModelSync::Config.listeners.push(listener) && listener
39
+ # @param settings (Hash): refer to PubSubModelSync::Subscriber.settings
40
+ def add_ps_subscriber(action, attrs, settings = {})
41
+ klass = PubSubModelSync::Subscriber
42
+ subscriber = klass.new(name, action, attrs: attrs, settings: settings)
43
+ PubSubModelSync::Config.subscribers.push(subscriber) && subscriber
41
44
  end
42
45
  end
43
46
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PubSubModelSync
4
- VERSION = '0.3.0'
4
+ VERSION = '0.4.2.1'
5
5
  end
@@ -5,6 +5,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
  require 'pub_sub_model_sync/version'
6
6
 
7
7
  Gem::Specification.new do |spec|
8
+ spec.required_ruby_version = '>= 2.4'
8
9
  spec.name = 'pub_sub_model_sync'
9
10
  spec.version = PubSubModelSync::VERSION
10
11
  spec.authors = ['Owen']
@@ -32,7 +33,6 @@ Gem::Specification.new do |spec|
32
33
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
33
34
  spec.require_paths = ['lib']
34
35
 
35
- spec.add_dependency 'activesupport'
36
36
  spec.add_dependency 'rails'
37
37
 
38
38
  spec.add_development_dependency 'bundler'
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pub_sub_model_sync
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Owen
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-04-29 00:00:00.000000000 Z
11
+ date: 2020-08-20 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: activesupport
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '0'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: rails
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -103,15 +89,6 @@ extra_rdoc_files: []
103
89
  files:
104
90
  - ".github/workflows/ruby.yml"
105
91
  - ".gitignore"
106
- - ".idea/.gitignore"
107
- - ".idea/.rakeTasks"
108
- - ".idea/codeStyles/codeStyleConfig.xml"
109
- - ".idea/encodings.xml"
110
- - ".idea/inspectionProfiles/Project_Default.xml"
111
- - ".idea/misc.xml"
112
- - ".idea/modules.xml"
113
- - ".idea/pub_sub_model_sync.iml"
114
- - ".idea/vcs.xml"
115
92
  - ".rspec"
116
93
  - ".rubocop.yml"
117
94
  - CHANGELOG.md
@@ -123,10 +100,14 @@ files:
123
100
  - Rakefile
124
101
  - bin/console
125
102
  - bin/setup
103
+ - gemfiles/Gemfile_4
104
+ - gemfiles/Gemfile_5
105
+ - gemfiles/Gemfile_6
126
106
  - lib/pub_sub_model_sync.rb
127
107
  - lib/pub_sub_model_sync/config.rb
128
108
  - lib/pub_sub_model_sync/connector.rb
129
109
  - lib/pub_sub_model_sync/message_processor.rb
110
+ - lib/pub_sub_model_sync/message_publisher.rb
130
111
  - lib/pub_sub_model_sync/mock_google_service.rb
131
112
  - lib/pub_sub_model_sync/mock_kafka_service.rb
132
113
  - lib/pub_sub_model_sync/mock_rabbit_service.rb
@@ -138,6 +119,7 @@ files:
138
119
  - lib/pub_sub_model_sync/service_google.rb
139
120
  - lib/pub_sub_model_sync/service_kafka.rb
140
121
  - lib/pub_sub_model_sync/service_rabbit.rb
122
+ - lib/pub_sub_model_sync/subscriber.rb
141
123
  - lib/pub_sub_model_sync/subscriber_concern.rb
142
124
  - lib/pub_sub_model_sync/tasks/worker.rake
143
125
  - lib/pub_sub_model_sync/version.rb
@@ -149,7 +131,7 @@ metadata:
149
131
  homepage_uri: https://github.com/owen2345/pub_sub_model_sync
150
132
  source_code_uri: https://github.com/owen2345/pub_sub_model_sync
151
133
  changelog_uri: https://github.com/owen2345/pub_sub_model_sync/blob/master/CHANGELOG.md
152
- post_install_message:
134
+ post_install_message:
153
135
  rdoc_options: []
154
136
  require_paths:
155
137
  - lib
@@ -157,7 +139,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
157
139
  requirements:
158
140
  - - ">="
159
141
  - !ruby/object:Gem::Version
160
- version: '0'
142
+ version: '2.4'
161
143
  required_rubygems_version: !ruby/object:Gem::Requirement
162
144
  requirements:
163
145
  - - ">="
@@ -165,7 +147,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
165
147
  version: '0'
166
148
  requirements: []
167
149
  rubygems_version: 3.0.8
168
- signing_key:
150
+ signing_key:
169
151
  specification_version: 4
170
152
  summary: Permit to sync models between apps through pub/sub
171
153
  test_files: []
@@ -1,8 +0,0 @@
1
- # Default ignored files
2
- /shelf/
3
- /workspace.xml
4
- # Datasource local storage ignored files
5
- /dataSources/
6
- /dataSources.local.xml
7
- # Editor-based HTTP Client requests
8
- /httpRequests/
@@ -1,7 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <Settings><!--This file was automatically generated by Ruby plugin.
3
- You are allowed to:
4
- 1. Remove rake task
5
- 2. Add existing rake tasks
6
- To add existing rake tasks automatically delete this file and reload the project.
7
- --><RakeGroup description="" fullCmd="" taksId="rake"><RakeTask description="Build pub_sub_model_sync-0.1.0.gem into the pkg directory" fullCmd="build" taksId="build" /><RakeTask description="Remove any temporary products" fullCmd="clean" taksId="clean" /><RakeTask description="Remove any generated files" fullCmd="clobber" taksId="clobber" /><RakeTask description="Build and install pub_sub_model_sync-0.1.0.gem into system gems" fullCmd="install" taksId="install" /><RakeGroup description="" fullCmd="" taksId="install"><RakeTask description="Build and install pub_sub_model_sync-0.1.0.gem into system gems without network access" fullCmd="install:local" taksId="local" /></RakeGroup><RakeTask description="Create tag v0.1.0 and build and push pub_sub_model_sync-0.1.0.gem to TODO: Set to 'http://mygemserver.com'" fullCmd="release[remote]" taksId="release[remote]" /><RakeTask description="Run RSpec code examples" fullCmd="spec" taksId="spec" /><RakeTask description="" fullCmd="default" taksId="default" /><RakeTask description="" fullCmd="release" taksId="release" /><RakeGroup description="" fullCmd="" taksId="release"><RakeTask description="" fullCmd="release:guard_clean" taksId="guard_clean" /><RakeTask description="" fullCmd="release:rubygem_push" taksId="rubygem_push" /><RakeTask description="" fullCmd="release:source_control_push" taksId="source_control_push" /></RakeGroup></RakeGroup></Settings>
@@ -1,5 +0,0 @@
1
- <component name="ProjectCodeStyleConfiguration">
2
- <state>
3
- <option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
4
- </state>
5
- </component>
@@ -1,4 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="Encoding" addBOMForNewFiles="with NO BOM" />
4
- </project>
@@ -1,16 +0,0 @@
1
- <component name="InspectionProjectProfileManager">
2
- <profile version="1.0">
3
- <option name="myName" value="Project Default" />
4
- <inspection_tool class="Rubocop" enabled="true" level="WARNING" enabled_by_default="true">
5
- <option name="mySeverityMap">
6
- <map>
7
- <entry key="convention" value="ERROR" />
8
- <entry key="error" value="ERROR" />
9
- <entry key="fatal" value="ERROR" />
10
- <entry key="refactor" value="ERROR" />
11
- <entry key="warning" value="ERROR" />
12
- </map>
13
- </option>
14
- </inspection_tool>
15
- </profile>
16
- </component>
@@ -1,7 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="JavaScriptSettings">
4
- <option name="languageLevel" value="ES6" />
5
- </component>
6
- <component name="ProjectRootManager" version="2" project-jdk-name="RVM: ruby-2.5.3" project-jdk-type="RUBY_SDK" />
7
- </project>
@@ -1,8 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="ProjectModuleManager">
4
- <modules>
5
- <module fileurl="file://$PROJECT_DIR$/.idea/pub_sub_model_sync.iml" filepath="$PROJECT_DIR$/.idea/pub_sub_model_sync.iml" />
6
- </modules>
7
- </component>
8
- </project>
@@ -1,96 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <module type="RUBY_MODULE" version="4">
3
- <component name="ModuleRunConfigurationManager">
4
- <shared />
5
- </component>
6
- <component name="NewModuleRootManager">
7
- <content url="file://$MODULE_DIR$" />
8
- <orderEntry type="jdk" jdkName="RVM: ruby-2.6.5" jdkType="RUBY_SDK" />
9
- <orderEntry type="sourceFolder" forTests="false" />
10
- <orderEntry type="library" scope="PROVIDED" name="actioncable (v6.0.2.2, RVM: ruby-2.6.5) [gem]" level="application" />
11
- <orderEntry type="library" scope="PROVIDED" name="actionmailbox (v6.0.2.2, RVM: ruby-2.6.5) [gem]" level="application" />
12
- <orderEntry type="library" scope="PROVIDED" name="actionmailer (v6.0.2.2, RVM: ruby-2.6.5) [gem]" level="application" />
13
- <orderEntry type="library" scope="PROVIDED" name="actionpack (v6.0.2.2, RVM: ruby-2.6.5) [gem]" level="application" />
14
- <orderEntry type="library" scope="PROVIDED" name="actiontext (v6.0.2.2, RVM: ruby-2.6.5) [gem]" level="application" />
15
- <orderEntry type="library" scope="PROVIDED" name="actionview (v6.0.2.2, RVM: ruby-2.6.5) [gem]" level="application" />
16
- <orderEntry type="library" scope="PROVIDED" name="activejob (v6.0.2.2, RVM: ruby-2.6.5) [gem]" level="application" />
17
- <orderEntry type="library" scope="PROVIDED" name="activemodel (v6.0.2.2, RVM: ruby-2.6.5) [gem]" level="application" />
18
- <orderEntry type="library" scope="PROVIDED" name="activerecord (v6.0.2.2, RVM: ruby-2.6.5) [gem]" level="application" />
19
- <orderEntry type="library" scope="PROVIDED" name="activestorage (v6.0.2.2, RVM: ruby-2.6.5) [gem]" level="application" />
20
- <orderEntry type="library" scope="PROVIDED" name="activesupport (v6.0.2.2, RVM: ruby-2.6.5) [gem]" level="application" />
21
- <orderEntry type="library" scope="PROVIDED" name="addressable (v2.7.0, RVM: ruby-2.6.5) [gem]" level="application" />
22
- <orderEntry type="library" scope="PROVIDED" name="amq-protocol (v2.3.0, RVM: ruby-2.6.5) [gem]" level="application" />
23
- <orderEntry type="library" scope="PROVIDED" name="ast (v2.4.0, RVM: ruby-2.6.5) [gem]" level="application" />
24
- <orderEntry type="library" scope="PROVIDED" name="builder (v3.2.4, RVM: ruby-2.6.5) [gem]" level="application" />
25
- <orderEntry type="library" scope="PROVIDED" name="bundler (v2.1.4, RVM: ruby-2.6.5) [gem]" level="application" />
26
- <orderEntry type="library" scope="PROVIDED" name="bunny (v2.14.3, RVM: ruby-2.6.5) [gem]" level="application" />
27
- <orderEntry type="library" scope="PROVIDED" name="concurrent-ruby (v1.1.6, RVM: ruby-2.6.5) [gem]" level="application" />
28
- <orderEntry type="library" scope="PROVIDED" name="crass (v1.0.6, RVM: ruby-2.6.5) [gem]" level="application" />
29
- <orderEntry type="library" scope="PROVIDED" name="database_cleaner (v1.8.4, RVM: ruby-2.6.5) [gem]" level="application" />
30
- <orderEntry type="library" scope="PROVIDED" name="database_cleaner-active_record (v1.8.0, RVM: ruby-2.6.5) [gem]" level="application" />
31
- <orderEntry type="library" scope="PROVIDED" name="diff-lcs (v1.3, RVM: ruby-2.6.5) [gem]" level="application" />
32
- <orderEntry type="library" scope="PROVIDED" name="digest-crc (v0.5.1, RVM: ruby-2.6.5) [gem]" level="application" />
33
- <orderEntry type="library" scope="PROVIDED" name="erubi (v1.9.0, RVM: ruby-2.6.5) [gem]" level="application" />
34
- <orderEntry type="library" scope="PROVIDED" name="faraday (v0.17.3, RVM: ruby-2.6.5) [gem]" level="application" />
35
- <orderEntry type="library" scope="PROVIDED" name="globalid (v0.4.2, RVM: ruby-2.6.5) [gem]" level="application" />
36
- <orderEntry type="library" scope="PROVIDED" name="google-cloud-core (v1.3.2, RVM: ruby-2.6.5) [gem]" level="application" />
37
- <orderEntry type="library" scope="PROVIDED" name="google-cloud-env (v1.2.1, RVM: ruby-2.6.5) [gem]" level="application" />
38
- <orderEntry type="library" scope="PROVIDED" name="google-cloud-pubsub (v1.0.2, RVM: ruby-2.6.5) [gem]" level="application" />
39
- <orderEntry type="library" scope="PROVIDED" name="google-gax (v1.7.1, RVM: ruby-2.6.5) [gem]" level="application" />
40
- <orderEntry type="library" scope="PROVIDED" name="google-protobuf (v3.11.4, RVM: ruby-2.6.5) [gem]" level="application" />
41
- <orderEntry type="library" scope="PROVIDED" name="googleapis-common-protos (v1.3.9, RVM: ruby-2.6.5) [gem]" level="application" />
42
- <orderEntry type="library" scope="PROVIDED" name="googleapis-common-protos-types (v1.0.4, RVM: ruby-2.6.5) [gem]" level="application" />
43
- <orderEntry type="library" scope="PROVIDED" name="googleauth (v0.9.0, RVM: ruby-2.6.5) [gem]" level="application" />
44
- <orderEntry type="library" scope="PROVIDED" name="grpc (v1.27.0, RVM: ruby-2.6.5) [gem]" level="application" />
45
- <orderEntry type="library" scope="PROVIDED" name="grpc-google-iam-v1 (v0.6.9, RVM: ruby-2.6.5) [gem]" level="application" />
46
- <orderEntry type="library" scope="PROVIDED" name="i18n (v1.8.2, RVM: ruby-2.6.5) [gem]" level="application" />
47
- <orderEntry type="library" scope="PROVIDED" name="jaro_winkler (v1.5.4, RVM: ruby-2.6.5) [gem]" level="application" />
48
- <orderEntry type="library" scope="PROVIDED" name="jwt (v2.2.1, RVM: ruby-2.6.5) [gem]" level="application" />
49
- <orderEntry type="library" scope="PROVIDED" name="loofah (v2.5.0, RVM: ruby-2.6.5) [gem]" level="application" />
50
- <orderEntry type="library" scope="PROVIDED" name="mail (v2.7.1, RVM: ruby-2.6.5) [gem]" level="application" />
51
- <orderEntry type="library" scope="PROVIDED" name="marcel (v0.3.3, RVM: ruby-2.6.5) [gem]" level="application" />
52
- <orderEntry type="library" scope="PROVIDED" name="memoist (v0.16.2, RVM: ruby-2.6.5) [gem]" level="application" />
53
- <orderEntry type="library" scope="PROVIDED" name="method_source (v1.0.0, RVM: ruby-2.6.5) [gem]" level="application" />
54
- <orderEntry type="library" scope="PROVIDED" name="mimemagic (v0.3.4, RVM: ruby-2.6.5) [gem]" level="application" />
55
- <orderEntry type="library" scope="PROVIDED" name="mini_mime (v1.0.2, RVM: ruby-2.6.5) [gem]" level="application" />
56
- <orderEntry type="library" scope="PROVIDED" name="mini_portile2 (v2.4.0, RVM: ruby-2.6.5) [gem]" level="application" />
57
- <orderEntry type="library" scope="PROVIDED" name="minitest (v5.14.0, RVM: ruby-2.6.5) [gem]" level="application" />
58
- <orderEntry type="library" scope="PROVIDED" name="multi_json (v1.14.1, RVM: ruby-2.6.5) [gem]" level="application" />
59
- <orderEntry type="library" scope="PROVIDED" name="multipart-post (v2.1.1, RVM: ruby-2.6.5) [gem]" level="application" />
60
- <orderEntry type="library" scope="PROVIDED" name="nio4r (v2.5.2, RVM: ruby-2.6.5) [gem]" level="application" />
61
- <orderEntry type="library" scope="PROVIDED" name="nokogiri (v1.10.9, RVM: ruby-2.6.5) [gem]" level="application" />
62
- <orderEntry type="library" scope="PROVIDED" name="os (v1.0.1, RVM: ruby-2.6.5) [gem]" level="application" />
63
- <orderEntry type="library" scope="PROVIDED" name="parallel (v1.19.1, RVM: ruby-2.6.5) [gem]" level="application" />
64
- <orderEntry type="library" scope="PROVIDED" name="parser (v2.7.0.4, RVM: ruby-2.6.5) [gem]" level="application" />
65
- <orderEntry type="library" scope="PROVIDED" name="public_suffix (v4.0.3, RVM: ruby-2.6.5) [gem]" level="application" />
66
- <orderEntry type="library" scope="PROVIDED" name="rack (v2.2.2, RVM: ruby-2.6.5) [gem]" level="application" />
67
- <orderEntry type="library" scope="PROVIDED" name="rack-test (v1.1.0, RVM: ruby-2.6.5) [gem]" level="application" />
68
- <orderEntry type="library" scope="PROVIDED" name="rails (v6.0.2.2, RVM: ruby-2.6.5) [gem]" level="application" />
69
- <orderEntry type="library" scope="PROVIDED" name="rails-dom-testing (v2.0.3, RVM: ruby-2.6.5) [gem]" level="application" />
70
- <orderEntry type="library" scope="PROVIDED" name="rails-html-sanitizer (v1.3.0, RVM: ruby-2.6.5) [gem]" level="application" />
71
- <orderEntry type="library" scope="PROVIDED" name="railties (v6.0.2.2, RVM: ruby-2.6.5) [gem]" level="application" />
72
- <orderEntry type="library" scope="PROVIDED" name="rainbow (v3.0.0, RVM: ruby-2.6.5) [gem]" level="application" />
73
- <orderEntry type="library" scope="PROVIDED" name="rake (v13.0.1, RVM: ruby-2.6.5) [gem]" level="application" />
74
- <orderEntry type="library" scope="PROVIDED" name="rexml (v3.2.4, RVM: ruby-2.6.5) [gem]" level="application" />
75
- <orderEntry type="library" scope="PROVIDED" name="rly (v0.2.3, RVM: ruby-2.6.5) [gem]" level="application" />
76
- <orderEntry type="library" scope="PROVIDED" name="rspec (v3.9.0, RVM: ruby-2.6.5) [gem]" level="application" />
77
- <orderEntry type="library" scope="PROVIDED" name="rspec-core (v3.9.1, RVM: ruby-2.6.5) [gem]" level="application" />
78
- <orderEntry type="library" scope="PROVIDED" name="rspec-expectations (v3.9.0, RVM: ruby-2.6.5) [gem]" level="application" />
79
- <orderEntry type="library" scope="PROVIDED" name="rspec-mocks (v3.9.1, RVM: ruby-2.6.5) [gem]" level="application" />
80
- <orderEntry type="library" scope="PROVIDED" name="rspec-support (v3.9.2, RVM: ruby-2.6.5) [gem]" level="application" />
81
- <orderEntry type="library" scope="PROVIDED" name="rubocop (v0.80.1, RVM: ruby-2.6.5) [gem]" level="application" />
82
- <orderEntry type="library" scope="PROVIDED" name="ruby-kafka (v1.0.0, RVM: ruby-2.6.5) [gem]" level="application" />
83
- <orderEntry type="library" scope="PROVIDED" name="ruby-progressbar (v1.10.1, RVM: ruby-2.6.5) [gem]" level="application" />
84
- <orderEntry type="library" scope="PROVIDED" name="signet (v0.11.0, RVM: ruby-2.6.5) [gem]" level="application" />
85
- <orderEntry type="library" scope="PROVIDED" name="sprockets (v4.0.0, RVM: ruby-2.6.5) [gem]" level="application" />
86
- <orderEntry type="library" scope="PROVIDED" name="sprockets-rails (v3.2.1, RVM: ruby-2.6.5) [gem]" level="application" />
87
- <orderEntry type="library" scope="PROVIDED" name="sqlite3 (v1.4.2, RVM: ruby-2.6.5) [gem]" level="application" />
88
- <orderEntry type="library" scope="PROVIDED" name="thor (v1.0.1, RVM: ruby-2.6.5) [gem]" level="application" />
89
- <orderEntry type="library" scope="PROVIDED" name="thread_safe (v0.3.6, RVM: ruby-2.6.5) [gem]" level="application" />
90
- <orderEntry type="library" scope="PROVIDED" name="tzinfo (v1.2.7, RVM: ruby-2.6.5) [gem]" level="application" />
91
- <orderEntry type="library" scope="PROVIDED" name="unicode-display_width (v1.6.1, RVM: ruby-2.6.5) [gem]" level="application" />
92
- <orderEntry type="library" scope="PROVIDED" name="websocket-driver (v0.7.1, RVM: ruby-2.6.5) [gem]" level="application" />
93
- <orderEntry type="library" scope="PROVIDED" name="websocket-extensions (v0.1.4, RVM: ruby-2.6.5) [gem]" level="application" />
94
- <orderEntry type="library" scope="PROVIDED" name="zeitwerk (v2.3.0, RVM: ruby-2.6.5) [gem]" level="application" />
95
- </component>
96
- </module>
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="VcsDirectoryMappings">
4
- <mapping directory="$PROJECT_DIR$" vcs="Git" />
5
- </component>
6
- </project>