rdux 2.0.1 → 3.0.0

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: d9466995069583a72d2327967aebfa05218c2c224aa091645dbee874e53a491f
4
- data.tar.gz: e1ec237a0de458512951e68f29596b0677a45d56b417d4b139ee4aa6a79bea13
3
+ metadata.gz: 7d08bba313e7358c9ab679919bffa752086205e61c3884285da3cca786b1c63e
4
+ data.tar.gz: 6aa632159bdc20f99fb82686374bc0cf72359a08219485168d5be3fe2e4441bc
5
5
  SHA512:
6
- metadata.gz: f1eec1d56443a217a827f9cfc6d30bbcc0d0ab4fb68dc912cbf9ac4986342e91de660e4876ce7d3bbf67493905be10edd5b2a07fa2056d110fe118f2f5e68892
7
- data.tar.gz: 799a77dd6396597790ca62b27cc203fa4b529935a486d6136645bc64d8759801b844c918f863e990a1e06cd973c4b02184da100d496a1439a3c957c34f4a3581
6
+ metadata.gz: 6420dd96aa5f4c5f51e8bd4ef1d5b689892a7693405b5f27045bbaa5af57a8c09237a9213eb30a85907d7fd7bd4af926c85494843ff32d0330f36dc39c8e375e
7
+ data.tar.gz: 873fe11556fff66df5290cb3f9aaa9414c39d28299b2f8a1a207a5351853de70e5801fd8d654d9022d1864a243eab4b12f2727be6d866ce7731877d96727a712
data/README.md CHANGED
@@ -49,7 +49,7 @@ $ bin/rails rdux:install:migrations
49
49
  $ bin/rails db:migrate
50
50
  ```
51
51
 
52
- ⚠️ Note: Rdux uses `JSONB` datatype instead of `text` for Postgres.
52
+ ⚠️ Note: Rdux requires Rails 7.1+. It uses `jsonb` columns on PostgreSQL and `json` on other adapters.
53
53
 
54
54
  ## 🎮 Usage
55
55
 
@@ -60,7 +60,7 @@ To dispatch an action using Rdux, use the `dispatch` method (aliased as `perform
60
60
  Definition:
61
61
 
62
62
  ```ruby
63
- def dispatch(action, payload, opts = {}, meta: nil)
63
+ def dispatch(action, payload, opts: {}, meta: nil)
64
64
 
65
65
  alias perform dispatch
66
66
  ```
@@ -78,7 +78,7 @@ Example:
78
78
  Rdux.perform(
79
79
  Task::Create,
80
80
  { task: { name: 'Foo bar baz' } },
81
- { ars: { user: current_user } },
81
+ opts: { ars: { user: current_user } },
82
82
  meta: { bar: 'baz' }
83
83
  )
84
84
  ```
@@ -89,7 +89,7 @@ Rdux.perform(
89
89
 
90
90
  ### 🕵️‍♀️ Processing an action
91
91
 
92
- Action in Rdux is processed by an action performer which is a Plain Old Ruby Object (PORO) that implements a class or instance method `call`.
92
+ Action in Rdux is processed by an action performer which is a Plain Old Ruby Object (PORO) that implements the `self.call` method.
93
93
  This method accepts a required `payload` and an optional `opts` argument.
94
94
  `opts[:action]` stores the Active Record object.
95
95
  `call` method processes the action and must return a `Rdux::Result` struct.
@@ -102,8 +102,8 @@ Example:
102
102
  # app/actions/task/create.rb
103
103
 
104
104
  class Task
105
- class Create
106
- def call(payload, opts)
105
+ module Create
106
+ def self.call(payload, opts)
107
107
  user = opts.dig(:ars, :user) || User.find(payload['user_id'])
108
108
  task = user.tasks.new(payload['task'])
109
109
  if task.save
@@ -269,7 +269,7 @@ class CreditCard
269
269
  private
270
270
 
271
271
  def create(payload, opts)
272
- res = Rdux.perform(Create, payload, opts)
272
+ res = Rdux.perform(Create, payload, opts:)
273
273
  res.ok ? res : Rdux::Result[ok: false, val: { errors: res.val[:errors] }, save: true]
274
274
  end
275
275
  end
@@ -277,6 +277,191 @@ class CreditCard
277
277
  end
278
278
  ```
279
279
 
280
+ ### 🧹 Development Mode
281
+
282
+ Set the `RDUX_DEV` environment variable to prevent Rdux from persisting failed actions on exceptions. When `RDUX_DEV` is set, the action record is destroyed and the exception is re-raised without storing error details in the database.
283
+
284
+ ```bash
285
+ RDUX_DEV=1 bin/rails server
286
+ ```
287
+
288
+ This keeps your development database clean from failed action records caused by exceptions during iterative development.
289
+
290
+ ## 🧩 Process
291
+
292
+ **Process** 👉 a series of actions or steps taken in order to achieve a particular end.
293
+
294
+ `Rdux::Process` is a persisted model that groups multiple `Rdux::Action`s.
295
+ It also stores an ordered list of `steps` (`jsonb`/`json`).
296
+
297
+ When a process starts:
298
+
299
+ * Steps run **sequentially** in the order defined in `STEPS`
300
+ * Process execution continues only when the latest process action returns `ok: true`
301
+ * Execution stops on the first failed action step (`ok == false`)
302
+ * `process.ok` is persisted from the latest non-`nil` step result
303
+
304
+ Key points:
305
+
306
+ * `Rdux::Process` **has many** `Rdux::Action`s (`process.actions`)
307
+ * `Rdux::Action` **belongs to** a process (`action.process`)
308
+ * `Rdux.start(ProcessModuleOrClass, payload)` starts a process performer (a PORO namespace/class with a `STEPS` constant)
309
+ * `STEPS` must be an `Array` (validated on `Rdux::Process`)
310
+ * `steps` is stored as `jsonb` on PostgreSQL and `json` on other adapters (default: `[]`)
311
+ * `STEPS` supports:
312
+ * a step definition hash (`{ name: User::Create, payload: ->(payload, prev_res) { ... } }`)
313
+ * a callable step (`->(payload, process) { ... }`)
314
+ * For hash steps, Rdux dispatches `Rdux.perform(step_name, step_payload, process: process)` (`step_payload` is the full process payload unless `payload:` proc is provided)
315
+ * For callable steps, Rdux calls the step with `(safe_payload, process)` and the step is responsible for dispatching an action (with `Rdux.perform(..., process:)`)
316
+ * ⚠️ If a step returns `ok: false`, that step action is persisted (and can be assigned to the process) **only** when it also returns `save: true`. This is required.
317
+ * Inside an action performer, use `opts[:action]` to access the current persisted action, then traverse `opts[:action].process.actions` (and their `result`)
318
+ * Actions dispatched *inside* an action performer (via `Rdux.perform`) are linked via `rdux_action_id` (`action.rdux_actions`) and are not automatically assigned to the process
319
+
320
+ Example:
321
+
322
+ ```ruby
323
+ module Processes
324
+ module Subscription
325
+ module Create
326
+ STEPS = [
327
+ lambda { |payload, process|
328
+ payload = payload.slice('plan_id', 'user', 'total_cents')
329
+ Rdux.perform(::Subscription::Preview, payload, process:)
330
+ },
331
+ lambda { |payload, process|
332
+ payload = payload.slice('user')
333
+ Rdux.perform(User::Create, payload, process:)
334
+ },
335
+ { name: CreditCard::Create,
336
+ payload: lambda { |payload, prev_res|
337
+ payload.slice('credit_card').merge(user_id: prev_res.action.result['user_id'])
338
+ } },
339
+ { name: Payment::Create,
340
+ payload: ->(_, prev_res) { { token: prev_res.val[:credit_card].token } } },
341
+ { name: ::Subscription::Create,
342
+ payload: ->(payload, prev_res) { payload.slice('plan_id').merge(ext_charge_id: prev_res.val[:charge_id]) } }
343
+ ].freeze
344
+ end
345
+ end
346
+ end
347
+
348
+ res = Rdux.start(Processes::Subscription::Create, payload)
349
+ process = res.val[:process]
350
+
351
+ # from any action performer:
352
+ def self.call(payload, opts)
353
+ results = opts[:action].process.actions.order(:id).pluck(:result)
354
+ # ...
355
+ end
356
+ ```
357
+
358
+ ## 🛠️ Helpers
359
+
360
+ ### `ActionResult`
361
+
362
+ `ActionResult` is not part of Rdux itself, but a useful helper you can copy into your app to persist DB changes and resource relations alongside an action.
363
+
364
+ It sets `action.result` with:
365
+
366
+ * `relations` — a map of `"model_name#id" => id` (or raw hashes) for each resource that was modified or created
367
+ * `db_changes` — `saved_changes` for each resource that was modified or created
368
+ * any extra key/value pairs passed as keyword arguments
369
+
370
+ It also creates an `ActionResource` record for each AR resource, linking it to the action via a polymorphic association.
371
+
372
+ **Usage:**
373
+
374
+ ```ruby
375
+ # inside an action performer
376
+ opts[:action].result = ActionResult.call(
377
+ action: opts[:action],
378
+ resources: [task]
379
+ )
380
+
381
+ # action.result stored in DB:
382
+ # {
383
+ # "relations" => { "task#1" => 1 },
384
+ # "db_changes" => {
385
+ # "task#1" => {
386
+ # "id" => [nil, 1],
387
+ # "name" => [nil, "Foo bar baz"],
388
+ # "user_id" => [nil, 42],
389
+ # "created_at" => [nil, "2024-06-28 21:35:36"],
390
+ # "updated_at" => [nil, "2024-06-28 21:35:36"]
391
+ # }
392
+ # }
393
+ # }
394
+ ```
395
+
396
+ Resources can be ActiveRecord objects or plain hashes (merged directly into `relations`):
397
+
398
+ ```ruby
399
+ ActionResult.call(
400
+ action: opts[:action],
401
+ resources: [task, { user_id: user.id }],
402
+ additional_info: 'Foo Bar Baz'
403
+ )
404
+ ```
405
+
406
+ **`ActionResource` model** (`app/models/action_resource.rb`):
407
+
408
+ ```ruby
409
+ class ActionResource < ApplicationRecord
410
+ belongs_to :action, class_name: 'Rdux::Action'
411
+ belongs_to :resource, polymorphic: true
412
+
413
+ validates :action_id, uniqueness: { scope: %i[resource_type resource_id] }
414
+ validates :resource_type, presence: true
415
+ end
416
+ ```
417
+
418
+ **`ActionResult` service** (`app/services/action_result.rb`):
419
+
420
+ ```ruby
421
+ class ActionResult
422
+ class << self
423
+ def call(action:, resources:, **custom)
424
+ result = { relations: {}, db_changes: {} }
425
+
426
+ resources.each do |resource|
427
+ if resource.is_a?(Hash)
428
+ result[:relations].merge!(resource)
429
+ next
430
+ end
431
+
432
+ key = relation_key(resource)
433
+ result[:relations][key] = resource.id
434
+ result[:db_changes][key] = resource.saved_changes if resource.saved_changes.present?
435
+ end
436
+
437
+ persist_relations(result[:relations], action.id)
438
+ result.merge(custom)
439
+ end
440
+
441
+ private
442
+
443
+ def relation_key(resource)
444
+ "#{resource.class.name.underscore}##{resource.id}"
445
+ end
446
+
447
+ def resource_type_for(name)
448
+ type = name.sub(/_id$/, '').sub(/#\d+$/, '').camelize
449
+ resource_class = type.safe_constantize
450
+ resource_class && resource_class < ApplicationRecord ? type : nil
451
+ end
452
+
453
+ def persist_relations(relations, action_id)
454
+ relations.each do |name, id|
455
+ resource_type = resource_type_for(name)
456
+ next if resource_type.nil? || !id.to_s.match?(/\A\d+\z/)
457
+
458
+ ActionResource.create!(action_id:, resource_type:, resource_id: id)
459
+ end
460
+ end
461
+ end
462
+ end
463
+ ```
464
+
280
465
  ## 👩🏽‍🔬 Testing
281
466
 
282
467
  ### 💉 Setup
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rdux
4
+ module SafePayload
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ attr_accessor :payload_unsanitized
9
+ end
10
+
11
+ def only_sanitized_payload?
12
+ payload_sanitized && payload_unsanitized.nil?
13
+ end
14
+
15
+ def safe_payload
16
+ payload_unsanitized || payload
17
+ end
18
+ end
19
+ end
@@ -4,26 +4,25 @@ module Rdux
4
4
  class Action < ActiveRecord::Base
5
5
  self.table_name_prefix = 'rdux_'
6
6
 
7
- attr_accessor :payload_unsanitized
7
+ include SafePayload
8
8
 
9
9
  belongs_to :rdux_action, optional: true, class_name: 'Rdux::Action'
10
+ belongs_to :process, optional: true, class_name: 'Rdux::Process', foreign_key: 'rdux_process_id'
10
11
  has_many :rdux_actions, class_name: 'Rdux::Action', foreign_key: 'rdux_action_id'
11
12
 
12
- if ActiveRecord::Base.connection.adapter_name != 'PostgreSQL'
13
- serialize :payload, coder: JSON
14
- serialize :result, coder: JSON
15
- serialize :meta, coder: JSON
16
- end
17
-
18
13
  validates :name, presence: true
19
14
  validates :payload, presence: true
20
15
 
21
16
  scope :ok, ->(val = true) { where(ok: val) }
22
17
  scope :failed, -> { where(ok: false) }
23
18
 
19
+ def process_defined?
20
+ has_attribute?(:rdux_process_id) && rdux_process_id
21
+ end
22
+
24
23
  def call(opts = {})
25
24
  return false if performed?
26
- return false if payload_sanitized && payload_unsanitized.nil?
25
+ return false if only_sanitized_payload?
27
26
 
28
27
  opts.merge!(action: self)
29
28
  perform_action(opts)
@@ -35,23 +34,14 @@ module Rdux
35
34
  !ok.nil?
36
35
  end
37
36
 
38
- def action_performer
39
- name_const = name.to_s.constantize
40
- return name_const if name_const.respond_to?(:call)
41
- return unless name_const.is_a?(Class)
42
-
43
- obj = name_const.new
44
- obj.respond_to?(:call) ? obj : nil
45
- end
46
-
47
37
  def perform_action(opts)
48
- performer = action_performer
38
+ performer = name.to_s.constantize
49
39
  return if performer.nil?
50
40
 
51
41
  if performer.method(:call).arity.abs == 2
52
- performer.call(payload_unsanitized || payload, opts)
42
+ performer.call(safe_payload, opts)
53
43
  else
54
- performer.call(payload_unsanitized || payload)
44
+ performer.call(safe_payload)
55
45
  end
56
46
  end
57
47
  end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rdux
4
+ class Process < ActiveRecord::Base
5
+ self.table_name_prefix = 'rdux_'
6
+
7
+ include SafePayload
8
+
9
+ has_many :actions, class_name: 'Rdux::Action', foreign_key: 'rdux_process_id', inverse_of: :process,
10
+ dependent: :nullify
11
+
12
+ validates :name, presence: true
13
+ validates :payload, presence: true
14
+ validate :steps_must_be_array
15
+
16
+ before_validation on: :create do
17
+ self.steps = performer::STEPS.map { _1.is_a?(Hash) ? _1[:name] : _1 }
18
+ end
19
+
20
+ def resume(res)
21
+ return res unless res.ok
22
+
23
+ ok_actions_count = 0
24
+ if res.val != :start
25
+ ok_actions_count = actions.ok.count
26
+ update!(ok: true) && return if ok_actions_count == steps.size
27
+ end
28
+
29
+ call_step(index: ok_actions_count, prev_res: res)
30
+ end
31
+
32
+ private
33
+
34
+ def steps_must_be_array
35
+ if !steps.is_a?(Array)
36
+ errors.add(:steps, 'must be an Array')
37
+ elsif steps.empty?
38
+ errors.add(:steps, 'must include at least 1 step')
39
+ end
40
+ end
41
+
42
+ def performer
43
+ name.constantize
44
+ end
45
+
46
+ def action_payload(step_def:, prev_res:)
47
+ step_def[:payload].is_a?(Proc) ? step_def[:payload].call(safe_payload, prev_res) : safe_payload
48
+ end
49
+
50
+ def call_step(index:, prev_res: nil)
51
+ step_def = performer::STEPS[index]
52
+ if steps[index].is_a?(Hash)
53
+ step_def.call(safe_payload, self)
54
+ return Rdux::Result[ok: nil]
55
+ end
56
+
57
+ action_payload = action_payload(step_def:, prev_res:)
58
+ Rdux.perform(steps[index], action_payload, process: self)
59
+ end
60
+ end
61
+ end
@@ -1,13 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class CreateRduxActions < ActiveRecord::Migration[7.0]
4
+ include Rdux::MigrationHelpers
5
+
4
6
  def change
5
7
  create_table :rdux_actions do |t|
6
8
  t.string :name, null: false
7
- t.column :payload, (ActiveRecord::Base.connection.adapter_name == 'PostgreSQL' ? :jsonb : :text), null: false
9
+ t.column :payload, json_column_type, null: false
8
10
  t.boolean :payload_sanitized, default: false, null: false
9
- t.column :result, (ActiveRecord::Base.connection.adapter_name == 'PostgreSQL' ? :jsonb : :text)
10
- t.column :meta, (ActiveRecord::Base.connection.adapter_name == 'PostgreSQL' ? :jsonb : :text)
11
+ t.column :result, json_column_type
12
+ t.column :meta, json_column_type
11
13
  t.column :ok, :boolean
12
14
 
13
15
  t.belongs_to :rdux_action, index: true, foreign_key: true
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateRduxProcesses < ActiveRecord::Migration[7.0]
4
+ include Rdux::MigrationHelpers
5
+
6
+ def change
7
+ create_table :rdux_processes do |t|
8
+ t.string :name, null: false
9
+ t.boolean :ok
10
+ t.column :steps, json_column_type,
11
+ null: false,
12
+ default: []
13
+ t.column :payload, json_column_type, null: false
14
+ t.boolean :payload_sanitized, default: false, null: false
15
+
16
+ t.timestamps
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddRduxProcessToRduxActions < ActiveRecord::Migration[7.0]
4
+ def change
5
+ add_reference :rdux_actions, :rdux_process, index: true, foreign_key: { to_table: :rdux_processes }
6
+ end
7
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rdux
4
+ module Dispatching
5
+ def dispatch(name, payload, opts_arg = {}, opts: nil, meta: nil, process: nil) # rubocop:disable Metrics/ParameterLists
6
+ opts ||= opts_arg
7
+ action = store(name, payload, ars: opts.delete(:ars), meta:, process:)
8
+ process(action, opts.merge(process:))
9
+ end
10
+
11
+ def store(name, payload, ars: nil, meta: nil, process: nil)
12
+ (ars || {}).each { |k, v| payload["#{k}_id"] = v.id }
13
+ action = Action.new(name:, payload:, meta:)
14
+ action.process = process if process
15
+ Sanitize.call(action)
16
+ action
17
+ end
18
+
19
+ def process(action, opts = {})
20
+ res = action.call(opts)
21
+ return res if destroy_action(res, action)
22
+
23
+ assign_to_action(res, action)
24
+ persist(res, action)
25
+ resume_process(action, res)&.tap { return _1 }
26
+
27
+ res
28
+ rescue StandardError => e
29
+ handle_exception(e, action)
30
+ end
31
+
32
+ alias perform dispatch
33
+
34
+ private
35
+
36
+ def destroy_action(res, action)
37
+ return false if res.ok || res.save
38
+ return false if action.process_defined?
39
+
40
+ action.destroy
41
+ end
42
+
43
+ def assign_to_action(res, action)
44
+ action.ok = res.ok
45
+ return unless res.result
46
+
47
+ action.result ||= {}
48
+ action.result.merge!(res.result)
49
+ end
50
+
51
+ def persist(res, action)
52
+ res.action = action.tap(&:save!)
53
+ res.nested&.each { |nested_res| action.rdux_actions << nested_res.action }
54
+ end
55
+
56
+ def resume_process(action, res)
57
+ return unless action.ok
58
+ return unless action.process_defined?
59
+
60
+ action.process.resume(res)
61
+ end
62
+
63
+ def handle_exception(exc, action)
64
+ raise(action.destroy && exc) if ENV['RDUX_DEV']
65
+
66
+ action.ok = false
67
+ action.result ||= {}
68
+ action.result.merge!({ 'Exception' => {
69
+ class: exc.class.name,
70
+ message: exc.message
71
+ } })
72
+ action.save!
73
+ raise exc
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rdux
4
+ module MigrationHelpers
5
+ private
6
+
7
+ def json_column_type
8
+ connection.adapter_name == 'PostgreSQL' ? :jsonb : :json
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rdux
4
+ module Processing
5
+ def start(performer, payload)
6
+ process = Process.new(name: performer, payload:)
7
+ Sanitize.call(process)
8
+ res = process.resume(Rdux::Result[ok: true, val: :start])
9
+ process.update!(ok: res.ok) unless res.ok.nil?
10
+ Result[ok: res.ok, val: { process: }]
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rdux
4
+ module Sanitize
5
+ def self.call(aro)
6
+ param_filter = ActiveSupport::ParameterFilter.new(Rails.application.config.filter_parameters)
7
+ payload_sanitized = param_filter.filter(aro.payload)
8
+ aro.payload_sanitized = aro.payload != payload_sanitized
9
+ aro.payload_unsanitized = aro.payload if aro.payload_sanitized
10
+ aro.payload = payload_sanitized
11
+ aro.save! if aro.changed?
12
+ end
13
+ end
14
+ end
data/lib/rdux/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rdux
4
- VERSION = '2.0.1'
4
+ VERSION = '3.0.0'
5
5
  end
data/lib/rdux.rb CHANGED
@@ -1,65 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rdux/engine'
4
- require 'rdux/store'
4
+ require 'rdux/migration_helpers'
5
5
  require 'rdux/result'
6
- require 'active_support/concern'
6
+ require 'rdux/sanitize'
7
+ require 'rdux/dispatching'
8
+ require 'rdux/processing'
7
9
 
8
10
  module Rdux
9
- class << self
10
- def dispatch(name, payload, opts = {}, meta: nil)
11
- action = store(name, payload, ars: opts[:ars], meta:)
12
- process(action, opts)
13
- end
14
-
15
- def store(name, payload, ars: nil, meta: nil)
16
- (ars || {}).each { |k, v| payload["#{k}_id"] = v.id }
17
- Store.call(name, payload, meta)
18
- end
19
-
20
- def process(action, opts = {})
21
- res = action.call(opts)
22
- return res if destroy_action(res, action)
23
-
24
- assign_to_action(res, action)
25
- persist(res, action)
26
- res
27
- rescue StandardError => e
28
- handle_exception(e, action)
29
- end
30
-
31
- alias perform dispatch
32
-
33
- private
34
-
35
- def destroy_action(res, action)
36
- return false if res.ok || res.save
37
-
38
- action.destroy
39
- end
40
-
41
- def assign_to_action(res, action)
42
- action.ok = res.ok
43
- return unless res.result
44
-
45
- action.result ||= {}
46
- action.result.merge!(res.result)
47
- end
48
-
49
- def persist(res, action)
50
- res.action = action.tap(&:save!)
51
- res.nested&.each { |nested_res| action.rdux_actions << nested_res.action }
52
- end
53
-
54
- def handle_exception(exc, action)
55
- action.ok = false
56
- action.result ||= {}
57
- action.result.merge!({ 'Exception' => {
58
- class: exc.class.name,
59
- message: exc.message
60
- } })
61
- action.save!
62
- raise exc
63
- end
64
- end
11
+ extend Dispatching
12
+ extend Processing
65
13
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rdux
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Zbigniew Humeniuk
@@ -15,7 +15,7 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: '7.0'
18
+ version: '7.1'
19
19
  - - "<"
20
20
  - !ruby/object:Gem::Version
21
21
  version: '9.0'
@@ -25,7 +25,7 @@ dependencies:
25
25
  requirements:
26
26
  - - ">="
27
27
  - !ruby/object:Gem::Version
28
- version: '7.0'
28
+ version: '7.1'
29
29
  - - "<"
30
30
  - !ruby/object:Gem::Version
31
31
  version: '9.0'
@@ -98,12 +98,19 @@ files:
98
98
  - MIT-LICENSE
99
99
  - README.md
100
100
  - Rakefile
101
+ - app/models/concerns/rdux/safe_payload.rb
101
102
  - app/models/rdux/action.rb
103
+ - app/models/rdux/process.rb
102
104
  - db/migrate/20230621215718_create_rdux_actions.rb
105
+ - db/migrate/20260109000000_create_rdux_processes.rb
106
+ - db/migrate/20260109000001_add_rdux_process_to_rdux_actions.rb
103
107
  - lib/rdux.rb
108
+ - lib/rdux/dispatching.rb
104
109
  - lib/rdux/engine.rb
110
+ - lib/rdux/migration_helpers.rb
111
+ - lib/rdux/processing.rb
105
112
  - lib/rdux/result.rb
106
- - lib/rdux/store.rb
113
+ - lib/rdux/sanitize.rb
107
114
  - lib/rdux/version.rb
108
115
  homepage: https://github.com/artofcodelabs/rdux
109
116
  licenses:
@@ -117,14 +124,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
117
124
  requirements:
118
125
  - - ">="
119
126
  - !ruby/object:Gem::Version
120
- version: 3.1.2
127
+ version: 3.3.8
121
128
  required_rubygems_version: !ruby/object:Gem::Requirement
122
129
  requirements:
123
130
  - - ">="
124
131
  - !ruby/object:Gem::Version
125
132
  version: '0'
126
133
  requirements: []
127
- rubygems_version: 3.6.8
134
+ rubygems_version: 4.0.6
128
135
  specification_version: 4
129
136
  summary: A Minimal Event Sourcing Plugin for Rails
130
137
  test_files: []
data/lib/rdux/store.rb DELETED
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Rdux
4
- module Store
5
- class << self
6
- def call(name, payload, meta)
7
- action = Action.new(name:, payload:, meta:)
8
- sanitize(action)
9
- action.save!
10
- action
11
- end
12
-
13
- private
14
-
15
- def sanitize(action)
16
- param_filter = ActiveSupport::ParameterFilter.new(Rails.application.config.filter_parameters)
17
- payload_sanitized = param_filter.filter(action.payload)
18
- action.payload_sanitized = action.payload != payload_sanitized
19
- action.payload_unsanitized = action.payload if action.payload_sanitized
20
- action.payload = payload_sanitized
21
- end
22
- end
23
- end
24
- end