Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add extension for ROM transactions #15

Merged
merged 1 commit into from
Jun 10, 2024
Merged

Conversation

waiting-for-dev
Copy link
Member

We add a Dry::Operation::Extensions::ROM module that, when included, gives access to a #transaction method. This method wraps the yielded steps in a ROM transaction, rolling back in case one of them returns a failure.

We lean on a new Dry::Operation#intercepting_failure method, which allows running a callback before the failure is re-thrown again to be managed by the wrapping #steps call. Besides providing clarity, this method will be reused by future extensions.

The extension expects the including class to define a #rom method giving access to the ROM container.

class MyOperation < Dry::Operation
  include Dry::Operation::Extensions::ROM

  attr_reader :rom

  def initialize(rom:)
    @rom = rom
  end

  def call(input)
    attrs = step validate(input)
    user = transaction do
      new_user = step persist(attrs)
      step assign_initial_role(new_user)
      new_user
    end
    step notify(user)
    user
  end

  # ...
end

The extension uses the :default gateway by default, but it can be changed both at include time with include Dry::Operation::Extensions::ROM[gateway: :my_gateway], and at runtime with #transaction(gateway: :my_gateway).

This commit also establishes the dry-operation's convention for database transactions. Instead of wrapping the whole flow, we require the user to be conscious of the transaction boundaries (not including, e.g., external requests or notifications). That encourages using individual operations when thinking about composition instead of the whole flow.

@waiting-for-dev
Copy link
Member Author

A quick note about being able to provide options to the #transaction method. It looks like when you invoke #transaction on a ROM relation, you can provide different options that are forwarded to the underlying engine. However, that doesn't look to be the case when you invoke Gateway#transaction (as we need to do here). ROM::Gateway will invoke a #transaction_runner method defined in every adapter and will forward options to it. For rom-sql, options are ignored.

@timriley, @solnic, please, just tell me if I'm missing something.

@waiting-for-dev waiting-for-dev force-pushed the waiting-for-dev/rom branch 2 times, most recently from ce38bc3 to 5a96ae4 Compare November 27, 2023 10:36
Copy link
Member

@timriley timriley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Just a couple very minor things here but otherwise let's get this in :)

Let's make sure we get the issue with the #transaction arguments properly looked at, too, but AFAICT that shouldn't prevent this from being merged in the meantime?

We add a `Dry::Operation::Extensions::ROM` module that, when included,
gives access to a `#transaction` method. This method wraps the yielded
steps in a ROM [1] transaction, rolling back in case one of them returns
a failure.

We lean on a new `Dry::Operation#intercepting_failure` method, which
allows running a callback before the failure is re-thrown again to be
managed by the wrapping `#steps` call. Besides providing clarity, this
method will be reused by future extensions.

The extension expects the including class to define a `#rom` method
giving access to the ROM container.

```ruby
class MyOperation < Dry::Operation
  include Dry::Operation::Extensions::ROM

  attr_reader :rom

  def initialize(rom:)
    @rom = rom
  end

  def call(input)
    attrs = step validate(input)
    user = transaction do
      new_user = step persist(attrs)
      step assign_initial_role(new_user)
      new_user
    end
    step notify(user)
    user
  end

  # ...
end
```

The extension uses the `:default` gateway by default, but it can be
changed both at include time with `include
Dry::Operation::Extensions::ROM[gateway: :my_gateway]`, and at runtime
with `#transaction(gateway: :my_gateway)`.

This commit also establishes the dry-operation's convention for database
transactions. Instead of wrapping the whole flow, we require the user to
be conscious of the transaction boundaries (not including, e.g.,
external requests or notifications). That encourages using individual
operations when thinking about composition instead of the whole flow.

[1] - https://rom-rb.org
@waiting-for-dev waiting-for-dev merged commit eac1bd5 into main Jun 10, 2024
6 checks passed
@waiting-for-dev waiting-for-dev deleted the waiting-for-dev/rom branch June 10, 2024 16:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants