Last active
August 29, 2015 14:18
-
-
Save jamesmartin/cbdb799a6b3c15cafb8b to your computer and use it in GitHub Desktop.
Define a struct-like object, with attributes that can be called normally, or yielded when they exist.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class SafeStruct | |
| def self.with_attributes(params) | |
| params.keys.map do |key| | |
| define_method(key) do |&block| | |
| ivar = instance_variable_get("@#{key}".to_sym) | |
| if block && !ivar.nil? | |
| block.call(ivar) | |
| end | |
| ivar | |
| end | |
| end | |
| new(params) | |
| end | |
| def initialize(params) | |
| params.each do |key, value| | |
| instance_variable_set("@#{key}".to_sym, value) | |
| end | |
| end | |
| def method_missing(symbol, *args, &block) | |
| /without_(.*)/.match(symbol.to_s) do |matches| | |
| ivar = matches[1] | |
| yield if instance_variable_get("@#{ivar}".to_sym).nil? | |
| return | |
| end | |
| /with_(.*)/.match(symbol.to_s) do |matches| | |
| ivar = instance_variable_get("@#{matches[1]}".to_sym) | |
| yield ivar unless ivar.nil? | |
| return | |
| end | |
| super | |
| end | |
| def respond_to_missing?(method_name, include_private = false) | |
| method_name.to_s.start_with?('with_') || method_name.to_s.start_with?('without_') || super | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| require_relative 'safe_struct' | |
| describe 'yieldable attributes' do | |
| it 'yields an attribute value when the attribute is not nil' do | |
| t = SafeStruct.with_attributes(name: 'some value') | |
| expect { |b| t.name(&b) }.to yield_with_args('some value') | |
| end | |
| it 'does not yield when the attribute is not defined' do | |
| t = SafeStruct.with_attributes(irrelevant: 'some value') | |
| expect { |b| t.name(&b) }.not_to yield_control | |
| end | |
| it 'returns the value when a block is not given' do | |
| t = SafeStruct.with_attributes(name: 'some value') | |
| expect(t.name).to eq 'some value' | |
| end | |
| it 'returns nil when the attribute is defined but the value is nil' do | |
| t = SafeStruct.with_attributes(name: nil) | |
| expect(t.name).to eq nil | |
| end | |
| context "without attributes" do | |
| it 'yields to the block when an attribute is not defined' do | |
| t = SafeStruct.with_attributes(irrelevant: 'irrelevant') | |
| expect { |b| t.without_name(&b) }.to yield_control | |
| end | |
| it 'does not yield when the attribute is defined and has a value' do | |
| t = SafeStruct.with_attributes(name: 'some value') | |
| expect { |b| t.without_name(&b) }.not_to yield_control | |
| end | |
| it 'responds to without_"attribute_name" when the attribute is defined' do | |
| t = SafeStruct.with_attributes(name: 'some value') | |
| expect(t).to respond_to(:without_name) | |
| end | |
| end | |
| context "with attributes" do | |
| it 'yields to the block when an attribute is defined' do | |
| t = SafeStruct.with_attributes(name: 'some value') | |
| expect { |b| t.with_name(&b) }.to yield_with_args('some value') | |
| end | |
| it 'does not yield when the value is not defined' do | |
| t = SafeStruct.with_attributes(irrelevant: 'irrelevant') | |
| expect { |b| t.with_name(&b) }.not_to yield_control | |
| end | |
| it 'responds to with_"attribute_name" when the attribute is defined' do | |
| t = SafeStruct.with_attributes(name: 'some value') | |
| expect(t).to respond_to(:with_name) | |
| end | |
| end | |
| end | |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
And now, with and without:
Almost looks like we're declaring behaviours based on types of person (with and without email addresses).