README.md in the_help-1.4.2 vs README.md in the_help-1.5.0
- old
+ new
@@ -1,11 +1,11 @@
# TheHelp
-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/the_help`. To experiment with that code, run `bin/console` for an interactive prompt.
+TheHelp is a framework for developing service objects in a way that encourages
+adherence to the [Single Responsibility Principle][SRP] and [Tell Don't
+Ask][TDA]
-TODO: Delete this and the text above, and describe your gem
-
## Installation
Add this line to your application's Gemfile:
```ruby
@@ -20,12 +20,127 @@
$ gem install the_help
## Usage
-TODO: Write usage instructions here
+Create subclasses of [`TheHelp::Service`][lib/the_help/service.rb] and call
+them.
+Make it easier to call a service by including
+[`TheHelp::ServiceCaller`][lib/the_help/service_caller.rb].
+
+### Running Callbacks
+
+This library encourages you to pass in callbacks to a service rather than
+relying on a return value from the service call. For example:
+
+```ruby
+class Foo < TheHelp::Service
+ authorization_policy allow_all: true
+
+ main do
+ call_service(GetSomeWidgets,
+ customer_id: 12345,
+ each_widget: method(:process_widget),
+ invalid_customer: method(:no_customer),
+ no_widgets_found: method(:no_widgets))
+ do_something_else
+ end
+
+ private
+
+ def process_widget(widget)
+ # do something with it
+ end
+
+ def invalid_customer
+ # handle this case
+ stop!
+ end
+
+ def no_widgets
+ # handle this case
+ end
+
+ def do_something_else
+ # ...
+ end
+end
+```
+
+When writing a service that accepts callbacks like this, do not simply run
+`#call` on the callback that was passed in. Instead you must use the
+`#run_callback` method. This ensures that, if the callback method you pass in
+tries to halt the execution of the service, it will behave as expected.
+
+In the above service, it is clear that the intention is to stop executing the
+`Foo` service in the case where `GetSomeWidgets` reports back that the customer
+was invalid. However, if `GetSomeWidgets` is implemented as:
+
+```ruby
+class GetSomeWidgets < TheHelp::Service
+ input :customer_id
+ input :each_widget
+ input :invalid_customer
+ input :no_widgets_found
+
+ authorization_policy allow_all: true
+
+ main do
+ set_some_stuff_up
+ if customer_invalid?
+ invalid_customer.call
+ no_widgets_found.call
+ do_some_important_cleanup_for_invalid_customers
+ else
+ #...
+ end
+ end
+
+ #...
+end
+```
+
+then the problem is that the call to `#stop!` in the `Foo#invalid_customer`
+callback will not just stop the `Foo` service, it will also stop the
+`GetSomeWidgets` service at the point where the callback is executed (because it
+uses `throw` behind the scenes.) This would cause the
+`do_some_important_cleanup_for_invalid_customers` method to never be called.
+
+You can protect yourself from this by implementing `GetSomeWidgets` like this,
+instead:
+
+```ruby
+class GetSomeWidgets < TheHelp::Service
+ input :customer_id
+ input :each_widget
+ input :invalid_customer
+ input :no_widgets_found
+
+ authorization_policy allow_all: true
+
+ main do
+ set_some_stuff_up
+ if customer_invalid?
+ run_callback(invalid_customer)
+ run_callback(no_widgets_found)
+ do_some_important_cleanup_for_invalid_customers
+ else
+ #...
+ end
+ end
+
+ #...
+end
+```
+
+This will ensure that callbacks only stop the service that provides them, not
+the service that calls them. (If you really do need to allow the calling service
+to stop the execution of the inner service, you could raise an exception or
+throw a symbol other than `:stop`; but do so with caution, since it may have
+unintended consequences further down the stack.)
+
## Development
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
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 tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
@@ -39,5 +154,8 @@
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
## Code of Conduct
Everyone interacting in the TheHelp project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/jwilger/the_help/blob/master/CODE_OF_CONDUCT.md).
+
+[SRP]: https://en.wikipedia.org/wiki/Single_responsibility_principle
+[TDA]: https://martinfowler.com/bliki/TellDontAsk.html