diff --git a/lib/nenv/environment.rb b/lib/nenv/environment.rb index 782e393..63c3399 100644 --- a/lib/nenv/environment.rb +++ b/lib/nenv/environment.rb @@ -55,17 +55,21 @@ def _create_env_accessor(klass, meth, &block) def _create_env_writer(klass, meth, &block) env_name = nil + dumper = nil klass.send(:define_method, meth) do |raw_value| env_name ||= _namespaced_sanitize(meth) - ENV[env_name] = Dumper.new.dump(raw_value, &block) + dumper ||= Dumper.setup(&block) + ENV[env_name] = dumper.(raw_value) end end def _create_env_reader(klass, meth, &block) env_name = nil + loader = nil klass.send(:define_method, meth) do env_name ||= _namespaced_sanitize(meth) - Loader.new(meth).load(ENV[env_name], &block) + loader ||= Loader.setup(meth, &block) + loader.(ENV[env_name]) end end diff --git a/lib/nenv/environment/dumper.rb b/lib/nenv/environment/dumper.rb index a702259..c83b11f 100644 --- a/lib/nenv/environment/dumper.rb +++ b/lib/nenv/environment/dumper.rb @@ -1,9 +1,14 @@ module Nenv class Environment - class Dumper - def dump(raw_value, &callback) - return callback.call(raw_value) if callback - raw_value.nil? ? nil : raw_value.to_s + module Dumper + require 'nenv/environment/dumper/default' + + def self.setup(&callback) + if callback + callback + else + Default + end end end end diff --git a/lib/nenv/environment/dumper/default.rb b/lib/nenv/environment/dumper/default.rb new file mode 100644 index 0000000..368b7bc --- /dev/null +++ b/lib/nenv/environment/dumper/default.rb @@ -0,0 +1,9 @@ +module Nenv + class Environment + module Dumper::Default + def self.call(raw_value) + raw_value.nil? ? nil : raw_value.to_s + end + end + end +end diff --git a/lib/nenv/environment/loader.rb b/lib/nenv/environment/loader.rb index fb8dc29..9d688e7 100644 --- a/lib/nenv/environment/loader.rb +++ b/lib/nenv/environment/loader.rb @@ -1,27 +1,18 @@ module Nenv class Environment - class Loader - def initialize(meth) - @bool = meth.to_s.end_with?('?') - end - - def load(raw_value, &callback) - return callback.call(raw_value) if callback - @bool ? _to_bool(raw_value) : raw_value - end - - private + module Loader + require 'nenv/environment/loader/predicate' + require 'nenv/environment/loader/default' - def _to_bool(raw_value) - case raw_value - when nil - nil - when '' - fail ArgumentError, "Can't convert empty string into Bool" - when '0', 'false', 'n', 'no', 'NO', 'FALSE' - false + def self.setup(meth, &callback) + if callback + callback else - true + if meth.to_s.end_with? '?' + Predicate + else + Default + end end end end diff --git a/lib/nenv/environment/loader/default.rb b/lib/nenv/environment/loader/default.rb new file mode 100644 index 0000000..9325f56 --- /dev/null +++ b/lib/nenv/environment/loader/default.rb @@ -0,0 +1,9 @@ +module Nenv + class Environment + module Loader::Default + def self.call(raw_value) + raw_value + end + end + end +end diff --git a/lib/nenv/environment/loader/predicate.rb b/lib/nenv/environment/loader/predicate.rb new file mode 100644 index 0000000..28b5531 --- /dev/null +++ b/lib/nenv/environment/loader/predicate.rb @@ -0,0 +1,18 @@ +module Nenv + class Environment + module Loader::Predicate + def self.call(raw_value) + case raw_value + when nil + nil + when '' + fail ArgumentError, "Can't convert empty string into Bool" + when '0', 'false', 'n', 'no', 'NO', 'FALSE' + false + else + true + end + end + end + end +end diff --git a/spec/lib/nenv/environment/dumper_spec.rb b/spec/lib/nenv/environment/dumper_spec.rb index 6500ce4..4de927f 100644 --- a/spec/lib/nenv/environment/dumper_spec.rb +++ b/spec/lib/nenv/environment/dumper_spec.rb @@ -3,7 +3,7 @@ require 'nenv/environment/dumper' RSpec.describe Nenv::Environment::Dumper do - subject { described_class.new.dump(value) } + subject { described_class.setup.(value) } context "with \"abc\"" do let(:value) { 'abc' } @@ -22,7 +22,7 @@ context 'with a block' do subject do - described_class.new.dump(value) { |data| YAML.dump(data) } + described_class.setup { |data| YAML.dump(data) }.(value) end context 'with a yaml string' do diff --git a/spec/lib/nenv/environment/loader_spec.rb b/spec/lib/nenv/environment/loader_spec.rb index 0bdfaa7..7583da8 100644 --- a/spec/lib/nenv/environment/loader_spec.rb +++ b/spec/lib/nenv/environment/loader_spec.rb @@ -3,7 +3,7 @@ RSpec.describe Nenv::Environment::Loader do context 'with no block' do - subject { described_class.new(meth).load(value) } + subject { described_class.setup(meth).(value) } context 'with a normal method' do let(:meth) { :foo } @@ -49,7 +49,7 @@ context 'with a block' do subject do - described_class.new(:foo).load(value) { |data| YAML.load(data) } + described_class.setup(:foo) { |data| YAML.load(data) }.(value) end context 'with a yaml string' do let(:value) { "--- foo\n...\n" } diff --git a/spec/lib/nenv/environment_spec.rb b/spec/lib/nenv/environment_spec.rb index 2835055..75f17b1 100644 --- a/spec/lib/nenv/environment_spec.rb +++ b/spec/lib/nenv/environment_spec.rb @@ -3,47 +3,62 @@ require 'nenv/environment' RSpec.describe Nenv::Environment do - let(:env) { instance_double(Hash) } # a hash is close enough - before(:each) { stub_const('ENV', env) } + class MockEnv < Hash # a hash is close enough + def []=(k, v) + fail TypeError, "no implicit conversion of #{k.class} into String" unless k.respond_to? :to_str + fail TypeError, "no implicit conversion of #{v.class} into String" unless v.respond_to? :to_str + super(k.to_str, v.to_str) + end + end - context 'without integration' do - let(:dumper) { instance_double(described_class::Dumper) } - let(:loader) { instance_double(described_class::Loader) } + before { stub_const('ENV', MockEnv.new) } + subject { instance } - before do - allow(described_class::Dumper).to receive(:new).and_return(dumper) - allow(described_class::Loader).to receive(:new).and_return(loader) - end + shared_examples 'accessor methods' do + describe 'predicate method' do + before { subject.create_method(:foo?) } - context 'with no namespace' do - let(:instance) { described_class.new } + it 'responds to it' do + expect(subject).to respond_to(:foo?) + end - context 'with an existing method' do - before do - subject.create_method(:foo?) + context 'when the method already exists' do + let(:error) { described_class::AlreadyExistsError } + let(:message) { 'Method :foo? already exists' } + specify do + expect do + subject.create_method(:foo?) + end.to raise_error(error, message) end + end - it 'uses the name as full key' do - expect(ENV).to receive(:[]).with('FOO').and_return('true') - expect(loader).to receive(:load).with('true').and_return(true) - expect(subject.foo?).to eq(true) + context 'with value stored in ENV' do + before { ENV[sample_key] = value } + + describe 'when value is truthy' do + let(:value) { 'true' } + it 'should return true' do + expect(subject.foo?).to eq true + end + end + + describe 'when value is falsey' do + let(:value) { '0' } + it 'should return false' do + expect(subject.foo?).to eq false + end end end end - context 'with any namespace' do - let(:namespace) { 'bar' } - let(:instance) { described_class.new(namespace) } - - describe 'creating a method' do - subject { instance } + describe 'reader method' do + context 'when added' do + before { subject.create_method(:foo) } - before do - subject.create_method(:foo) + it 'responds to it' do + expect(subject).to respond_to(:foo) end - it { is_expected.to respond_to(:foo) } - context 'when the method already exists' do let(:error) { described_class::AlreadyExistsError } let(:message) { 'Method :foo already exists' } @@ -55,165 +70,90 @@ end end - describe 'calling' do - subject { instance } - - context 'when method does not exist' do - let(:error) { NoMethodError } - let(:message) { /undefined method `foo' for/ } - it { expect { subject.foo }.to raise_error(error, message) } - end - - context 'with a reader method' do - context 'with no block' do - before { instance.create_method(meth) } - - context 'with a normal method' do - let(:meth) { :foo } - before do - allow(loader).to receive(:load).with('123').and_return(123) - end - - it 'returns unmarshalled stored value' do - expect(ENV).to receive(:[]).with('BAR_FOO').and_return('123') - expect(subject.foo).to eq 123 - end - end - - context 'with a bool method' do - let(:meth) { :foo? } - - it 'references the proper ENV variable' do - allow(loader).to receive(:load).with('false').and_return(false) - expect(ENV).to receive(:[]).with('BAR_FOO').and_return('false') - expect(subject.foo?).to eq false - end - end - end - - context 'with a block' do - before do - instance.create_method(:foo) { |data| YAML.load(data) } - end + context 'with value stored in ENV' do + before { ENV[sample_key] = value } - let(:value) { "---\n:foo: 5\n" } - - it 'unmarshals using the block' do - allow(ENV).to receive(:[]).with('BAR_FOO') - .and_return(value) - - allow(loader).to receive(:load).with(value) do |arg, &block| - expect(block).to be - block.call(arg) - end + context 'with no block' do + before { instance.create_method(:foo) } + let(:value) { '123' } - expect(subject.foo).to eq(foo: 5) - end + it 'returns marshalled stored value' do + expect(subject.foo).to eq '123' end end - context 'with a writer method' do - before { instance.create_method(:foo=) } + context 'with block' do + before { instance.create_method(:foo) { |data| YAML.load(data) } } + let(:value) { "---\n:foo: 5\n" } - it 'set the environment variable' do - expect(ENV).to receive(:[]=).with('BAR_FOO', '123') - allow(dumper).to receive(:dump).with(123).and_return('123') - subject.foo = 123 - end - - it 'marshals and stores the value' do - expect(ENV).to receive(:[]=).with('BAR_FOO', '123') - allow(dumper).to receive(:dump).with(123).and_return('123') - subject.foo = 123 - end - end - - context 'with a method containing underscores' do - before { instance.create_method(:foo_baz) } - it 'reads the correct variable' do - expect(ENV).to receive(:[]).with('BAR_FOO_BAZ').and_return('123') - allow(loader).to receive(:load).with('123').and_return(123) - subject.foo_baz + it 'returns unmarshalled stored value' do + expect(subject.foo).to eq(foo: 5) end end + end + end - context 'with a block' do - before do - instance.create_method(:foo=) { |data| YAML.dump(data) } - end - - let(:result) { "---\n:foo: 5\n" } - - it 'marshals using the block' do - allow(ENV).to receive(:[]=).with('BAR_FOO', result) - - allow(dumper).to receive(:dump).with(foo: 5) do |arg, &block| - expect(block).to be - block.call(arg) - end + describe 'writer method' do + context 'when added' do + before { subject.create_method(:foo=) } - subject.foo = { foo: 5 } - end + it 'responds to it' do + expect(subject).to respond_to(:foo=) end - context 'with an unsanitized name' do - pending + context 'when the method already exists' do + let(:error) { described_class::AlreadyExistsError } + let(:message) { 'Method :foo= already exists' } + specify do + expect do + subject.create_method(:foo=) + end.to raise_error(error, message) + end end end - end - end - describe 'with integration' do - context 'with any namespace' do - let(:namespace) { 'baz' } - let(:instance) { described_class.new(namespace) } - subject { instance } + describe 'env variable' do + after { expect(ENV[sample_key]).to eq result } - context 'with a reader method' do context 'with no block' do - before { instance.create_method(:foo) } + before { subject.create_method(:foo=) } + let(:result) { '123' } - it 'returns the stored value' do - allow(ENV).to receive(:[]).with('BAZ_FOO').and_return('123') - expect(subject.foo).to eq '123' + it 'stores a converted to string value' do + subject.foo = 123 end end - context 'with a block' do - before do - instance.create_method(:foo) { |data| YAML.load(data) } - end - - it 'unmarshals the value' do - expect(ENV).to receive(:[]).with('BAZ_FOO') - .and_return("---\n:foo: 5\n") + context 'with block' do + before { subject.create_method(:foo=) { |data| YAML.dump(data) } } + let(:result) { "---\n:foo: 5\n" } - expect(subject.foo).to eq(foo: 5) + it 'stores a marshaled value' do + subject.foo = { foo: 5 } end end end + end + end - context 'with a writer method' do - context 'with no block' do - before { instance.create_method(:foo=) } - - it 'marshals and stores the value' do - expect(ENV).to receive(:[]=).with('BAZ_FOO', '123') - subject.foo = 123 - end - end + context 'with no namespace' do + let(:instance) { described_class.new } + let(:sample_key) { 'FOO' } + include_examples 'accessor methods' + end - context 'with a block' do - before do - instance.create_method(:foo=) { |data| YAML.dump(data) } - end + context 'with any namespace' do + let(:namespace) { 'bar' } + let(:sample_key) { 'BAR_FOO' } + let(:instance) { described_class.new(namespace) } + include_examples 'accessor methods' - it 'nmarshals the value' do - expect(ENV).to receive(:[]=).with('BAZ_FOO', "---\n:foo: 5\n") + context 'with a method containing underscores' do + before { instance.create_method(:foo_baz) } - subject.foo = { foo: 5 } - end - end + it 'reads the correct variable' do + ENV['BAR_FOO_BAZ'] = '123' + expect(subject.foo_baz).to eq '123' end end end