flu-rails 8.0.0 → 8.0.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: a35d15acbcbab2206e789f346ef7c97cd92d4cdc9bed53fe6fb0025256fec3b6
4
- data.tar.gz: 5114f719bf9565182eada52d5ccd3c3a1e219ad2bc26e41c288ee7ff0ab58573
3
+ metadata.gz: 721522defefd5f7c30f0cd24dd1773b6191cf93bc8162d791e349883dd91698f
4
+ data.tar.gz: 3d5e0c52ddbffbdf5c021647a76460acde5726ca1201bda1022d21fdb3095a05
5
5
  SHA512:
6
- metadata.gz: a26ad7a3c9274ac803f842b509dcd81eebbc735812347c4473432cbbcb016355c6d7bc7440fe95070f4e53e708d8617849fad2a8c72ffb2443c64445682e2e5f
7
- data.tar.gz: 9364931b1ee248a8a43b18089149ba1db00bc4e91fcbd9a7325cae5e7f009493ff9a5ae3b3226d52bf794e17c85d05503811421850a9793f948912a2f5e82b5b
6
+ metadata.gz: 9965aef8635a94a1f2e8843253e3c8817b2b6b0f61f13b1f0137a1d555eb638c6e12d4f620c004e47f3365cec9fc9da57a2076e629f53ea11df045c21a0c0a27
7
+ data.tar.gz: 5818a4c90e2b819a12483f9683a75119c33e6573b57e0952916a3cbd258dd6d0e536be85105efbb1b1415fafa105c5de9de478ae3976a06f12ab2f28a86203a8
data/CHANGELOG.md CHANGED
@@ -5,7 +5,33 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ### [8.0.0] - 2026-07-31
8
+ ### [Unreleased]
9
+
10
+ **Fixed**
11
+
12
+ * Honour `configuration.logger` when rejecting a request on its user agent.
13
+ * Stop defining those helpers on `ActionController::Base` and `ActionController::API` themselves.
14
+ * Apply the model's `emitter:` override when exporting existing entities. `Util::ExportService`
15
+ passed `nil` instead of the model's lambda, so a replayed event carried the global
16
+ `application_name` while the live event for the same entity carried the overriden one — putting
17
+ the two on different routing keys. Unnoticed since `1.0.4`, where the option was introduced.
18
+ * Do not assume that a defined `Rails` constant means railties is loaded.
19
+
20
+ **Changed**
21
+
22
+ * Stop publishing `overriden_emitter` as part of the event payload.
23
+
24
+ **Tests**
25
+
26
+ * Enable the three controller examples that were disabled with `xit`, and run them through the real
27
+ `ActionController` dispatch, which is what triggers the callbacks registered by `track_requests`.
28
+ This is the code path that hid the `ActionController::UnfilteredParameters` bug fixed in `8.0.1`.
29
+ * Cover `Util::ExportService`, which had no test at all — and which is how the `emitter:` bug above
30
+ surfaced.
31
+ * Make the spec setup idempotent
32
+ * Integration tests with RabbitMQ
33
+
34
+ ### [8.0.1] - 2026-07-31
9
35
 
10
36
  **Fixed**
11
37
 
data/README.md CHANGED
@@ -40,7 +40,9 @@ Each configuration is detailed below.
40
40
  * Ruby >= 3.2 (tested against 3.2, 3.3 and 3.4)
41
41
  * Rails 8 only (`activerecord`, `actionpack` and `activesupport` are runtime dependencies,
42
42
  constrained to `~> 8.0`). `flu-rails` extends `ActiveRecord::Base` and `ActionController::Base`, so it requires Rails.
43
- * Tested with RabbitMQ 3.5.8
43
+ * RabbitMQ: no specific version is required by `flu-rails` itself. The broker is reached through
44
+ `bunny` 3.x and `rabbitmq_http_api_client` 3.x, which target the
45
+ [currently supported RabbitMQ release series](https://www.rabbitmq.com/release-information).
44
46
 
45
47
  ## Usage
46
48
 
@@ -171,10 +173,32 @@ All options have a default value. However, all of them can be changed in your in
171
173
  From the `flu-rails` directory:
172
174
 
173
175
  ```
174
- $ docker build . -t flu:test
175
- $ docker run -v `pwd`:/usr/src/app/ flu:test rspec spec
176
+ $ docker compose run --rm specs
176
177
  ```
177
178
 
179
+ This starts a RabbitMQ broker, waits for it, and runs the whole suite against it.
180
+
181
+ Part of the suite publishes to a real broker and drives its HTTP management API: nothing there is
182
+ stubbed, since a stub cannot tell whether the gem actually speaks AMQP. To run the specs directly on
183
+ your machine, start a broker first:
184
+
185
+ ```
186
+ $ docker compose up -d rabbitmq
187
+ $ bundle exec rspec
188
+ ```
189
+
190
+ Without one, those examples are reported as skipped and the rest of the suite still runs. Set
191
+ `FLU_REQUIRE_RABBITMQ` to turn that skip into a failure — this is what the CI workflow does, so that
192
+ a build cannot report success without having reached RabbitMQ:
193
+
194
+ ```
195
+ $ FLU_REQUIRE_RABBITMQ=true bundle exec rspec
196
+ ```
197
+
198
+ A broker of your own can be used instead through `FLU_RABBITMQ_HOST`, `FLU_RABBITMQ_PORT`,
199
+ `FLU_RABBITMQ_MANAGEMENT_PORT`, `FLU_RABBITMQ_USER` and `FLU_RABBITMQ_PASSWORD`. Every example
200
+ declares an exchange and queues of its own, and deletes them afterwards.
201
+
178
202
  ## Example of Events
179
203
 
180
204
  ### Structure of an Active Record change
@@ -276,12 +300,12 @@ no API key is stored in this repository, the workflow exchanges a short-lived Gi
276
300
  scoped RubyGems credential.
277
301
 
278
302
  1. Update `Flu::VERSION` in `lib/flu-rails/version.rb` and the `CHANGELOG.md`
279
- 2. Commit and push these changes to `master`
303
+ 2. Commit and push these changes to `main`
280
304
  3. Tag the commit and push the tag:
281
305
 
282
306
  ```
283
- $ git tag -a v8.0.0 -m "Version 8.0.0"
284
- $ git push origin v8.0.0
307
+ $ git tag -a v8.0.1 -m "Version 8.0.1"
308
+ $ git push origin v8.0.1
285
309
  ```
286
310
 
287
311
  The workflow then checks that the tag matches `Flu::VERSION`, runs the tests, builds the gem
data/flu-rails.gemspec ADDED
@@ -0,0 +1,39 @@
1
+ require_relative "lib/flu-rails/version"
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "flu-rails"
5
+ spec.version = Flu::VERSION
6
+ spec.authors = ["Loïc Vigneron", "Lorent Lempereur", "Thibault Poncelet", "Logan Clément"]
7
+ spec.email = ["support@commuty.net"]
8
+ spec.summary = "Track your application events and publish them to RabbitMQ."
9
+ spec.description = "Seamlessly emit events from an existing Rails application, without changing its " \
10
+ "codebase, and publish them to RabbitMQ. Events are generated from CRUD operations " \
11
+ "on ActiveRecord models and from requests on Rails controller actions."
12
+ spec.homepage = "https://github.com/crepesourcing/flu-rails"
13
+ spec.license = "MIT"
14
+
15
+ spec.required_ruby_version = ">= 3.2"
16
+
17
+ spec.metadata = {
18
+ "source_code_uri" => spec.homepage,
19
+ "changelog_uri" => "#{spec.homepage}/blob/main/CHANGELOG.md",
20
+ "bug_tracker_uri" => "#{spec.homepage}/issues",
21
+ "rubygems_mfa_required" => "true"
22
+ }
23
+
24
+ spec.files = Dir.glob(["lib/**/*.rb", "CHANGELOG.md", "MIT-LICENSE", "README.md", "flu-rails.gemspec"])
25
+ spec.require_paths = ["lib"]
26
+
27
+ spec.add_dependency "actionpack", "~> 8.0"
28
+ spec.add_dependency "activerecord", "~> 8.0"
29
+ spec.add_dependency "activesupport", "~> 8.0"
30
+ spec.add_dependency "bunny", "~> 3.1"
31
+ spec.add_dependency "logger", "~> 1.7"
32
+ spec.add_dependency "rabbitmq_http_api_client", "~> 3.2"
33
+
34
+ spec.add_development_dependency "byebug", "~> 13.0"
35
+ spec.add_development_dependency "ostruct", "~> 0.6"
36
+ spec.add_development_dependency "rake", ">= 13.0"
37
+ spec.add_development_dependency "rspec", "~> 3.13"
38
+ spec.add_development_dependency "sqlite3", "~> 2.9"
39
+ end
@@ -21,49 +21,38 @@ module Flu
21
21
  ignored_request_params = options.fetch(:ignored_request_params, []).map(&:to_sym)
22
22
  emitter_lambda = options[:emitter]
23
23
 
24
- before_action do
25
- define_request_id
26
- @request_start_time = Time.zone.now
27
- end
28
- prepend_before_action do
29
- define_request_entity_metadata_lambda(entity_metadata_lambda)
30
- end
31
- prepend_after_action do
32
- track_requests(event_factory, event_publisher, user_metadata_lambda, ignored_request_params, emitter_lambda)
33
- remove_request_entity_metadata_lambda
34
- end
35
- after_action do
36
- remove_request_id
37
- end
38
-
39
- def define_request_id
24
+ # These helpers are defined with 'define_method' rather than 'def' for two reasons. A bare
25
+ # 'def' opens a new scope, so it captures neither 'logger' nor the tracking options: the
26
+ # former silently resolved to ActionController::Base#logger, which ignored 'Flu.config.logger'
27
+ # altogether. And its default definee is the class 'class_eval' was called on, so the helpers
28
+ # landed on ActionController::Base itself, leaking onto every controller of the host
29
+ # application. Here 'self' is the class calling 'track_requests', which is where they belong.
30
+ define_method(:flu_define_request_id) do
40
31
  request_id = SecureRandom.uuid
41
32
  @flu_request_id = request_id
42
33
  Flu::CoreExt.flu_tracker_request_id = request_id
43
34
  end
44
35
 
45
- def remove_request_id
36
+ define_method(:flu_remove_request_id) do
46
37
  Flu::CoreExt.flu_tracker_request_id = nil
47
38
  end
48
39
 
49
- def define_request_entity_metadata_lambda(entity_metadata_lambda)
40
+ define_method(:flu_define_request_entity_metadata) do
50
41
  Flu::CoreExt.flu_tracker_request_entity_metadata = instance_exec(&entity_metadata_lambda) if entity_metadata_lambda
51
42
  end
52
43
 
53
- def remove_request_entity_metadata_lambda
44
+ define_method(:flu_remove_request_entity_metadata) do
54
45
  Flu::CoreExt.flu_tracker_request_entity_metadata = nil
55
46
  end
56
47
 
57
- def rejected_origin?(request)
58
- rejected_user_agents = Regexp.union(Flu.config.rejected_user_agents)
59
- user_agent = request.user_agent
60
- matching_user_agents = user_agent&.match(rejected_user_agents)
61
- !matching_user_agents.nil?
48
+ define_method(:flu_rejected_origin?) do
49
+ rejected_user_agents = Regexp.union(Flu.config.rejected_user_agents)
50
+ !request.user_agent&.match(rejected_user_agents).nil?
62
51
  end
63
52
 
64
- def track_requests(event_factory, event_publisher, user_metadata_lambda, ignored_request_params, emitter_lambda)
65
- if rejected_origin?(request)
66
- logger.warn "Origin user agent rejected: #{request.user_agent}"
53
+ define_method(:flu_track_request) do
54
+ if flu_rejected_origin?
55
+ logger.warn("Origin user agent rejected: #{request.user_agent}")
67
56
  else
68
57
  tracked_request = event_factory.create_data_from_request(@flu_request_id,
69
58
  params,
@@ -77,6 +66,21 @@ module Flu
77
66
  event_publisher.publish(event)
78
67
  end
79
68
  end
69
+
70
+ before_action do
71
+ flu_define_request_id
72
+ @request_start_time = Time.zone.now
73
+ end
74
+ prepend_before_action do
75
+ flu_define_request_entity_metadata
76
+ end
77
+ prepend_after_action do
78
+ flu_track_request
79
+ flu_remove_request_entity_metadata
80
+ end
81
+ after_action do
82
+ flu_remove_request_id
83
+ end
80
84
  end
81
85
  end
82
86
  end
@@ -48,7 +48,9 @@ module Flu
48
48
  original_emitter = @emitter
49
49
  overriden_emitter = data[:overriden_emitter]&.strip&.delete(".")
50
50
  final_emitter = overriden_emitter.blank? ? original_emitter : overriden_emitter
51
- Event.new(SecureRandom.uuid, final_emitter, kind, name, deep_camelize(data))
51
+ # 'overriden_emitter' selects the emitter of the event, it is not part of what is tracked:
52
+ # it is dropped from the payload instead of being published as an 'overridenEmitter' key.
53
+ Event.new(SecureRandom.uuid, final_emitter, kind, name, deep_camelize(data.except(:overriden_emitter)))
52
54
  end
53
55
 
54
56
  def create_data_from_entity_changes(action_name, entity, request_id, request_entity_metadata, changes, user_metadata_lambda, association_columns, ignored_model_changes, flu_overriden_emitter_lambda)
@@ -16,12 +16,13 @@ module Flu
16
16
  association_columns = entity_type.flu_association_columns
17
17
  user_metadata_lambda = entity_type.flu_user_metadata_lambdas[:create]
18
18
  ignored_model_changes = entity_type.flu_ignored_model_changes
19
+ overriden_emitter_lambda = entity_type.flu_overriden_emitter_lambda
19
20
 
20
21
  entities.each do |entity|
21
22
  print "\r" unless current_entity_index == 0
22
23
  current_entity_index += 1
23
24
  print "#{entity_type} (#{current_entity_type_index}/#{total_number_of_entity_types}) : #{current_entity_index}/#{total_number_of_entities}"
24
- data = extract_data_from(entity, event_factory, user_metadata_lambda, association_columns, ignored_model_changes)
25
+ data = extract_data_from(entity, event_factory, user_metadata_lambda, association_columns, ignored_model_changes, overriden_emitter_lambda)
25
26
  event = event_factory.build_entity_change_event(data)
26
27
  event.timestamp = entity.created_at unless entity.created_at.nil?
27
28
  event_publisher.publish(event)
@@ -52,9 +53,17 @@ module Flu
52
53
  end
53
54
  end
54
55
 
55
- def extract_data_from(entity, event_factory, user_metadata_lambda, association_columns, ignored_model_changes)
56
+ def extract_data_from(entity, event_factory, user_metadata_lambda, association_columns, ignored_model_changes, overriden_emitter_lambda)
56
57
  changes = create_changes_from_existing(entity)
57
- event_factory.create_data_from_entity_changes(:create, entity, nil, nil, changes, user_metadata_lambda, association_columns, ignored_model_changes, nil)
58
+ event_factory.create_data_from_entity_changes(:create,
59
+ entity,
60
+ nil,
61
+ nil,
62
+ changes,
63
+ user_metadata_lambda,
64
+ association_columns,
65
+ ignored_model_changes,
66
+ overriden_emitter_lambda)
58
67
  end
59
68
 
60
69
  def create_changes_from_existing(entity)
@@ -1,3 +1,3 @@
1
1
  module Flu
2
- VERSION = "8.0.0"
2
+ VERSION = "8.0.1"
3
3
  end
data/lib/flu-rails.rb CHANGED
@@ -62,7 +62,10 @@ module Flu
62
62
  end
63
63
 
64
64
  def self.is_testing_environment?
65
- defined?(Rails) && config.development_environments.include?(Rails.env)
65
+ # 'defined?(Rails)' alone is not enough: gems such as 'rails-html-sanitizer' define an empty
66
+ # 'Rails' namespace, so the constant can exist without railties ever defining 'Rails.env'.
67
+ return false unless defined?(Rails) && Rails.respond_to?(:env)
68
+ config.development_environments.include?(Rails.env)
66
69
  end
67
70
 
68
71
  def self.extend_models_and_controllers
metadata CHANGED
@@ -1,12 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flu-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.0.0
4
+ version: 8.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Loïc Vigneron
8
8
  - Lorent Lempereur
9
9
  - Thibault Poncelet
10
+ - Logan Clément
10
11
  bindir: bin
11
12
  cert_chain: []
12
13
  date: 1980-01-02 00:00:00.000000000 Z
@@ -169,8 +170,7 @@ description: Seamlessly emit events from an existing Rails application, without
169
170
  its codebase, and publish them to RabbitMQ. Events are generated from CRUD operations
170
171
  on ActiveRecord models and from requests on Rails controller actions.
171
172
  email:
172
- - info@spin42.com
173
- - info@commuty.net
173
+ - support@commuty.net
174
174
  executables: []
175
175
  extensions: []
176
176
  extra_rdoc_files: []
@@ -178,6 +178,7 @@ files:
178
178
  - CHANGELOG.md
179
179
  - MIT-LICENSE
180
180
  - README.md
181
+ - flu-rails.gemspec
181
182
  - lib/flu-rails.rb
182
183
  - lib/flu-rails/action_controller_extender.rb
183
184
  - lib/flu-rails/active_record_extender.rb
@@ -195,9 +196,8 @@ homepage: https://github.com/crepesourcing/flu-rails
195
196
  licenses:
196
197
  - MIT
197
198
  metadata:
198
- homepage_uri: https://github.com/crepesourcing/flu-rails
199
199
  source_code_uri: https://github.com/crepesourcing/flu-rails
200
- changelog_uri: https://github.com/crepesourcing/flu-rails/blob/master/CHANGELOG.md
200
+ changelog_uri: https://github.com/crepesourcing/flu-rails/blob/main/CHANGELOG.md
201
201
  bug_tracker_uri: https://github.com/crepesourcing/flu-rails/issues
202
202
  rubygems_mfa_required: 'true'
203
203
  rdoc_options: []