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
Author
Good idea from @mattgay:
What if every attribute had in inverse yieldable?
- person.email do |email|
We will send email to you at: #{email}.
- person.without_email do
We don’t have an email address for you.
Author
And now, with and without:
- person = SafeStruct.with_attributes(name: 'Harry', age: 50)
- person.with_email do |email|
We will send email to you at: #{email}.
- person.without_email do
We don't have an email address for you.Almost looks like we're declaring behaviours based on types of person (with and without email addresses).
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Could be useful in context where you want to be declarative, and not concerned with the notion of 'presence', like a Rails view, for example:
In this example, the email block would not be rendered, because the person object was initialized with an
emailattribute ofnil.The alternative is for the template to check for the presence of an attribute value before rendering:
Which do you prefer? Why?