Module: BBLib::Attrs

Defined in:
lib/bblib/core/mixins/attrs.rb

Overview

Adds type casted attr getters and setters with all kinds of other goodness to ensure classes accept, set and return data the correct way without requiring piles or boiler plate code.

Instance Method Summary collapse

Instance Method Details

#_ancestor_attrsObject



13
14
15
16
17
18
19
20
21
22
23
24
# File 'lib/bblib/core/mixins/attrs.rb', line 13

def _ancestor_attrs
  hash = {}
  ancestors.reverse.each do |ancestor|
    next if ancestor == self
    hash = hash.merge(ancestor._attrs) if ancestor.respond_to?(:_attrs)
  end
  # Need to dup to avoid subclasses modifying parents
  hash.hmap do |k, v|
    v[:options] = v[:options].dup
    [k, v.dup]
  end
end

#_attr_pack(arg, klasses, opts = {}, &block) ⇒ Object



435
436
437
438
439
440
441
# File 'lib/bblib/core/mixins/attrs.rb', line 435

def _attr_pack(arg, klasses, opts = {}, &block)
  klasses = [klasses].flatten
  unless BBLib.is_any?(arg, *klasses)
    return klasses.first.new(*[arg].flatten(1), &block) if klasses.first.respond_to?(:new)
  end
  nil
end

#_attr_serialize?(opts) ⇒ Boolean

Returns:

  • (Boolean)


122
123
124
125
126
# File 'lib/bblib/core/mixins/attrs.rb', line 122

def _attr_serialize?(opts)
  return false unless respond_to?(:serialize_method)
  (opts[:private] || opts[:protected]) && opts[:serialize] ||
  (opts.include?(:serialize) && opts[:serialize]) || !opts.include?(:serialize)
end

#_attrsObject



9
10
11
# File 'lib/bblib/core/mixins/attrs.rb', line 9

def _attrs
  @_attrs ||= _ancestor_attrs
end

#attr_accessor(*args, **opts) ⇒ Object



56
57
58
59
60
61
62
# File 'lib/bblib/core/mixins/attrs.rb', line 56

def attr_accessor(*args, **opts)
  args.each do |arg|
    _register_attr(arg, :attr_accessor)
    serialize_method(arg, opts[:serialize_method], opts[:serialize_opts] || {}) if _attr_serialize?(opts)
  end
  super(*args)
end

#attr_array(*methods, **opts) ⇒ Object Also known as: attr_ary



249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/bblib/core/mixins/attrs.rb', line 249

def attr_array(*methods, **opts)
  opts[:default] = [] unless opts.include?(:default) || opts.include?(:default_proc)
  methods.each do |method|
    attr_custom(method, opts) do |arg|
      if opts[:allow_nil] && arg.nil?
        arg
      else
        args = arg.is_a?(Array) ? arg : [arg]
        args = args.uniq if opts[:uniq]
        args
      end
    end
    attr_array_adder(method, opts[:adder_name], singleton: opts[:singleton]) if opts[:add_rem] || opts[:adder]
    attr_array_remover(method, opts[:remover_name], singleton: opts[:singleton]) if opts[:add_rem] || opts[:remover]
  end
end

#attr_array_adder(method, name = nil, singleton: false, &block) ⇒ Object



302
303
304
305
306
307
308
309
310
311
312
313
# File 'lib/bblib/core/mixins/attrs.rb', line 302

def attr_array_adder(method, name = nil, singleton: false, &block)
  name = "add_#{method}" unless name
  mthd_type = singleton ? :define_singleton_method : :define_method
  send(mthd_type, name) do |*args|
    array = send(method)
    [args].flatten(1).each do |arg|
      arg = yield(arg) if block_given?
      array.push(arg)
    end
    send("#{method}=", array)
  end
end

#attr_array_of(klasses, *methods, **opts) ⇒ Object Also known as: attr_ary_of



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/bblib/core/mixins/attrs.rb', line 268

def attr_array_of(klasses, *methods, **opts)
  opts[:default] = [] unless opts.include?(:default) || opts.include?(:default_proc)
  klasses = [klasses].flatten
  methods.each do |method|
    attr_custom(method, opts.merge(classes: klasses)) do |args|
      array = []
      if args.nil?
        if opts[:allow_nil]
          array = nil
        else
          raise ArgumentError, "#{method} cannot be set to nil."
        end
      else
        args = [args] unless args.is_a?(Array)
        args.each do |arg|
          match = BBLib.is_any?(arg, *klasses)
          if match
            array.push(arg)
          elsif arg && (!opts.include?(:pack) || opts[:pack]) && arg = _attr_pack(arg, klasses, opts)
            array.push(arg)
          else
            raise TypeError, "Invalid class passed to #{method} on #{self}: #{arg.class}. Must be a #{klasses.join_terms(:or)}." unless opts[:suppress]
          end
        end
      end
      opts[:uniq] ? array.uniq : array
    end
    attr_array_adder(method, opts[:adder_name], singleton: opts[:singleton]) if opts[:add_rem] || opts[:adder]
    attr_array_remover(method, opts[:remover_name], singleton: opts[:singleton]) if opts[:add_rem] || opts[:remover]
  end
end

#attr_array_remover(method, name = nil, singleton: false) ⇒ Object



315
316
317
318
319
320
321
322
323
324
# File 'lib/bblib/core/mixins/attrs.rb', line 315

def attr_array_remover(method, name = nil, singleton: false)
  name = "remove_#{method}" unless name
  define_method(name) do |*args|
    array = instance_variable_get("@#{method}")
    [args].flatten(1).map do |arg|
      next unless array && !array.empty?
      array.delete(arg)
    end
  end
end

#attr_boolean(*methods, **opts) ⇒ Object Also known as: attr_bool



179
180
181
182
183
184
185
186
187
188
189
# File 'lib/bblib/core/mixins/attrs.rb', line 179

def attr_boolean(*methods, **opts)
  methods.each do |method|
    attr_custom(method, opts) { |arg| arg ? true : false }
    next if opts[:no_?]
    if opts[:singleton]
      singleton_class.send(:alias_method, "#{method}?", method)
    else
      alias_method "#{method}?", method
    end
  end
end

#attr_custom(method, opts = {}, &block) ⇒ Object



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/bblib/core/mixins/attrs.rb', line 64

def attr_custom(method, opts = {}, &block)
  called_by = caller_locations(1, 1)[0].label.gsub('block in ', '') rescue :attr_custom
  type      = (called_by =~ /^attr_/ ? called_by.to_sym : (opts[:attr_type] || :custom))
  opts      = opts.dup
  ivar      = "@#{method}".to_sym
  mthd_type = opts[:singleton] ? :define_singleton_method : :define_method

  self.send(mthd_type, "#{method}=") do |args|
    if opts[:pre_proc]
      if opts[:pre_proc].is_a?(Proc)
        args = opts[:pre_proc].call(args)
      else
        args = send(opts[:pre_proc], args)
      end
    end
    instance_variable_set(ivar, yield(args))
  end

  self.send(mthd_type, method) do
    if opts[:getter] && opts[:getter].is_a?(Proc)
      opts[:getter].arity == 0 ? opts[:getter].call : opts[:getter].call(self)
    elsif instance_variable_defined?(ivar) && !(var = instance_variable_get(ivar)).nil?
      var
    elsif opts.include?(:default) || opts.include?(:default_proc)
      default_value =
        if opts[:default].respond_to?(:dup) && BBLib.is_any?(opts[:default], Array, Hash)
          opts[:default].dup rescue opts[:default]
        elsif opts[:default_proc].is_a?(Proc)
          prc = opts[:default_proc]
          prc.arity == 0 ? prc.call : prc.call(self)
        elsif opts[:default_proc].is_a?(Symbol)
          send(opts[:default_proc])
        else
          opts[:default]
        end
      send("#{method}=", default_value)
    end
  end

  if opts[:aliases]
    [opts[:aliases]].flatten.each do |als|
      obj = opts[:singleton] ? self.singleton_class : self
      obj.send(:alias_method, als, method)
      obj.send(:alias_method, "#{als}=", "#{method}=")
    end
  end

  unless opts[:singleton]
    protected method if opts[:protected] || opts[:protected_reader]
    protected "#{method}=".to_sym if opts[:protected] || opts[:protected_writer]
    private method if opts[:private] || opts[:private_reader]
    private "#{method}=".to_sym if opts[:private] || opts[:private_writer]

    serialize_method(method, opts[:serialize_method], (opts[:serialize_opts] || {}).merge(default: opts[:default])) if _attr_serialize?(opts)
    _register_attr(method, type, opts)
  end
end

#attr_date(*methods, **opts) ⇒ Object



378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
# File 'lib/bblib/core/mixins/attrs.rb', line 378

def attr_date(*methods, **opts)
  methods.each do |method|
    attr_custom(method, **opts) do |arg|
      if opts[:formats]
        arg = arg.to_s
        [opts[:formats]].flatten.each do |format|
          arg = Date.strptime(arg, format) rescue arg
        end
      end
      if arg.is_a?(Date) || arg.nil? && opts[:allow_nil]
        arg
      elsif arg.is_a?(Numeric)
        Date.parse(Time.at(arg).to_s)
      else
        begin
          Date.parse(arg.to_s)
        rescue => _e
          nil
        end
      end
    end
  end
end

#attr_dir(*methods, **opts) ⇒ Object



340
341
342
343
344
345
346
347
348
349
350
351
352
# File 'lib/bblib/core/mixins/attrs.rb', line 340

def attr_dir(*methods, **opts)
  methods.each do |method|
    attr_custom(method, opts) do |arg|
      exists = Dir.exist?(arg.to_s)
      if !exists && (opts[:mkdir] || opts[:mkpath]) && arg
        FileUtils.mkpath(arg.to_s)
        exists = Dir.exist?(arg.to_s)
      end
      raise ArgumentError, "#{method} must be set to a valid directory. '#{arg}' cannot be found." unless exists || (opts[:allow_nil] && arg.nil?)
      arg
    end
  end
end

#attr_element_of(list, *methods, **opts) ⇒ Object



216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/bblib/core/mixins/attrs.rb', line 216

def attr_element_of(list, *methods, **opts)
  methods.each do |method|
    attr_custom(method, opts.merge(list: list)) do |arg|
      ls = list.is_a?(Proc) ? list.call(self) : list
      if ls.include?(arg) || (opts[:allow_nil] && arg.nil?)
        arg
      elsif opts[:fallback]
        opts[:fallback]
      else
        raise ArgumentError, "Invalid option '#{arg}' for #{method}." unless opts.include?(:raise) && !opts[:raise]
      end
    end
  end
end

#attr_elements_of(list, *methods, **opts) ⇒ Object



231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# File 'lib/bblib/core/mixins/attrs.rb', line 231

def attr_elements_of(list, *methods, **opts)
  opts[:default] = [] unless opts.include?(:default) || opts.include?(:default_proc)
  methods.each do |method|
    attr_custom(method, opts.merge(list: list)) do |args|
      ls = list.is_a?(Proc) ? list.call(self) : list
      [].tap do |final|
        [args].flatten(1).each do |arg|
          if ls.include?(arg) || (opts[:allow_nil] && arg.nil?)
            final << arg
          else
            raise ArgumentError, "Invalid option '#{arg}' for #{method}." unless opts.include?(:raise) && !opts[:raise]
          end
        end
      end
    end
  end
end

#attr_file(*methods, **opts) ⇒ Object



326
327
328
329
330
331
332
333
334
335
336
337
338
# File 'lib/bblib/core/mixins/attrs.rb', line 326

def attr_file(*methods, **opts)
  methods.each do |method|
    attr_custom(method, opts) do |arg|
      exists = File.exist?(arg.to_s)
      if !exists && opts[:mkfile] && arg
        FileUtils.touch(arg.to_s)
        exists = File.exist?(arg.to_s)
      end
      raise ArgumentError, "#{method} must be set to a valid file. '#{arg}' cannot be found." unless exists || (opts[:allow_nil] && arg.nil?)
      arg
    end
  end
end

#attr_float(*methods, **opts) ⇒ Object



159
160
161
162
163
# File 'lib/bblib/core/mixins/attrs.rb', line 159

def attr_float(*methods, **opts)
  methods.each do |method|
    attr_custom(method, opts) { |arg| arg.nil? && opts[:allow_nil] ? arg : arg.to_f }
  end
end

#attr_float_between(min, max, *methods, **opts) ⇒ Object



201
202
203
204
205
# File 'lib/bblib/core/mixins/attrs.rb', line 201

def attr_float_between(min, max, *methods, **opts)
  methods.each do |method|
    attr_custom(method, opts.merge(min: min, max: max)) { |arg| arg.nil? && opts[:allow_nil] ? arg : BBLib.keep_between(arg, min, max).to_f }
  end
end

#attr_hash(*methods, **opts) ⇒ Object



402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
# File 'lib/bblib/core/mixins/attrs.rb', line 402

def attr_hash(*methods, **opts)
  opts[:default] = {} unless opts.include?(:default) || opts.include?(:default_proc)
  methods.each do |method|
    attr_custom(method, **opts) do |arg|
      raise ArgumentError, "#{method} must be set to a hash, not a #{arg.class} (for #{self})." unless arg.is_a?(Hash) || arg.nil? && opts[:allow_nil]
      if opts[:keys] && arg
        arg.keys.each do |key|
          if BBLib.is_any?(key, *opts[:keys])
            next
          elsif (opts.include?(:pack_key) && opts[:pack_key]) && new_key = _attr_pack(key, klasses, opts)
            arg[new_key] = arg.delete(key)
          else
            raise ArgumentError, "Invalid key type for #{method}: #{key.class}. Must be #{[opts[:keys]].flatten.join_terms(:or)}."
          end
        end
      end
      if opts[:values] && arg
        arg.each do |key, value|
          if BBLib.is_any?(value, *opts[:values])
            next
          elsif (!opts.include?(:pack_value) || opts[:pack_value]) && value = _attr_pack(value, klasses, opts)
            arg[key] = arg.delete(value)
          else
            raise TypeError, "Invalid value type for #{method}: #{value.class}. Must be #{opts[:values].join_terms(:or)}."
          end
        end
      end
      arg = arg.keys_to_sym if opts[:symbol_keys] && arg
      arg
    end
  end
end

#attr_integer(*methods, **opts) ⇒ Object Also known as: attr_int



151
152
153
154
155
# File 'lib/bblib/core/mixins/attrs.rb', line 151

def attr_integer(*methods, **opts)
  methods.each do |method|
    attr_custom(method, opts) { |arg| arg.nil? && opts[:allow_nil] ? arg : arg.to_i }
  end
end

#attr_integer_between(min, max, *methods, **opts) ⇒ Object Also known as: attr_int_between



193
194
195
196
197
# File 'lib/bblib/core/mixins/attrs.rb', line 193

def attr_integer_between(min, max, *methods, **opts)
  methods.each do |method|
    attr_custom(method, opts.merge(min: min, max: max)) { |arg| arg.nil? && opts[:allow_nil] ? arg : BBLib.keep_between(arg, min, max).to_i }
  end
end

#attr_integer_loop(min, max, *methods, **opts) ⇒ Object Also known as: attr_int_loop, attr_float_loop



207
208
209
210
211
# File 'lib/bblib/core/mixins/attrs.rb', line 207

def attr_integer_loop(min, max, *methods, **opts)
  methods.each do |method|
    attr_custom(method, opts) { |arg| arg.nil? && opts[:allow_nil] ? arg : BBLib.loop_between(arg, min, max) }
  end
end

#attr_of(klasses, *methods, **opts) ⇒ Object



128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/bblib/core/mixins/attrs.rb', line 128

def attr_of(klasses, *methods, **opts)
  allowed = [klasses].flatten
  methods.each do |method|
    attr_custom(method, opts.merge(_attr_type: :of, classes: klasses)) do |arg|
      if BBLib.is_any?(arg, *allowed) || (arg.nil? && opts[:allow_nil])
        arg
      elsif arg && (!opts.include?(:pack) || opts[:pack]) && arg = _attr_pack(arg, klasses, opts)
        arg
      else
        raise TypeError, "#{method} must be set to a class of #{allowed.join_terms(:or)}, not #{arg.class} (#{self})" unless opts[:suppress]
      end
    end
  end
end

#attr_reader(*args, **opts) ⇒ Object



43
44
45
46
47
48
49
# File 'lib/bblib/core/mixins/attrs.rb', line 43

def attr_reader(*args, **opts)
  args.each do |arg|
    _register_attr(arg, :attr_reader)
    serialize_method(arg, opts[:serialize_method], opts[:serialize_opts] || {}) if _attr_serialize?(opts)
  end
  super(*args)
end

#attr_set(name, opts = {}) ⇒ Object



26
27
28
29
30
31
# File 'lib/bblib/core/mixins/attrs.rb', line 26

def attr_set(name, opts = {})
  return false unless _attrs[name]
  opts.each do |k, v|
    _attrs[name][:options][k] = v
  end
end

#attr_string(*methods, **opts) ⇒ Object Also known as: attr_str



143
144
145
146
147
# File 'lib/bblib/core/mixins/attrs.rb', line 143

def attr_string(*methods, **opts)
  methods.each do |method|
    attr_custom(method, opts) { |arg| arg.nil? && opts[:allow_nil] ? arg : arg.to_s }
  end
end

#attr_symbol(*methods, **opts) ⇒ Object Also known as: attr_sym



165
166
167
168
169
170
171
172
173
174
175
# File 'lib/bblib/core/mixins/attrs.rb', line 165

def attr_symbol(*methods, **opts)
  methods.each do |method|
    attr_custom(method, opts) do |arg|
      if arg.nil?
        opts[:allow_nil] ? arg : raise(ArgumentError, "#{method} cannot be set to nil.")
      else
        arg.to_s.to_sym
      end
    end
  end
end

#attr_time(*methods, **opts) ⇒ Object



354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
# File 'lib/bblib/core/mixins/attrs.rb', line 354

def attr_time(*methods, **opts)
  methods.each do |method|
    attr_custom(method, **opts) do |arg|
      if opts[:formats]
        arg = arg.to_s
        [opts[:formats]].flatten.each do |format|
          arg = Time.strptime(arg, format) rescue arg
        end
      end
      if arg.is_a?(Time) || arg.nil? && opts[:allow_nil]
        arg
      elsif arg.is_a?(Numeric)
        Time.at(arg)
      else
        begin
          Time.parse(arg.to_s)
        rescue => _e
          nil
        end
      end
    end
  end
end

#attr_writer(*args, **opts) ⇒ Object



51
52
53
54
# File 'lib/bblib/core/mixins/attrs.rb', line 51

def attr_writer(*args, **opts)
  args.each { |arg| _register_attr(arg, :attr_writer) }
  super(*args)
end

#instance_readersObject

Lists all attr_* getter methods that were created on this class.



34
35
36
# File 'lib/bblib/core/mixins/attrs.rb', line 34

def instance_readers
  _attrs.map { |k, v| [:attr_writer].any? { |t| v[:type] == t } ? nil : k }.compact
end

#instance_writersObject

Lists all attr_* setter methods that were created on this class.



39
40
41
# File 'lib/bblib/core/mixins/attrs.rb', line 39

def instance_writers
  _attrs.keys.map { |m| "#{m}=".to_sym }.select { |m| method_defined?(m) }
end