composable-service 0.0.12 → 0.0.14

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: dc5b70b66baf4e7832ff94d98dc2c8d3b6549f8a125821d492ec4fa09d005678
4
- data.tar.gz: 2cfab4a6fb7894d73ee6367e7a4d2bceb1e6194122d4a8a9b8fe88b311df616c
3
+ metadata.gz: b3c690febbfbbd5921084d97b28acd8b0c1a501f6f9a317d72f58309ccd9019f
4
+ data.tar.gz: 0242eaf874208b7f5c57818fc934c09a98fef18ec869a9f47f66a476487c42d9
5
5
  SHA512:
6
- metadata.gz: 742240c553a3d2fbf5bd1fc79535babf18d511ff3fb9d6f149c301dd6ceeb6c8abf779c6e7f88e076c70e7a1b98a698c443b90e28b0316ed7affa296aeb42286
7
- data.tar.gz: 6bc3ac05b832b0fee8a5a9d2cce9177d0c927b51fc04683af14bb1f3def93f6713f268aa630d637ebb318b4a36e45771143ced991772f5d4e58b62cf23048317
6
+ metadata.gz: b7a4752d7a9228d8177abab42e2d03157535e9e504f46683eb3007aa0764fe3267b0bb67780e3d919ef2289d33296909dbde4704ae4020bfbb21fae7e72caad3
7
+ data.tar.gz: bf76c2769e5e29f5ec310dc0eba0430bf82ea69a6fd98791f080115bd71247db522e002400f1c284338b1037b50e33791531d75f4e86727dd43109bfa6847939
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## [Unreleased]
2
2
 
3
+ - Preserve results across nested subclasses and mark aborted callbacks as failures.
4
+ - Document service lifecycle and transaction boundaries.
5
+ - Add behavioral tests with complete line/branch coverage.
6
+
3
7
  ## [0.0.1] - 2022-01-24
4
8
 
5
9
  - Initial release
data/README.md CHANGED
@@ -1,43 +1,144 @@
1
- # Composable::Service
1
+ # composable-service
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/composable/service`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ Validated service objects with attributes, record composition, callbacks and an
4
+ explicit result. Requires Ruby 3.2+ and ActiveModel/ActiveSupport 7.2+.
5
+ Install `gem "composable-service"`; require `composable/service`.
4
6
 
5
- TODO: Delete this and the text above, and describe your gem
7
+ ## Implement an operation
6
8
 
7
- ## Installation
9
+ ```ruby
10
+ require "composable/service"
8
11
 
9
- Add this line to your application's Gemfile:
12
+ class BuildGreeting < Composable::Service::Command
13
+ attribute :name, type: :string
14
+ validates :name, presence: true
10
15
 
11
- ```ruby
12
- gem 'composable-service'
16
+ private
17
+
18
+ def save
19
+ "Hello, #{name}"
20
+ end
21
+ end
22
+
23
+ operation = BuildGreeting.call(name: "Example")
24
+ operation.success? # => true
25
+ operation.result # => "Hello, Example"
26
+ BuildGreeting.call(name: "").failure? # => true
13
27
  ```
14
28
 
15
- And then execute:
29
+ Implement `save`, whose return value becomes `result`. Call `.call` on the class
30
+ or initialize an instance and call it. Validation runs before the save callback
31
+ chain and persistence. `.call!` raises `Composable::Core::Error` on failure.
32
+ Before execution, `success?` and `failure?` both return false.
16
33
 
17
- $ bundle install
34
+ Use `before_save`, `around_save`, and `after_save` for operation lifecycle hooks.
35
+ An aborted callback or a save returning `false` records failure; nil is a valid
36
+ result. `rescue_from` handles expected `StandardError` subclasses for `.call`;
37
+ unhandled errors and all exceptions from `.call!` propagate. Handlers should add
38
+ an appropriate error without including secrets.
18
39
 
19
- Or install it yourself as:
40
+ ## Compose records
20
41
 
21
- $ gem install composable-service
42
+ `attribute`, `composable`, `sync`, typing, defaults and conditions come from
43
+ [core](../composable-core/README.md). Declare explicit mappings for allowed
44
+ writes. Record assignment initializes mapped attributes; explicit operation
45
+ attributes override them. Eligible records are saved before `save`, inside nested
46
+ record transactions. ActiveRecord validation errors become operation errors.
22
47
 
23
- ## Usage
48
+ Exceptions roll back changes on a shared database connection. Adding errors alone
49
+ does not undo writes. External API calls and work across multiple databases are
50
+ not transactional; use application-level compensation or an outbox when needed.
51
+ Service objects do not authorize resources or manage job retries automatically.
24
52
 
25
- TODO: Write usage instructions here
53
+ Use a fresh operation for each execution. Read failures before consuming the
54
+ result. See [testing](../docs/testing.md) for running this gem's suite.
26
55
 
27
- ## Development
56
+ ## Inspect failure and use the raising interface
28
57
 
29
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
58
+ ```ruby
59
+ operation = BuildGreeting.new(name: "")
60
+ operation.success? # => false: it has not run yet
61
+ operation.failure? # => false
62
+
63
+ operation.call
64
+ operation.failure? # => true
65
+ operation.errors.full_messages # => ["Name can't be blank"]
66
+ operation.result # => nil
67
+
68
+ begin
69
+ BuildGreeting.call!(name: "")
70
+ rescue Composable::Core::Error => error
71
+ # Present a suitable application error; do not assume a result is available.
72
+ end
73
+ ```
30
74
 
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
75
+ ## Add callbacks and handle expected exceptions
32
76
 
33
- ## Contributing
77
+ This example takes an application-provided delivery adapter. The adapter must
78
+ implement `deliver(message)` and return a receipt; it may raise `DeliveryUnavailable`.
34
79
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/jairovm/composables. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/jairovm/composables/tree/main/CODE_OF_CONDUCT.md).
80
+ ```ruby
81
+ class DeliveryUnavailable < StandardError; end
82
+
83
+ class SendNotice < Composable::Service::Command
84
+ attribute :message, type: :string
85
+ attribute :delivery
86
+ validates :message, :delivery, presence: true
87
+
88
+ before_save do
89
+ self.message = message.strip
90
+ end
91
+
92
+ rescue_from DeliveryUnavailable do
93
+ errors.add(:base, "Delivery is temporarily unavailable")
94
+ end
95
+
96
+ private
97
+
98
+ def save
99
+ delivery.deliver(message)
100
+ end
101
+ end
102
+
103
+ # adapter is supplied by the application.
104
+ operation = SendNotice.call(message: " Example notice ", delivery: adapter)
105
+ if operation.success?
106
+ receipt = operation.result
107
+ else
108
+ messages = operation.errors.full_messages
109
+ end
110
+ ```
36
111
 
37
- ## License
112
+ Only the expected exception is translated. Other exceptions propagate, and
113
+ `SendNotice.call!` propagates `DeliveryUnavailable` without calling the handler.
114
+ The callback runs after validation; use setters when normalization must affect
115
+ validation itself.
38
116
 
39
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
117
+ ## Update a composed record
40
118
 
41
- ## Code of Conduct
119
+ Assume the application defines a `Project` model with a `name` column. The caller
120
+ must obtain an authorized project before invoking the service.
121
+
122
+ ```ruby
123
+ class RenameProject < Composable::Service::Command
124
+ attribute :name, type: :string
125
+ validates :name, presence: true
126
+
127
+ composable :project do
128
+ sync :name
129
+ end
130
+
131
+ private
132
+
133
+ def save
134
+ project
135
+ end
136
+ end
137
+
138
+ operation = RenameProject.call(project: authorized_project, name: "New name")
139
+ operation.result # => the saved project on success
140
+ ```
42
141
 
43
- Everyone interacting in the Composable::Service project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/jairovm/composables/tree/main/CODE_OF_CONDUCT.md).
142
+ Implement `save` in subclasses, including subclasses of your application service
143
+ base. Overriding `call` can bypass the inherited command wrapper. Use a fresh
144
+ instance for each operation instead of calling the same instance repeatedly.
@@ -6,20 +6,16 @@ module Composable
6
6
  include Core::AttributeDSL
7
7
  include Core::ComposableDSL
8
8
  include Core::Callbacks
9
-
10
- class << self
11
- def inherited(subclass)
12
- super
13
- subclass.prepend(Core::Command)
14
- end
15
- end
9
+ prepend Core::Command
16
10
 
17
11
  def call
18
12
  return unless valid?
19
13
 
20
- run_callbacks :save do
21
- save_composables { save }
14
+ completed = run_callbacks :save do
15
+ @result = save_composables { save }
22
16
  end
17
+ errors.add(:base, :invalid) if completed == false
18
+ @result
23
19
  end
24
20
 
25
21
  private
@@ -10,7 +10,7 @@ module Composable
10
10
  module VERSION
11
11
  MAJOR = 0
12
12
  MINOR = 0
13
- TINY = 12
13
+ TINY = 14
14
14
  PRE = nil
15
15
 
16
16
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: composable-service
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.12
4
+ version: 0.0.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jairo Vazquez
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-03-02 00:00:00.000000000 Z
10
+ date: 2026-10-07 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: composable-core
@@ -16,14 +15,14 @@ dependencies:
16
15
  requirements:
17
16
  - - '='
18
17
  - !ruby/object:Gem::Version
19
- version: 0.0.12
18
+ version: 0.0.14
20
19
  type: :runtime
21
20
  prerelease: false
22
21
  version_requirements: !ruby/object:Gem::Requirement
23
22
  requirements:
24
23
  - - '='
25
24
  - !ruby/object:Gem::Version
26
- version: 0.0.12
25
+ version: 0.0.14
27
26
  description: Service composable object to work with ruby services
28
27
  email:
29
28
  - jairovm20@gmail.com
@@ -45,7 +44,6 @@ metadata:
45
44
  homepage_uri: https://github.com/jairovm/composables
46
45
  source_code_uri: https://github.com/jairovm/composables/tree/main/composable-service
47
46
  changelog_uri: https://github.com/jairovm/composables/tree/main/composable-service/CHANGELOG.md
48
- post_install_message:
49
47
  rdoc_options: []
50
48
  require_paths:
51
49
  - lib
@@ -53,15 +51,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
53
51
  requirements:
54
52
  - - ">="
55
53
  - !ruby/object:Gem::Version
56
- version: 2.7.0
54
+ version: 3.2.0
57
55
  required_rubygems_version: !ruby/object:Gem::Requirement
58
56
  requirements:
59
57
  - - ">="
60
58
  - !ruby/object:Gem::Version
61
59
  version: '0'
62
60
  requirements: []
63
- rubygems_version: 3.5.5
64
- signing_key:
61
+ rubygems_version: 3.6.2
65
62
  specification_version: 4
66
63
  summary: Service Composable Object
67
64
  test_files: []