Skip to content

Instantly share code, notes, and snippets.

@westonganger
Last active May 19, 2023 21:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save westonganger/aee11a627c266a1281462cf08f2ea3c5 to your computer and use it in GitHub Desktop.
Save westonganger/aee11a627c266a1281462cf08f2ea3c5 to your computer and use it in GitHub Desktop.
Ruby Result objects (Success / Failure)
class Result
def initialize(h={}, **attrs)
if !h.is_a?(Hash)
raise ArgumentError.new("Non-hash argument provided")
end
@attrs = h.presence || attrs
@attrs.each do |k,v|
define_singleton_method(k) do
return v
end
end
self
end
def success?
raise NoMethodError.new("Method not defined on subclass")
end
def to_h
Marshal.restore(Marshal.dump(@attrs))
end
alias_method :to_hash, :to_h
end
class Success < SafeResult
def success?
true
end
end
class Failure < SafeResult
def success?
false
end
end
require "spec_helper"
RSpec.describe Result do
it "can only call defined attributes/methods" do
result = Result.new(foo: "foo-val", bar: "bar-val")
expect(result.foo).to eq("foo-val")
expect(result.bar).to eq("bar-val")
expect { result.baz }.to raise_error(NoMethodError)
end
it "raises error is success? is not defined" do
result = Result.new
expect { result.success? }.to raise_error(NoMethodError)
end
it "defines methods only to the singleton" do
result_a = Result.new(foo: "foo-val")
result_b = Result.new(bar: "bar-val")
expect(result_a.respond_to?(:foo)).to eq(true)
expect(result_a.respond_to?(:bar)).to eq(false)
expect(result_b.respond_to?(:foo)).to eq(false)
expect(result_b.respond_to?(:bar)).to eq(true)
end
it "assigns values only to the singleton" do
result_a = Result.new(foo: "foo1")
result_b = Result.new(foo: "foo2")
expect(result_a.foo).to eq("foo1")
expect(result_b.foo).to eq("foo2")
end
it "allow attributes with matching string/symbol names and only returns value for the last defined name" do
result = Result.new(:foo => "sym-foo", "foo" => "str-foo")
expect(result.foo).to eq("str-foo")
result = Result.new("foo" => "str-foo", :foo => "sym-foo")
expect(result.foo).to eq("sym-foo")
end
context "initialize" do
it "allows named arguments" do
result = Result.new(:foo => "sym-foo", "foo" => "str-foo")
expect(result.foo).to eq("str-foo")
end
it "allows hash as positional param" do
result = Result.new({:foo => "sym-foo", "foo" => "str-foo"})
expect(result.foo).to eq("str-foo")
end
it "errors on non-hash as positional param" do
expect { Result.new("str", foo: :bar) }.to raise_error(ArgumentError)
expect { Result.new([], foo: :bar) }.to raise_error(ArgumentError)
end
end
context "to_h" do
it "with no attributes" do
result = described_class.new
expect(result.to_h).to eq({})
end
it "with attributes" do
result = described_class.new(foo: "foo-val", bar: "bar-val")
expect(result.to_h).to eq({:foo => "foo-val", :bar => "bar-val"})
end
it "returns both string and symbol keys" do
result = described_class.new({"foo" => "foo-val", :bar => "bar-val"})
expect(result.to_h).to eq({"foo" => "foo-val", :bar => "bar-val"})
end
it "cannot modify the input hash via the output hash" do
input_h = {foo: "foo", bar: "bar"}
result = described_class.new(input_h)
h = result.to_h
h.slice!(:bar)
expect(input_h).to eq({:foo => "foo", :bar => "bar"})
end
it "cannot modify the result object via the output hash" do
result = described_class.new(foo: "foo", bar: "bar")
h = result.to_h
h.slice!(:bar)
expect(result.foo).to eq("foo")
expect(result.bar).to eq("bar")
result = described_class.new(foo: "foo", errors: {:bar => "bar", :baz => "baz"})
h = result.to_h
h[:errors].slice!(:baz)
expect(result.errors).to eq({:bar => "bar", :baz => "baz"})
end
end
end
RSpec.describe Success do
it "inherits from Result" do
expect(described_class < Result).to eq(true)
end
context "success?" do
it "returns true" do
result = described_class.new
expect(result.success?).to eq(true)
end
end
end
RSpec.describe Failure do
it "inherits from Result" do
expect(described_class < Result).to eq(true)
end
context "success?" do
it "returns false" do
result = described_class.new
expect(result.success?).to eq(false)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment