File tree Expand file tree Collapse file tree 5 files changed +210
-0
lines changed
Expand file tree Collapse file tree 5 files changed +210
-0
lines changed Original file line number Diff line number Diff line change @@ -52,6 +52,16 @@ def initialize(model = nil)
5252 Brightbox . config . cache_id ( @id ) if Brightbox . config . respond_to? ( :cache_id )
5353 end
5454
55+ def attributes
56+ fog_attributes
57+ end
58+
59+ # Returns the transformed attributes from the fog model with a
60+ # wrapper to allow access using either String or Symbol keys.
61+ def fog_attributes
62+ IndifferentAccessHash . new ( deep_symbolize ( fog_model . attributes ) )
63+ end
64+
5565 def fog_model
5666 @fog_model ||= self . class . find ( @id )
5767 end
@@ -192,5 +202,24 @@ def created_on
192202
193203 fog_model . created_at . strftime ( "%Y-%m-%d" )
194204 end
205+
206+ private
207+
208+ # Recursively converts all keys in a hash to symbols to ensure
209+ # consistent access.
210+ def deep_symbolize ( hash )
211+ return hash unless hash . respond_to? ( :each_with_object )
212+
213+ hash . each_with_object ( { } ) do |( k , v ) , result |
214+ result [ k . to_sym ] = case v
215+ when Hash
216+ deep_symbolize ( v )
217+ when Array
218+ v . map { |i | i . is_a? ( Hash ) ? deep_symbolize ( i ) : i }
219+ else
220+ v
221+ end
222+ end
223+ end
195224 end
196225end
Original file line number Diff line number Diff line change 1+ # A simpler wrapper to allows either String or Symbol keys to be used
2+ # when accessing attributes since fog applies a change on the top
3+ # level resulting in a mix of both which has introduced issues.
4+ class IndifferentAccessHash
5+ def initialize ( hash )
6+ @hash = hash
7+ end
8+
9+ # @param key [String, Symbol] the key to look up
10+ # @return [Object] the value of the key
11+ def []( key )
12+ value = @hash [ key . to_s ] || @hash [ key . to_sym ]
13+ wrap ( value )
14+ end
15+
16+ # @param key [String, Symbol] the key to set
17+ # @param value [Object] the value to set
18+ # @return [Object] the value of the key
19+ def []=( key , value )
20+ @hash [ key . to_s ] = value
21+ end
22+
23+ # @param other [Object] the object to compare
24+ # @return [Object] the result of the comparison
25+ def ==( other )
26+ @hash == ( other . is_a? ( IndifferentAccessHash ) ? other . to_h : other )
27+ end
28+
29+ def method_missing ( method , *args , &block )
30+ @hash . send ( method , *args , &block )
31+ end
32+
33+ def to_h
34+ @hash
35+ end
36+
37+ private
38+
39+ # This is to handle nested hashes to avoid the original issue again
40+ def wrap ( value )
41+ case value
42+ when Hash
43+ IndifferentAccessHash . new ( value )
44+ when Array
45+ value . map { |v | wrap ( v ) }
46+ else
47+ value
48+ end
49+ end
50+ end
Original file line number Diff line number Diff line change @@ -71,6 +71,7 @@ module Config
7171require_relative "brightbox-cli/connection_manager"
7272require_relative "brightbox-cli/tables"
7373require_relative "brightbox-cli/logging"
74+ require_relative "brightbox-cli/indifferent_access_hash"
7475require_relative "brightbox-cli/api"
7576require_relative "brightbox-cli/config/cache"
7677require_relative "brightbox-cli/config/gpg_encrypted_passwords"
Original file line number Diff line number Diff line change 1+ require "spec_helper"
2+
3+ RSpec . describe Brightbox ::Api , "#attributes" do
4+ subject ( :model_attributes ) { api_model . attributes }
5+
6+ let ( :api_model ) { Brightbox ::Api . new ( fog_model ) }
7+ let ( :fog_model ) do
8+ double (
9+ "Fog::Compute::Brightbox::Api" ,
10+ id : "res-12345" ,
11+ attributes : attributes
12+ )
13+ end
14+
15+ context "when attributes are not set" do
16+ let ( :attributes ) { nil }
17+
18+ it "returns an indifferent access hash" do
19+ expect ( model_attributes ) . to be_instance_of ( IndifferentAccessHash )
20+ end
21+ end
22+
23+ context "when attributes are set with a mix of string and symbols" do
24+ let ( :attributes ) do
25+ {
26+ id : "res-12345" ,
27+ name : "test" ,
28+ acme : acme_details
29+ }
30+ end
31+ let ( :acme_details ) do
32+ {
33+ "domains" => [
34+ {
35+ "identifier" => "example.test" ,
36+ "status" => "pending"
37+ }
38+ ]
39+ }
40+ end
41+
42+ it "transforms the keys with deep symbolize" do
43+ expect ( model_attributes ) . to eq (
44+ id : "res-12345" ,
45+ name : "test" ,
46+ acme : {
47+ domains : [
48+ {
49+ identifier : "example.test" ,
50+ status : "pending"
51+ }
52+ ]
53+ }
54+ )
55+ end
56+
57+ it "can accessed by string keys" do
58+ expect ( model_attributes [ "acme" ] [ "domains" ] . first [ "identifier" ] ) . to eq ( "example.test" )
59+ end
60+
61+ it "can accessed by symbol keys" do
62+ expect ( model_attributes [ :acme ] [ :domains ] . first [ :identifier ] ) . to eq ( "example.test" )
63+ end
64+ end
65+ end
Original file line number Diff line number Diff line change 1+ require "spec_helper"
2+
3+ RSpec . describe Brightbox ::Api , "#fog_attributes" do
4+ subject ( :model_attributes ) { api_model . fog_attributes }
5+
6+ let ( :api_model ) { Brightbox ::Api . new ( fog_model ) }
7+ let ( :fog_model ) do
8+ double (
9+ "Fog::Compute::Brightbox::Api" ,
10+ id : "res-12345" ,
11+ attributes : attributes
12+ )
13+ end
14+
15+ context "when attributes are not set" do
16+ let ( :attributes ) { nil }
17+
18+ it "returns an indifferent access hash" do
19+ expect ( model_attributes ) . to be_instance_of ( IndifferentAccessHash )
20+ end
21+ end
22+
23+ context "when attributes are set with a mix of string and symbols" do
24+ let ( :attributes ) do
25+ {
26+ id : "res-12345" ,
27+ name : "test" ,
28+ acme : acme_details
29+ }
30+ end
31+ let ( :acme_details ) do
32+ {
33+ "domains" => [
34+ {
35+ "identifier" => "example.test" ,
36+ "status" => "pending"
37+ }
38+ ]
39+ }
40+ end
41+
42+ it "transforms the keys with deep symbolize" do
43+ expect ( model_attributes ) . to eq (
44+ id : "res-12345" ,
45+ name : "test" ,
46+ acme : {
47+ domains : [
48+ {
49+ identifier : "example.test" ,
50+ status : "pending"
51+ }
52+ ]
53+ }
54+ )
55+ end
56+
57+ it "can not accessed by string keys" do
58+ expect ( model_attributes [ "acme" ] [ "domains" ] . first [ "identifier" ] ) . to eq ( "example.test" )
59+ end
60+
61+ it "can accessed by symbol keys" do
62+ expect ( model_attributes [ :acme ] [ :domains ] . first [ :identifier ] ) . to eq ( "example.test" )
63+ end
64+ end
65+ end
You can’t perform that action at this time.
0 commit comments