module Usable

Constants

VERSION

Attributes

usables[W]

Public Class Methods

Struct(attributes = {}) click to toggle source
# File lib/usable/struct.rb, line 2
def self.Struct(attributes = {})
  Class.new do
    extend Usable
    self.usables = Usable::Config.new(attributes)
    define_usable_accessors
    attributes.keys.map(&:to_sym).each do |key|
      define_method(key) { @attrs[key] }
      define_method("#{key}=") { |new_val| @attrs[key] = new_val }
    end

    attr_accessor :attrs

    def initialize(attrs = {})
      @attrs = usables.merge(attrs)
    end

    def [](key)
      @attrs[key]
    end

    def []=(key, val)
      @attrs[key] = val
    end

    def each(&block)
      @attrs.each(&block)
    end

    def to_h
      @attrs.dup
    end

    alias to_hash to_h

    def merge(other)
      to_h.merge!(other)
    end

    alias + merge
  end
end
copy_usables(context, recipient) click to toggle source
# File lib/usable.rb, line 66
def self.copy_usables(context, recipient)
  unless Usable.frozen?
    recipient.usables += context.usables
    Usable.extended_constants << recipient
  end
end
extended(base) click to toggle source
# File lib/usable.rb, line 38
def self.extended(base)
  if base.is_a? Class
    # Define an instance level version of +usables+
    base.class_eval do
      def usables
        self.class.usables
      end

      def usable_method(method_name)
        self.class.usable_method(self, method_name)
      end
    end
  end

  unless base.respond_to?(:config)
    base.instance_eval do
      def config(&block)
        if block
          usables.instance_eval(&block)
        else
          usables
        end
      end
    end
  end
  extended_constants << base unless Usable.frozen?
end
extended_constants() click to toggle source

Keep track of extended classes and modules so we can freeze all usables on boot in production environments

# File lib/usable.rb, line 24
def self.extended_constants
  @extended_constants ||= Set.new
end
freeze() click to toggle source
Calls superclass method
# File lib/usable.rb, line 28
def self.freeze
  logger.debug { "freezing! #{extended_constants.to_a}" }
  extended_constants
  super
  # This may eager load classes, which is why we freeze ourselves first,
  # so the +extended+ hook doesn't try to modify @extended_constants while we're iterating over it
  extended_constants.each { |const| const.usables.freeze }
  self
end
logger() click to toggle source
# File lib/usable.rb, line 9
def self.logger
  @logger ||= begin
    require 'logger'
    Logger.new(STDOUT).tap do |config|
      config.formatter = proc { |*args| "[#{name}] #{args[0]}: #{args[-1]}\n" }
      config.level = Logger::ERROR
    end
  end
end
logger=(obj) click to toggle source
# File lib/usable.rb, line 19
def self.logger=(obj)
  @logger = obj
end
new(attrs = {}) click to toggle source
# File lib/usable/struct.rb, line 14
def initialize(attrs = {})
  @attrs = usables.merge(attrs)
end

Public Instance Methods

[](key) click to toggle source
# File lib/usable/struct.rb, line 18
def [](key)
  @attrs[key]
end
[]=(key, val) click to toggle source
# File lib/usable/struct.rb, line 22
def []=(key, val)
  @attrs[key] = val
end
config(&block) click to toggle source
# File lib/usable.rb, line 54
def config(&block)
  if block
    usables.instance_eval(&block)
  else
    usables
  end
end
define_usable_accessors() click to toggle source
# File lib/usable.rb, line 91
def define_usable_accessors
  usables.to_h.keys.each do |key|
    define_singleton_method(key) { usables.send(key) }
    define_singleton_method("#{key}=") { |new_val| usables.send("#{key}=", new_val) }
    define_method(key) { usables.send(key) }
    define_method("#{key}=") { |new_val| usables.send("#{key}=", new_val) }
  end
end
each(&block) click to toggle source
# File lib/usable/struct.rb, line 26
def each(&block)
  @attrs.each(&block)
end
extended(base) click to toggle source
Calls superclass method
# File lib/usable.rb, line 78
def extended(base)
  return if base === self
  base.extend(Usable) unless base.respond_to?(:usables)
  Usable.copy_usables(self, base)
  super
end
included(base) click to toggle source
Calls superclass method
# File lib/usable.rb, line 85
def included(base)
  base.extend(Usable) unless base.respond_to?(:usables)
  Usable.copy_usables(self, base)
  super
end
inherited(base) click to toggle source
Calls superclass method
# File lib/usable.rb, line 73
def inherited(base)
  Usable.copy_usables(self, base)
  super
end
merge(other) click to toggle source
# File lib/usable/struct.rb, line 36
def merge(other)
  to_h.merge!(other)
end
to_h() click to toggle source
# File lib/usable/struct.rb, line 30
def to_h
  @attrs.dup
end
usable(*args, &block) click to toggle source

@description Includes the given module with a set of options or block to configure it

@example

class Example
  extend Usable
  usable Mixin, only: [:foo, :bar] do
    baz "Available as `Example.usables.baz` or `Example.usables.mixin.baz`"
  end
end

@note Hides methods @param [Module] mod @param [Hash] options Customize the extension of the module as well as define config settings on the target @option [Array,Symbol] :only Limit which methods are copied from the module @option [String,Symbol] :method (:include) The method to use for including the module @return self

# File lib/usable.rb, line 123
def usable(*args, &block)
  options = args.last.is_a?(Hash) ? args.pop : {}
  only = options.delete(:only)
  extension_method = options.delete(:method)
  args.each do |mod|
    ModExtender.new(mod, only: only, method: extension_method).call self
    # Define settings on @usables and on the scoped @usables
    scope = Config.new
    # Nest the new config under a namespace based on it's name, unless it's the default name we gave
    if mod.name && !mod.name.include?("UsableMod")
      scope_name = mod.name.split('::').last.gsub(/\B([A-Z])([a-z_0-9])/, '_\1\2').downcase
      usables[scope_name] = scope
    end
    if mod.respond_to? :usables
      scope += mod.usables
      self.usables += mod.usables
    end
    # any left over -options- are considered "config" settings
    if options
      [scope, usables].each { |x| options.each { |k, v| x[k] = v } }
    end
    if block_given?
      [scope, usables].each { |x| x.instance_eval(&block) }
    end
    if mod.const_defined?(:InstanceMethods, false)
      send :include, mod.const_get(:InstanceMethods, false)
    end
    if mod.const_defined?(:ClassMethods, false)
      send :extend, mod.const_get(:ClassMethods, false)
    end
  end
  self
end
usable_method(method_name) click to toggle source
# File lib/usable.rb, line 46
def usable_method(method_name)
  self.class.usable_method(self, method_name)
end
usables() click to toggle source
# File lib/usable.rb, line 42
def usables
  self.class.usables
end