From 2630109c8b8ce210592d29ce45f5198de81ad3fe Mon Sep 17 00:00:00 2001 From: Ted Johansson Date: Mon, 8 Mar 2021 11:52:27 +0800 Subject: [PATCH] [Feature] Add FunctionalObject mixin --- README.md | 45 +++++++++++++++++++++++-- lib/stimpack/functional_object.rb | 28 +++++++++++++++ spec/stimpack/functional_object_spec.rb | 43 +++++++++++++++++++++++ 3 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 lib/stimpack/functional_object.rb create mode 100644 spec/stimpack/functional_object_spec.rb diff --git a/README.md b/README.md index ddaeecb..eab2182 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,44 @@ -# Stimpack +# Stimpack 💉 -Supporting libraries for NydusNetwork. +Supporting libraries for NydusNetwork. Stimpack consists of a number of well +tested building blocks which can be used independently, or be combined at the +application level to build systems with consistent, well-defined interfaces +and behaviour. + +## Table of Contents + +- [FunctionalObject](#functionalobject) + +## FunctionalObject + +A simple mixin that provides a shorthand notation for instantiating and +invoking `#call` on an object. + +**Example:** + +Given this class definition: + +```ruby +class Foo + include FunctionalObject + + def initialize(bar:) + @bar = bar + end + + def call + puts bar + end + + private + + attr_reader :bar +end +``` + +we can now initialize and invoke an instance of `Foo` by calling: + +```ruby +Foo.(bar: "Hello world!") +#=> "Hello world!" +``` diff --git a/lib/stimpack/functional_object.rb b/lib/stimpack/functional_object.rb new file mode 100644 index 0000000..6d1537b --- /dev/null +++ b/lib/stimpack/functional_object.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Stimpack + module FunctionalObject + module ClassMethods + # Instantiates an object and proxies arguments to the `#call` method. + # This allows for shorthand invocation, e.g.: + # + # AccruePoints.(user: user, amount: amount) + # + # which shortens code and aids in stubbing responses. + # + def call(...) + new(...).() + end + end + + def self.included(klass) + klass.extend(ClassMethods) + end + + # This is the main entry point to be implemented by each concrete class. + # + def call + raise NotImplementedError + end + end +end diff --git a/spec/stimpack/functional_object_spec.rb b/spec/stimpack/functional_object_spec.rb new file mode 100644 index 0000000..7ef8ae1 --- /dev/null +++ b/spec/stimpack/functional_object_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require "stimpack/functional_object" + +RSpec.describe Stimpack::FunctionalObject do + subject(:service) { klass } + + describe ".call" do + context "when service does not implement #call" do + let(:klass) do + Class.new do + include Stimpack::FunctionalObject + end + end + + it { expect { klass.() }.to raise_error(NotImplementedError) } + end + + context "when service implements #call" do + let(:klass) do + Class.new do + include Stimpack::FunctionalObject + + def initialize(foo, bar:, &block) + @foo = foo + @bar = bar + @baz = block.() + end + + attr_reader :foo, :bar, :baz + + def call + [foo, bar, baz] + end + end + end + + it "delegates arguments, options, and block" do + expect(klass.("foo", bar: "bar") { "baz" }).to eq(%w[foo bar baz]) + end + end + end +end