Class: SmartHash

Inherits:
Hash
  • Object
show all
Defined in:
lib/smart_hash.rb,
lib/smart_hash/loose.rb,
lib/smart_hash/version.rb,
lib/smart_hash/dynamic_methods.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.

See rubydoc documentation for basic usage examples.

Direct Known Subclasses

Loose

Defined Under Namespace

Modules: DynamicMethods Classes: Loose

Constant Summary collapse

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.1"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*args) ⇒ SmartHash

Returns a new instance of SmartHash.



46
47
48
49
# File 'lib/smart_hash.rb', line 46

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)



199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/smart_hash.rb', line 199

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/
    # Assignment. Method name is pre-validated for us by Ruby.
    attr = $1.to_sym
    raise ArgumentError, "Attribute is protected: #{attr}" if @protected_attrs.include? attr
    self[attr] = args[0]
  when /\A(#{ATTR_REGEXP})\z/
    # Access.
    attr = $1.to_sym
    if @strict
      _smart_hash_fetch(attr)
    else
      self[attr]
    end
  else
    super
  end
end

Instance Attribute Details

#declared_attrsObject (readonly)

See #declare.



32
33
34
# File 'lib/smart_hash.rb', line 32

def declared_attrs
  @declared_attrs
end

#protected_attrsObject (readonly)

See #protect.



35
36
37
# File 'lib/smart_hash.rb', line 35

def protected_attrs
  @protected_attrs
end

#strictObject

Strict mode. Default is true.

person = SmartHash[]
person.invalid_stuff    # KeyError: key not found: :invalid_stuff

person.strict = false
person.invalid_stuff    # => nil


44
45
46
# File 'lib/smart_hash.rb', line 44

def strict
  @strict
end

Class Method Details

.[](*args) ⇒ Object

Alternative constructor.

person = SmartHash[]


54
55
56
57
58
59
60
# File 'lib/smart_hash.rb', line 54

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

Instance Method Details

#declare(*attrs) ⇒ Object

Declare attributes. By declaring the attributes you ensure that there’s no interference from existing methods.

person = SmartHash[]
person.declare(:size)
person.size             # KeyError: key not found: :size

person.size = "XL"
person.size             # => "XL"

See also #undeclare.

Raises:

  • (ArgumentError)


73
74
75
76
77
78
79
80
# File 'lib/smart_hash.rb', line 73

def declare(*attrs)
  raise ArgumentError, "No attributes specified" if attrs.empty?
  attrs.each do |attr|
    [attr, Symbol].tap {|v, klass| v.is_a?(klass) 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 from being assigned.

person = SmartHash[]
person.name = "John"
person.protect(:name)

person.name = "Bob"     # ArgumentError: Attribute 'name' is protected

See also #unprotect.

Raises:

  • (ArgumentError)


91
92
93
94
95
96
97
98
# File 'lib/smart_hash.rb', line 91

def protect(*attrs)
  raise ArgumentError, "No attributes specified" if attrs.empty?
  attrs.each do |attr|
    [attr, Symbol].tap {|v, klass| v.is_a?(klass) 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)


100
101
102
103
104
105
# File 'lib/smart_hash.rb', line 100

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

#unprotect(*attrs) ⇒ Object

Raises:

  • (ArgumentError)


107
108
109
110
111
112
# File 'lib/smart_hash.rb', line 107

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