Skip to content

Commit

Permalink
Merge pull request #3 from Andy9822/andy9822-support-multi-types-para…
Browse files Browse the repository at this point in the history
…meters

✨ feat: Support multi-types parameters
  • Loading branch information
Andy9822 authored Sep 18, 2024
2 parents 8db9343 + 75c8734 commit dc57f5c
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 13 deletions.
58 changes: 49 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,31 @@ Or install it yourself as:

### Parameters

#### Defining Parameters
```ruby
class CalculationService < BoringService
class RandomService < BoringService
# Type checking is optional. If omitted, anything is accepted.
# Type checking can also be done with a proc or anything that responds to #===
# e.g. parameter :start_number, ->(p) { p.respond_to?(:to_i) }
parameter :random_object

def call
random_object&.class
end
end

# The class-method version of `call` accepts the arguments as named parameters and, subsequently, calls the
# instance-method version of `call` (as it's defined in your service class).
RandomService.call #=> raise BoringService::ParameterRequired
RandomService.call(random_object: 'test') #=> String
RandomService.call(random_object: 1) #=> Integer
```

#### Basic Parameter Type Checking
```ruby
class CalculationService < BoringService
# If a type checking Class is provided, the type check will be enforced against the defined parameter.
parameter :start_number, Integer

# Parameters that define a default are optional.
# `default` supports also a proc, that gets evaluated at
# object instantiation.
# e.g. default: -> { Time.now }
# Parameters that define a `default` are optional.
parameter :end_number, Integer, default: 2

def call
Expand All @@ -52,13 +66,39 @@ class CalculationService < BoringService
end
end

# The class-method version of `call` accepts the arguments as named parameters and, subsequently, calls
# the instance-method version of `call` (as it's defined in your service object).
CalculationService.call(start_number: 1, end_number: 3) #=> 46
CalculationService.call(start_number: 1) #=> 45
CalculationService.call(start_number: '1') #=> BoringService::InvalidParameterValue
CalculationService.call(end_number: 3) #=> raise BoringService::ParameterRequired
```

#### Advanced Options for Parameter Type Checking
```ruby
class MoreOptionsService < BoringService
# Type checking can also be done with an Array of classes, a proc/lambda, or anything that responds to #===.
parameter :times_to_run, [String, Integer]
parameter :return_output, -> (param) { param.in?([true, false]) }

# `default` also supports a proc/lambda, that gets evaluated at object instantiation.
parameter :timestamp, Time, default: -> { Time.now.beginning_of_day }

def call
Array.new(times_to_run.to_i) { timestamp if return_output }
end
end

MoreOptionsService.call(times_to_run: 1, return_output: true) #=> [2024-08-30 00:00:00 -0400]
MoreOptionsService.call(times_to_run: 1, return_output: false) #=> [nil]
MoreOptionsService.call(times_to_run: 5, return_output: false) #=> [nil, nil, nil, nil, nil]
MoreOptionsService.call(times_to_run: '5', return_output: false) #=> [nil, nil, nil, nil, nil]
MoreOptionsService.call(times_to_run: 5, return_output: 'false') #=> raise BoringService::InvalidParameterValue
MoreOptionsService.call(
times_to_run: 1,
return_output: true,
timestamp: Time.now.beginning_of_day - 1.day
) #=> [2024-08-29 00:00:00 -0400]
```

### Hooks

```ruby
Expand Down
4 changes: 2 additions & 2 deletions lib/boring-service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ def call(**args)
# Parameters are also inherited from superclasses and can be redefined (overwritten) in subclasses.
#
# @param name [Symbol] name of the parameter
# @param type [Class, #===] type of the parameter. Can be a Class, a Proc or anything that defines a meaningful
# `===` method
# @param type [Class, Array, #===] type of the parameter. Can be a Class, an Array of classes, a Proc, or anything
# that defines a meaningful `===` method.
# @param **options [Hash] extra options for the parameter
# @option **options [Object, #call] :default default value if the parameter is not passed. If the default implements
# `#call`, it gets called once in the context of the method object instance when it is instantiated.
Expand Down
7 changes: 6 additions & 1 deletion lib/boring-service/parameter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,13 @@ def default_in(context)
end
end

# rubocop:disable Style/CaseEquality
def acceptable?(value)
(value.nil? && nullable?) || type === value # rubocop:disable Style/CaseEquality
if type.is_a?(Array)
type.any? { |t| t === value }
else
(value.nil? && nullable?) || type === value
end
end

def nullable?
Expand Down
2 changes: 1 addition & 1 deletion lib/boring-service/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

class BoringService
VERSION = "1.0.1"
VERSION = "1.1.0"
end
20 changes: 20 additions & 0 deletions spec/boring_service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ def first_pre_hook
end
end

class MultiTypeService < BoringService
parameter :required_param, [String, Integer]

def call
puts required_param
end
end

RSpec.describe BoringService do
describe ".call" do
it "should raise an error if a required param is not passed" do
Expand All @@ -46,5 +54,17 @@ class BadService < BoringService; end

expect { BadService.call }.to raise_error NotImplementedError
end

it "should support multiple types for a parameter" do
response = MultiTypeService.call(required_param: "foo")
expect(response[:required_param]).to eq "foo"

response = MultiTypeService.call(required_param: 5)
expect(response[:required_param]).to eq 5
end

it "should raise an error if the param type is not includes in the expected types array" do
expect { MultiTypeService.call(required_param: 5.5) }.to raise_error BoringService::InvalidParameterValue
end
end
end
1 change: 1 addition & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
require 'set'
require 'boring-service'

Dir[File.expand_path("../support/*.rb", __FILE__)].sort.each { |f| require f }

0 comments on commit dc57f5c

Please sign in to comment.