Skip to content

Commit

Permalink
Allow params checks in preconditions
Browse files Browse the repository at this point in the history
  • Loading branch information
pyromaniac committed Dec 10, 2024
1 parent cdfc1e5 commit 9dbf468
Show file tree
Hide file tree
Showing 17 changed files with 82 additions and 41 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ jobs:
- { ruby: '3.0', rails: '6.1' }
- { ruby: '3.1', rails: '7.0' }
- { ruby: '3.2', rails: '7.1' }
- { ruby: '3.3', rails: '7.2' }
- { ruby: '3.4', rails: '8.0' }
runs-on: ubuntu-latest
env:
BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/rails.${{ matrix.rails }}.gemfile
Expand Down
3 changes: 2 additions & 1 deletion Appraisals
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# frozen_string_literal: true

%w[5.2 6.0 6.1 7.0 7.1].each do |version|
%w[5.2 6.0 6.1 7.0 7.1 7.2 8.0].each do |version|
appraise "rails.#{version}" do
gem "activerecord", "~> #{version}.0"
gem "activesupport", "~> #{version}.0"
gem "sqlite3", version > "7.0" ? "~> 2.1" : "~> 1.4"
end
end
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

## [main](https://github.com/BookingSync/operations/tree/main)

### Changes
- Changed `Operations::Command::OperationFailed#message` to include detailed error messages
### Added

- Allow receiving params in preconditions. [\#56](https://github.com/BookingSync/operations/pull/56) ([pyromaniac](https://github.com/pyromaniac))

### Changes

- Changed `Operations::Command::OperationFailed#message` to include detailed error messages. [\#55](https://github.com/BookingSync/operations/pull/55) ([Azdaroth](https://github.com/Azdaroth))
- Rename Operations::Form#model_name parameter to param_key and make it public preserving backwards compatibility. [\#52](https://github.com/BookingSync/operations/pull/52) ([pyromaniac](https://github.com/pyromaniac))

## [0.7.2](https://github.com/BookingSync/operations/tree/v0.7.2)
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ When we need to check against the application state, preconditions are coming to

There are many potential scenarios when it can be handy. For example, we might need to render a button only when the subject entity satisfies preconditions for a particular operation. Or we want to return a list of possible operations from an API we have.

**Important:** a rule of thumb here is that preconditions don't depend on the user input, they only check the existing state of the application and they are supposed to access only the operation context for this purpose, not params.
**Important:** a rule of thumb here is that preconditions always depend on application/entities state. If a check depends only on params, then it is rather a Contract validation.

```ruby
class Post::Publish
Expand Down Expand Up @@ -425,7 +425,7 @@ class Post::Publish::NotPublishedPrecondition
include Dry::Monads[:result]

def call(post:, **)
return Failure(error: :already_published, tokens: { published_at: post.published_at }) if post.published?
return Failure(error: :already_published, path: [:post_id], tokens: { published_at: post.published_at }) if post.published?

Success()
end
Expand Down
1 change: 1 addition & 0 deletions gemfiles/rails.5.2.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ gem "rubocop-rails", require: false
gem "rubocop-rspec", require: false
gem "activerecord", "~> 5.2.0"
gem "activesupport", "~> 5.2.0"
gem "sqlite3", "~> 1.4"

gemspec path: "../"
1 change: 1 addition & 0 deletions gemfiles/rails.6.0.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ gem "rubocop-rails", require: false
gem "rubocop-rspec", require: false
gem "activerecord", "~> 6.0.0"
gem "activesupport", "~> 6.0.0"
gem "sqlite3", "~> 1.4"

gemspec path: "../"
1 change: 1 addition & 0 deletions gemfiles/rails.6.1.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ gem "rubocop-rails", require: false
gem "rubocop-rspec", require: false
gem "activerecord", "~> 6.1.0"
gem "activesupport", "~> 6.1.0"
gem "sqlite3", "~> 1.4"

gemspec path: "../"
1 change: 1 addition & 0 deletions gemfiles/rails.7.0.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ gem "rubocop-rails", require: false
gem "rubocop-rspec", require: false
gem "activerecord", "~> 7.0.0"
gem "activesupport", "~> 7.0.0"
gem "sqlite3", "~> 1.4"

gemspec path: "../"
1 change: 1 addition & 0 deletions gemfiles/rails.7.1.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ gem "rubocop-rails", require: false
gem "rubocop-rspec", require: false
gem "activerecord", "~> 7.1.0"
gem "activesupport", "~> 7.1.0"
gem "sqlite3", "~> 2.1"

gemspec path: "../"
15 changes: 15 additions & 0 deletions gemfiles/rails.7.2.gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# This file was generated by Appraisal

source "https://rubygems.org"

gem "bookingsync-rubocop", require: false, github: "BookingSync/bookingsync-rubocop", branch: "main"
gem "rspec"
gem "rubocop", require: false
gem "rubocop-performance", require: false
gem "rubocop-rails", require: false
gem "rubocop-rspec", require: false
gem "activerecord", "~> 7.2.0"
gem "activesupport", "~> 7.2.0"
gem "sqlite3", "~> 2.1"

gemspec path: "../"
15 changes: 15 additions & 0 deletions gemfiles/rails.8.0.gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# This file was generated by Appraisal

source "https://rubygems.org"

gem "bookingsync-rubocop", require: false, github: "BookingSync/bookingsync-rubocop", branch: "main"
gem "rspec"
gem "rubocop", require: false
gem "rubocop-performance", require: false
gem "rubocop-rails", require: false
gem "rubocop-rspec", require: false
gem "activerecord", "~> 8.0.0"
gem "activesupport", "~> 8.0.0"
gem "sqlite3", "~> 2.1"

gemspec path: "../"
2 changes: 1 addition & 1 deletion lib/operations/command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ class OperationFailed < StandardError

def initialize(operation_result)
@operation_result = operation_result
operation_class_name = operation_result.operation&.operation&.class&.name
operation_class_name = operation_result.operation.operation.class.name if operation_result.operation

super("#{operation_class_name} failed on #{operation_result.component}")
end
Expand Down
3 changes: 2 additions & 1 deletion lib/operations/components/preconditions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
class Operations::Components::Preconditions < Operations::Components::Prechecks
def call(params, context)
failures = callable.flat_map do |entry|
results = Array.wrap(entry.call(**context))
arg_names = call_args(entry, types: %i[req opt])
results = Array.wrap(arg_names.one? ? entry.call(params, **context) : entry.call(**context))
results.filter_map { |result| result_failure(result) }
end

Expand Down
2 changes: 1 addition & 1 deletion operations.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Gem::Specification.new do |spec|

spec.add_development_dependency "appraisal"
spec.add_development_dependency "database_cleaner-active_record"
spec.add_development_dependency "sqlite3", "~> 1.4"
spec.add_development_dependency "sqlite3", ">= 1.4"

spec.add_dependency "activerecord", ">= 5.2.0"
spec.add_dependency "activesupport", ">= 5.2.0"
Expand Down
49 changes: 25 additions & 24 deletions spec/operations/command_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1008,30 +1008,31 @@ def call; end
subject(:pretty_inspect) { command.pretty_inspect }

specify do
expect(pretty_inspect.gsub(%r{Proc:0x[^>]+}, "Proc:0x").gsub(%r{Class:0x[^>]+}, "Class:0x")).to eq(<<~INSPECT)
#<Operations::Command
operation=#<Proc:0x>,
contract=#<#<Class:0x> schema=#<Dry::Schema::Processor keys=[:name] rules={:name=>"key?(:name) \
AND key[name](str? AND filled?)"}> rules=[#<Dry::Validation::Rule keys=[]>]>,
policies=[#<Proc:0x>],
idempotency=[],
preconditions=[#<Proc:0x>],
on_success=[#<Proc:0x>],
on_failure=[#<Proc:0x>],
form_model_map={},
form_base=#<Class attributes={}>,
form_class=#<Class
attributes={:name=>
#<Operations::Form::Attribute
name=:name,
collection=false,
model_class=nil,
model_attribute=nil,
form=nil>}>,
form_hydrator=#<Proc:0x>,
configuration=#<Operations::Configuration info_reporter=nil \
error_reporter=#<Proc:0x> transaction=#<Proc:0x> after_commit=#<Proc:0x>>>
INSPECT
expect(pretty_inspect.gsub(%r{Proc:0x[^>]+}, "Proc:0x").gsub(%r{\:(\w+)=>}, '\1:')
.gsub(%r{Class:0x[^>]+}, "Class:0x")).to eq(<<~INSPECT)
#<Operations::Command
operation=#<Proc:0x>,
contract=#<#<Class:0x> schema=#<Dry::Schema::Processor keys=[:name] rules={name:"key?(:name) \
AND key[name](str? AND filled?)"}> rules=[#<Dry::Validation::Rule keys=[]>]>,
policies=[#<Proc:0x>],
idempotency=[],
preconditions=[#<Proc:0x>],
on_success=[#<Proc:0x>],
on_failure=[#<Proc:0x>],
form_model_map={},
form_base=#<Class attributes={}>,
form_class=#<Class
attributes={name:
#<Operations::Form::Attribute
name=:name,
collection=false,
model_class=nil,
model_attribute=nil,
form=nil>}>,
form_hydrator=#<Proc:0x>,
configuration=#<Operations::Configuration info_reporter=nil \
error_reporter=#<Proc:0x> transaction=#<Proc:0x> after_commit=#<Proc:0x>>>
INSPECT
end
end
end
9 changes: 4 additions & 5 deletions spec/operations/components/preconditions_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,12 @@
{ text: "Failure 4", path: [:name, 1], code: :foobar }
]
},
->(**) { { error: "Failure", foo: 42, path: [nil] } },
->(params, **) { { error: "Failure", path: [nil], **params } },
->(**) {}
]
end

it "aggregates failures" do
pp call.errors.to_h
expect(call)
.to be_failure
.and have_attributes(
Expand All @@ -67,7 +66,7 @@
{ text: "Failure 1", code: :failure1 },
{ text: "Failure 1", code: :failure1 },
"failure3",
{ text: "Failure", foo: 42 }
{ text: "Failure", name: "Batman" }
],
name: [["failure2"], { 1 => [{ text: "Failure 4", code: :foobar }] }]
}
Expand All @@ -81,7 +80,7 @@
{ text: "Failure 1", code: :failure1 },
{ text: "Failure 1", code: :failure1 },
"failure3",
{ text: "Failure", foo: 42 }
{ text: "Failure", name: "Batman" }
],
name: [["name failure2"], { 1 => [{ code: :foobar, text: "1 Failure 4" }] }]
)
Expand All @@ -90,7 +89,7 @@
{ text: "Échec 1", code: :failure1 },
{ text: "Échec 1", code: :failure1 },
"failure3",
{ text: "Failure", foo: 42 }
{ text: "Failure", name: "Batman" }
],
name: [["failure2"], { 1 => [{ code: :foobar, text: "Failure 4" }] }]
)
Expand Down
8 changes: 4 additions & 4 deletions spec/operations/form_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ def call(_, **)
subject(:pretty_inspect) { form.pretty_inspect }

specify do
expect(pretty_inspect.gsub(%r{Proc:0x[^>]+}, "Proc:0x")).to eq(<<~INSPECT)
expect(pretty_inspect.gsub(%r{Proc:0x[^>]+}, "Proc:0x").gsub(%r{\:(\w+)=>}, '\1:')).to eq(<<~INSPECT)
#<Operations::Form
param_key="dummy_operation_form",
model_map=#<Proc:0x>,
Expand All @@ -175,21 +175,21 @@ def call(_, **)
hydrators=[#<Proc:0x>],
hydration_merge_params=true,
form_class=#<Class
attributes={:entities=>
attributes={entities:
#<Operations::Form::Attribute
name=:entities,
collection=true,
model_class=DummyModel,
model_attribute="entities",
form=#<Class
attributes={:id=>
attributes={id:
#<Operations::Form::Attribute
name=:id,
collection=false,
model_class=DummyModel,
model_attribute="id",
form=nil>}>>,
:name=>
name:
#<Operations::Form::Attribute
name=:name,
collection=false,
Expand Down

0 comments on commit 9dbf468

Please sign in to comment.