Module: Usable

Included in:
Persistence
Defined in:
lib/usable.rb,
lib/usable/config.rb,
lib/usable/struct.rb,
lib/usable/version.rb,
lib/usable/eager_load.rb,
lib/usable/persistence.rb,
lib/usable/config_multi.rb,
lib/usable/mod_extender.rb,
lib/usable/config_register.rb

Defined Under Namespace

Modules: ConfigMulti, ConfigRegister, EagerLoad, Persistence Classes: Config, ModExtender, Railtie

Constant Summary collapse

VERSION =
"3.10.0".freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#usablesObject



100
101
102
# File 'lib/usable.rb', line 100

def usables
  @usables ||= Config.new
end

Class Method Details

.copy_usables(context, recipient) ⇒ Object



66
67
68
69
70
71
# 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) ⇒ Object



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
# 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_constantsObject

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



24
25
26
# File 'lib/usable.rb', line 24

def self.extended_constants
  @extended_constants ||= Set.new
end

.freezeObject



28
29
30
31
32
33
34
35
36
# 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

.loggerObject



9
10
11
12
13
14
15
16
17
# 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) ⇒ Object



19
20
21
# File 'lib/usable.rb', line 19

def self.logger=(obj)
  @logger = obj
end

.Struct(attributes = {}) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 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

Instance Method Details

#define_usable_accessorsObject



91
92
93
94
95
96
97
98
# 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

#extended(base) ⇒ Object



78
79
80
81
82
83
# 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) ⇒ Object



85
86
87
88
89
# 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) ⇒ Object



73
74
75
76
# File 'lib/usable.rb', line 73

def inherited(base)
  Usable.copy_usables(self, base)
  super
end

#usable(*args, only: nil, except: nil, method: nil, **options, &block) ⇒ Object

Note:

Hides methods

Returns self.

Examples:


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

Parameters:

  • args (Array<Module>)
    • array of modules to potentially modify the and then include

  • options (Hash)

    Customize the extension of the module as well as define config settings on the target

  • [Array,Symbol] (Hash)

    a customizable set of options

  • [String,Symbol] (Hash)

    a customizable set of options

  • [Array<Symbol>] (Hash)

    a customizable set of options

Returns:

  • self



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/usable.rb', line 124

def usable(*args, only: nil, except: nil, method: nil, **options, &block)
  args.each do |mod|
    ModExtender.new(mod, only: only, method: method, except: except).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(context, method_name) ⇒ Method

Returns bound to the given -context-.

Returns:

  • (Method)

    bound to the given -context-



156
157
158
# File 'lib/usable.rb', line 156

def usable_method(context, method_name)
  usables.available_methods[method_name].bind(context)
end