Class: SmartHash

Inherits:
Hash
  • Object
show all
Defined in:
lib/smart_hash.rb,
lib/smart_hash/loose.rb

Overview

A smarter alternative to OpenStruct

Major features:

  • You can access attributes as methods or keys.

  • Attribute access is strict by default.

  • You can use any attribute names.

  • Descends from `Hash` and inherits its rich feature set.

Basic Usage

Create an object and set a few attributes:

>> person = SmartHash[]
>> person.name = "John"
>> person.age = 25

>> person
=> {:name=>"John", :age=>25}

Read attributes:

>> person.name
=> "John"
>> person[:name]
=> "John"

Access an unset attribute:

>> person.invalid_stuff
KeyError: key not found: :invalid_stuff
>> person[:invalid_stuff]
=> nil

Please note that `[]` access is always non-strict since `SmartHash` behaves as `Hash` here.

Manipulate attributes which exist as methods:

>> person = SmartHash[:name => "John"]
>> person.size
=> 1
>> person.size = "XL"
>> person.size
=> "XL"

*IMPORTANT:* You can use any attribute names excluding these: `default`, `default_proc`, `strict`.

Use `Hash` features, e.g. merge:

>> person = SmartHash[:name => "John"]
>> person.merge(:surname => "Smith", :age => 25)
=> {:name=>"John", :surname=>"Smith", :age=>25}

, or iterate:

>> person.each {|k, v| puts "#{k}: #{v}"}
name: John
surname: Smith
age: 25

Direct Known Subclasses

Loose

Defined Under Namespace

Classes: Loose

Constant Summary

ATTR_REGEXP =

Attribute name regexp without delimiters.

/[a-zA-Z_]\w*/
FORBIDDEN_ATTRS =

Attribute names that are forbidden. Forbidden attrs cannot be manupulated as such and are handled as methods only.

[:default, :default_proc, :strict]
VERSION =

Gem version.

"0.1.0"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*args) ⇒ SmartHash



86
87
88
89
# File 'lib/smart_hash.rb', line 86

def initialize(*args)
  super
  _smart_hash_init
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_name, *args) ⇒ Object (private)



206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/smart_hash.rb', line 206

def method_missing(method_name, *args)
  # NOTE: No need to check for forbidden attrs here, since they exist as methods by definition.

  case method_name
  when /\A(.+)=\z/
    # Case "r.attr=". Attribute assignment. Method name is pre-validated for us by Ruby.
    attr = $1.to_sym
    raise ArgumentError, "Attribute '#{attr}' is protected" if @protected_attrs.include? attr

    self[attr] = args[0]
  when /\A#{ATTR_REGEXP}\z/
    # Case "r.attr".
    if @strict
      _smart_hash_fetch(method_name)
    else
      self[method_name]
    end
  else
    super
  end
end

Instance Attribute Details

#declared_attrsObject (readonly)

See #declare.



78
79
80
# File 'lib/smart_hash.rb', line 78

def declared_attrs
  @declared_attrs
end

#protected_attrsObject (readonly)

See #protect.



81
82
83
# File 'lib/smart_hash.rb', line 81

def protected_attrs
  @protected_attrs
end

#strictObject

Strict mode. Default is true.



84
85
86
# File 'lib/smart_hash.rb', line 84

def strict
  @strict
end

Class Method Details

.[](*args) ⇒ Object

Alternative constructor.

h = SmartHash[]


94
95
96
97
98
99
100
# File 'lib/smart_hash.rb', line 94

def self.[](*args)
  super.tap do |_|
    _.instance_eval do
      _smart_hash_init
    end
  end
end

Instance Method Details

#declare(*attrs) ⇒ Object

Declare that specific key(s) are going to be used as attributes. Thus, exception will be raised when trying to access declared attributes if they are not set, even if the corresponding method exists in Hash class.

r.declare(:size)
r.size            # `KeyError` or `IndexError` will be raised.

r.declare(:count, :size)    # Declare more than one attribute at once.

Raises:

  • (ArgumentError)


110
111
112
113
114
115
116
117
# File 'lib/smart_hash.rb', line 110

def declare(*attrs)
  raise ArgumentError, "No attrs specified" if attrs.empty?
  attrs.each do |attr|
    (v = attr).is_a?(klass = Symbol) or raise ArgumentError, "#{klass} expected, #{v.class} (#{v.inspect}) given"
    attr.to_s.match /\A#{ATTR_REGEXP}\z/ or raise ArgumentError, "Incorrect attribute name '#{attr}'"
    @declared_attrs << attr   # `Set` is returned.
  end
end

#protect(*attrs) ⇒ Object

Protect attributes against assignment.

r.protect(:size)
r.size = 1    # Exception.

Raises:

  • (ArgumentError)


123
124
125
126
127
128
129
130
# File 'lib/smart_hash.rb', line 123

def protect(*attrs)
  raise ArgumentError, "No attrs specified" if attrs.empty?
  attrs.each do |attr|
    (v = attr).is_a?(klass = Symbol) or raise ArgumentError, "#{klass} expected, #{v.class} (#{v.inspect}) given"
    attr.to_s.match /\A#{ATTR_REGEXP}\z/ or raise ArgumentError, "Incorrect attribute name '#{attr}'"
    @protected_attrs << attr
  end
end

#undeclare(*attrs) ⇒ Object

Raises:

  • (ArgumentError)


132
133
134
135
136
137
# File 'lib/smart_hash.rb', line 132

def undeclare(*attrs)
  raise ArgumentError, "No attrs specified" if attrs.empty?
  attrs.each do |attr|
    @declared_attrs.delete(attr)    # `Set` is returned.
  end
end

#unprotect(*attrs) ⇒ Object

Raises:

  • (ArgumentError)


139
140
141
142
143
144
# File 'lib/smart_hash.rb', line 139

def unprotect(*attrs)
  raise ArgumentError, "No attrs specified" if attrs.empty?
  attrs.each do |attr|
    @protected_attrs.delete(attr)
  end
end