Module: Mobility::Plugin

Overview

Defines convenience methods on plugin module to hook into initialize/included method calls on Mobility::Pluggable instance.

Also includes a configure class method to apply plugins to a pluggable (Pluggable instance), with a block.

Examples:

Defining a plugin

module MyPlugin
  extend Mobility::Plugin

  initialize_hook do |*names|
    names.each do |name|
      define_method "#{name}_foo" do
        # method body
      end
    end
  end

  included_hook do |klass, backend_class|
    backend_class.include MyBackendMethods
    klass.include MyModelMethods
  end
end

Configure an attributes class with plugins

class Translations < Mobility::Translations
end

Mobility::Plugin.configure(Translations) do
  cache
  fallbacks
end

Translations.included_modules
#=> [Mobility::Plugins::Fallbacks, Mobility::Plugins::Cache, ...]

Defined Under Namespace

Classes: CyclicDependency, DependencyConflict

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.configure(pluggable, defaults = pluggable.defaults) { ... } ⇒ Hash

Configure a pluggable Mobility::Pluggable with a block. Yields to a clean room where plugin names define plugins on the module. Plugin dependencies are resolved before applying them.

Examples:

Mobility::Plugin.configure(Translations) do
  cache
  fallbacks [:en, :de]
end

Parameters:

  • pluggable (Class, Module)
  • defaults (Hash) (defaults to: pluggable.defaults)

    Plugin defaults hash to update

Yields:

  • Block to define plugins

Returns:

  • (Hash)

    Updated plugin defaults

Raises:



67
68
69
# File 'lib/mobility/plugin.rb', line 67

def configure(pluggable, defaults = pluggable.defaults, &block)
  DependencyResolver.new(pluggable, defaults).call(&block)
end

Instance Method Details

#configure_default(defaults, key, *args) ⇒ Object

Method called when defining plugins to assign a default based on arguments and keyword arguments to the plugin method. By default, we simply assign the first argument, but plugins can opt to customize this if additional arguments or keyword arguments are required. (The backend plugin uses keyword arguments to set backend options.)

Parameters:

  • defaults (Hash)
  • key (Symbol)

    Plugin key on hash

  • args (Array)

    Method arguments



118
119
120
# File 'lib/mobility/plugin.rb', line 118

def configure_default(defaults, key, *args)
  defaults[key] = args[0] unless args.empty?
end

#default(value) ⇒ Object



105
106
107
# File 'lib/mobility/plugin.rb', line 105

def default(value)
  @default = value
end

#dependenciesObject



101
102
103
# File 'lib/mobility/plugin.rb', line 101

def dependencies
  @dependencies ||= {}
end

#dependencies_satisfied?(klass) ⇒ Boolean

Does this class include all plugins this plugin depends (directly) on?

Parameters:

  • klass (Class)

    Pluggable class

Returns:

  • (Boolean)


124
125
126
127
# File 'lib/mobility/plugin.rb', line 124

def dependencies_satisfied?(klass)
  plugin_keys = klass.included_plugins.map { |plugin| Plugins.lookup_name(plugin) }
  (dependencies.keys - plugin_keys).none?
end

#included(pluggable) ⇒ Object



94
95
96
97
98
99
# File 'lib/mobility/plugin.rb', line 94

def included(pluggable)
  if defined?(@default) && !pluggable.defaults.has_key?(name = Plugins.lookup_name(self))
    pluggable.defaults[name] = @default
  end
  super
end

#included_hook(&block) ⇒ Object



82
83
84
85
86
87
88
89
90
91
92
# File 'lib/mobility/plugin.rb', line 82

def included_hook(&block)
  plugin = self

  define_method :included do |klass|
    super(klass).tap do |backend_class|
      if plugin.dependencies_satisfied?(self.class)
        class_exec(klass, backend_class, &block)
      end
    end
  end
end

#initialize_hook(&block) ⇒ Object



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

def initialize_hook(&block)
  plugin = self

  define_method :initialize do |*args, **options|
    super(*args, **options)

    class_exec(*args, &block) if plugin.dependencies_satisfied?(self.class)
  end
end

#requires(plugin, include: true) ⇒ Object

Specifies a dependency of this plugin.

By default, the dependency is included (include: true). Passing :before or :after will ensure the dependency is included before or after this plugin.

Passing false does not include the dependency, but checks that it has been included when running include and initialize hooks (so hooks will not run for this plugin if it has not been included). In other words: disable this plugin unless this dependency has been included elsewhere. (Note that this check is not applied recursively.)

Parameters:

  • plugin (Symbol)

    Name of plugin dependency

  • [TrueClass, (Hash)

    a customizable set of options



143
144
145
146
147
148
# File 'lib/mobility/plugin.rb', line 143

def requires(plugin, include: true)
  unless [true, false, :before, :after].include?(include)
    raise ArgumentError, "requires 'include' keyword argument must be one of: true, false, :before or :after"
  end
  dependencies[plugin] = include
end