Module: Hashmake::HashMakeable

Defined in:
lib/hashmake/hash_makeable.rb

Overview

This module should be included for any class that wants to be ‘hash-makeable’, which means that a new object instance expects all its arguments to come in a single Hash. See the hash_make method in this module and the ArgSpec class for more details.

Defined Under Namespace

Modules: ClassMethods

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.included(base) ⇒ Object

Use the included hook to also extend the including class with HashMake class methods



16
17
18
# File 'lib/hashmake/hash_makeable.rb', line 16

def self.included(base)
  base.extend(ClassMethods)
end

Instance Method Details

#find_arg_specsObject

Look in the current class for a constant that is a Hash containing (only) ArgSpec objects. Returns the first constant matching this criteria, or nil if none was found.



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/hashmake/hash_makeable.rb', line 111

def find_arg_specs
  self.class.constants.each do |constant|
    val = self.class.const_get(constant)
    if val.is_a? Hash
      all_arg_specs = true
      val.each do |key,value|
        unless value.is_a? ArgSpec
          all_arg_specs = false
          break
        end
      end
      
      if all_arg_specs
        return val
      end
    end
  end
  
  return nil
end

#hash_make(arg_specs, hashed_args, assign_args = true) ⇒ Object

Process a hash that contains ‘hashed args’. Each hashed arg is intended to be used in initializing an object instance.

Parameters:

  • arg_specs (Enumerable)

    An enumerable of ArgSpec objects. Each object details an arg key that might be expected in the args hash.

  • hashed_args (Hash)

    A hash that should contain at least all the required keys and valid values, according to the arg_specs passed in. Nonrequired keys can be given as well, but if they are not then a default value is assigned (again, according to arg_specs passed in).

Raises:

  • (ArgumentError)


31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/hashmake/hash_makeable.rb', line 31

def hash_make arg_specs, hashed_args, assign_args = true
  arg_specs.each do |key, arg_spec|
    raise ArgumentError, "arg_specs item #{arg_spec} is not a ArgSpec" unless arg_spec.is_a?(ArgSpec)
  end
  raise ArgumentError, "hashed_args is not a Hash" unless hashed_args.is_a?(Hash)

  arg_specs.each do |key, arg_spec|
    if hashed_args.has_key?(key)
      val = hashed_args[key]

      if Hashmake::hash_makeable?(arg_spec.type)
        # If the val is not of the right type, but is a Hash, attempt to
        # make an object of the right type if it is hash-makeable
        if arg_spec.container == Array && val.is_a?(Array)
          val.each_index do |i|
            item = val[i]
            if !item.is_a?(arg_spec.type) && item.is_a?(Hash)
              val[i] = arg_spec.type.new item
            end
          end
        elsif arg_spec.container == Hash && val.is_a?(Hash)
          val.each_key do |item_key|
            item = val[item_key]
            if !item.is_a?(arg_spec.type) && item.is_a?(Hash)
              val[item_key] = arg_spec.type.new item
            end
          end
        else
          if !val.is_a?(arg_spec.type) && val.is_a?(Hash)
            val = arg_spec.type.new val
          end
        end
      end
    else
      if arg_spec.reqd
        raise ArgumentError, "hashed_args does not have required key #{key}"
      else
        if arg_spec.default.is_a?(Proc) && arg_spec.type != Proc
          val = arg_spec.default.call
        else
          val = arg_spec.default
        end
      end
    end
    
    validate_arg arg_spec, val
    if assign_args
      self.instance_variable_set("@#{key.to_s}".to_sym, val)
    end        
  end
end

#make_hash(arg_specs = nil) ⇒ Object

Produce a hash that contains ‘hashed args’. Each hashed arg is intended to be used in initializing an object instance.

Parameters:

  • arg_specs (Enumerable) (defaults to: nil)

    An enumerable of ArgSpec objects. Each one details an arg key that might be expected in the args hash. This param is nil by default. If the param is nil, this method will attempt to locate arg specs using find_arg_specs.



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/hashmake/hash_makeable.rb', line 141

def make_hash arg_specs = nil
  if arg_specs.nil?
    arg_specs = self.find_arg_specs
    raise "No arg specs given, and no class constant that is a Hash containing only ArgSpec objects was found" if arg_specs.nil?
  end
  
  arg_specs.each do |key, arg_spec|
    raise ArgumentError, "arg_specs item #{arg_spec} is not a ArgSpec" unless arg_spec.is_a?(ArgSpec)
  end

  hash = {}

  arg_specs.each do |key, arg_spec|
    sym = "@#{key}".to_sym
    raise ArgumentError, "current obj #{self} does not include instance variable #{sym}" if !self.instance_variables.include?(sym)
    val = self.instance_variable_get(sym)
    
    should_assign = false

    if arg_spec.reqd
      should_assign = true
    else
      if arg_spec.default.is_a?(Proc)
        should_assign = (val != arg_spec.default.call)
      else
        should_assign = (val != arg_spec.default)
      end
    end
    
    if should_assign
      if val.is_a?(Array) && arg_spec.container == Array
        ary = val
        val = []
        ary.each do |item|
          if Hashmake::hash_makeable?(item.class) and item.class == arg_spec.type
            val << item.make_hash
          else
            val << item
          end
        end
      elsif val.is_a?(Hash) && arg_spec.container == Hash
        hsh = val
        val = {}
        hsh.each do |hsh_key,item|
          if Hashmake::hash_makeable? item.class and item.class == arg_spec.type
            val[hsh_key] = item.make_hash
          else
            val[hsh_key] = item
          end
        end
      elsif Hashmake::hash_makeable?(val.class) and item.class == arg_spec.type
        val = val.make_hash
      end
      
      hash[key] = val
    end
  end
  
  return hash
end

#validate_arg(arg_spec, val) ⇒ Object

Check the given value, using the given ArgSpec object. An ArgumentError exception will be raised if the value is not valid.



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/hashmake/hash_makeable.rb', line 85

def validate_arg arg_spec, val
  if arg_spec.container == Array
    raise ArgumentError, "val #{val} is not an array" unless val.is_a?(Array)
    val.each do |item|
      raise ArgumentError, "array item #{item} is not a #{arg_spec.type}" unless item.is_a?(arg_spec.type)
      raise ArgumentError, "array item #{item} is not valid" unless arg_spec.validator.call(item)      
    end
  elsif arg_spec.container == Hash
    raise ArgumentError, "val #{val} is not a hash" unless val.is_a?(Hash)
    val.values.each do |item|
      raise ArgumentError, "hash item #{item} is not a #{arg_spec.type}" unless item.is_a?(arg_spec.type)
      raise ArgumentError, "hash item #{item} is not valid" unless arg_spec.validator.call(item)      
    end
  elsif arg_spec.container.nil?
    raise ArgumentError, "val #{val} is not a #{arg_spec.type}" unless val.is_a?(arg_spec.type)
    raise ArgumentError, "val #{val} is not valid" unless arg_spec.validator.call(val)      
  else
    raise ArgumentError, "arg_spec.container #{arg_spec.container} is not valid"
  end
  
  return true
end