Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #47 from lotus/attributes
Utils::Attributes
- Loading branch information
Showing
4 changed files
with
269 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
require 'lotus/utils/hash' | ||
|
||
module Lotus | ||
module Utils | ||
# A set of attributes. | ||
# | ||
# It internally stores the data as a Hash. | ||
# | ||
# All the operations convert keys to strings. | ||
# This strategy avoids memory attacks due to Symbol abuses when parsing | ||
# untrusted input. | ||
# | ||
# At the same time, this allows to get/set data with the original key or | ||
# with the string representation. See the examples below. | ||
# | ||
# @since x.x.x | ||
class Attributes | ||
# Initialize a set of attributes | ||
# All the keys of the given Hash are recursively converted to strings. | ||
# | ||
# @param hash [#to_h] a Hash or any object that implements #to_h | ||
# | ||
# @return [Lotus::Utils::Attributes] self | ||
# | ||
# @since x.x.x | ||
# | ||
# @example | ||
# require 'lotus/utils/attributes' | ||
# | ||
# attributes = Lotus::Utils::Attributes.new(a: 1, b: { 2 => [3, 4] } }) | ||
# attributes.to_h # => { "a" => 1, "b" => { "2" => [3, 4] } } | ||
def initialize(hash = {}) | ||
@attributes = Utils::Hash.new(hash, &nil).stringify! | ||
end | ||
|
||
# Get the value associated with the given attribute | ||
# | ||
# @param attribute [#to_s] a String or any object that implements #to_s | ||
# | ||
# @return [Object,NilClass] the associated value, if present | ||
# | ||
# @since x.x.x | ||
# | ||
# @example | ||
# require 'lotus/utils/attributes' | ||
# | ||
# attributes = Lotus::Utils::Attributes.new(a: 1, 'b' => 2, 23 => 'foo') | ||
# | ||
# attributes.get(:a) # => 1 | ||
# attributes.get('a') # => 1 | ||
# | ||
# attributes.get(:b) # => 2 | ||
# attributes.get('b') # => 2 | ||
# | ||
# attributes.get(23) # => "foo" | ||
# attributes.get('23') # => "foo" | ||
# | ||
# attributes.get(:unknown) # => nil | ||
# attributes.get('unknown') # => nil | ||
def get(attribute) | ||
@attributes[attribute.to_s] | ||
end | ||
|
||
# Set the given value for the given attribute | ||
# | ||
# @param attribute [#to_s] a String or any object that implements #to_s | ||
# @param value [Object] any value | ||
# | ||
# @return [NilClass] | ||
# | ||
# @since x.x.x | ||
# | ||
# @example | ||
# require 'lotus/utils/attributes' | ||
# | ||
# attributes = Lotus::Utils::Attributes.new | ||
# | ||
# attributes.set(:a, 1) | ||
# attributes.get(:a) # => 1 | ||
# attributes.get('a') # => 1 | ||
# | ||
# attributes.set('b', 2) | ||
# attributes.get(:b) # => 2 | ||
# attributes.get('b') # => 2 | ||
# | ||
# attributes.set(23, 'foo') | ||
# attributes.get(23) # => "foo" | ||
# attributes.get('23') # => "foo" | ||
def set(attribute, value) | ||
@attributes[attribute.to_s] = value | ||
nil | ||
end | ||
|
||
# Returns a deep duplicated copy of the attributes as a Hash | ||
# | ||
# @return [Lotus::Utils::Hash] | ||
# | ||
# @since x.x.x | ||
# | ||
# @see Lotus::Utils::Hash | ||
def to_h | ||
@attributes.deep_dup | ||
end | ||
end | ||
end | ||
end |
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
require 'test_helper' | ||
require 'bigdecimal' | ||
require 'lotus/utils/attributes' | ||
|
||
describe Lotus::Utils::Attributes do | ||
describe '#initialize' do | ||
before do | ||
class AttributesSet | ||
def to_h | ||
{a: 1} | ||
end | ||
end | ||
end | ||
|
||
after do | ||
Object.__send__(:remove_const, :AttributesSet) | ||
end | ||
|
||
it 'accepts an object that implements #to_h' do | ||
attributes = Lotus::Utils::Attributes.new(AttributesSet.new) | ||
attributes.to_h.must_equal({'a' => 1}) | ||
end | ||
|
||
it "ignores hash default" do | ||
attributes = Lotus::Utils::Attributes.new{|h,k| h[k] = [] } | ||
attributes.get('uknown').must_be_nil | ||
end | ||
|
||
it 'recursively stringify keys' do | ||
attributes = Lotus::Utils::Attributes.new({a: 1, b: { 2 => [3, 4] }}) | ||
attributes.to_h.must_equal({'a'=>1, 'b'=>{'2'=>[3,4]}}) | ||
end | ||
end | ||
|
||
describe '#get' do | ||
it 'returns value associated to the given key (string)' do | ||
attributes = Lotus::Utils::Attributes.new('foo' => 'bar') | ||
attributes.get('foo').must_equal 'bar' | ||
attributes.get(:foo).must_equal 'bar' | ||
end | ||
|
||
it 'returns value associated to the given key (symbol)' do | ||
attributes = Lotus::Utils::Attributes.new(foo: 'bar') | ||
attributes.get(:foo).must_equal 'bar' | ||
attributes.get('foo').must_equal 'bar' | ||
end | ||
|
||
it 'returns value associated to the given key (number)' do | ||
attributes = Lotus::Utils::Attributes.new( 23 => 'foo') | ||
attributes.get(23).must_equal 'foo' | ||
attributes.get('23').must_equal 'foo' | ||
end | ||
|
||
it 'correctly handles Ruby falsey' do | ||
attributes = Lotus::Utils::Attributes.new('foo' => false) | ||
attributes.get(:foo).must_equal false | ||
attributes.get('foo').must_equal false | ||
|
||
attributes = Lotus::Utils::Attributes.new(foo: false) | ||
attributes.get(:foo).must_equal false | ||
end | ||
|
||
it 'ignores hash default' do | ||
attributes = Lotus::Utils::Attributes.new{|h,k| h[k] = [] } | ||
attributes.get('foo').must_be_nil | ||
attributes.get(:foo).must_be_nil | ||
end | ||
|
||
it 'overrides clashing keys' do | ||
attributes = Lotus::Utils::Attributes.new('foo' => 'bar', foo: 'baz') | ||
attributes.get('foo').must_equal 'baz' | ||
attributes.get(:foo).must_equal 'baz' | ||
end | ||
end | ||
|
||
describe '#set' do | ||
it 'is a void operation' do | ||
Lotus::Utils::Attributes.new.set('foo', 11).must_be_nil | ||
end | ||
|
||
it 'sets a value (string)' do | ||
attributes = Lotus::Utils::Attributes.new | ||
attributes.set('foo', 'bar') | ||
|
||
attributes.get('foo').must_equal 'bar' | ||
attributes.get(:foo).must_equal 'bar' | ||
end | ||
|
||
it 'sets a value (symbol)' do | ||
attributes = Lotus::Utils::Attributes.new | ||
attributes.set(:foo, 'bar') | ||
|
||
attributes.get('foo').must_equal 'bar' | ||
attributes.get(:foo).must_equal 'bar' | ||
end | ||
|
||
it 'sets a value (number)' do | ||
attributes = Lotus::Utils::Attributes.new | ||
attributes.set(23, 'bar') | ||
|
||
attributes.get(23).must_equal 'bar' | ||
attributes.get('23').must_equal 'bar' | ||
end | ||
end | ||
|
||
describe '#to_h' do | ||
it 'returns a ::Hash' do | ||
attributes = Lotus::Utils::Attributes.new(foo: 'bar') | ||
attributes.to_h.must_equal({'foo' => 'bar'}) | ||
end | ||
|
||
it 'prevents information escape' do | ||
actual = Lotus::Utils::Attributes.new({'a' => 1}) | ||
hash = actual.to_h | ||
hash.merge!('b' => 2) | ||
|
||
actual.get('b').must_be_nil | ||
end | ||
end | ||
end |
This file contains 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