Module: Merb::Slices

Defined in:
lib/merb-slices/module.rb,
lib/merb-slices/version.rb,
lib/merb-slices/module_mixin.rb,
lib/merb-slices/controller_mixin.rb

Defined Under Namespace

Modules: ControllerMixin, ModuleMixin, Support Classes: Activate, DynamicLoader, Initialize, Loader

Constant Summary collapse

VERSION =
'1.1.3'.freeze

Class Method Summary collapse

Class Method Details

.[](module_name) ⇒ Module

Retrieve a slice module by name

Parameters:

  • The (#to_s)

    slice module to check for.

Returns:

  • (Module)

    The slice module itself.



10
11
12
# File 'lib/merb-slices/module.rb', line 10

def [](module_name)
  Object.full_const_get(module_name.to_s) if exists?(module_name)
end

.activate(slice_module) ⇒ Object

Activate a Slice module at runtime

Looks for previously registered slices; then searches :search_path for matches.

Parameters:

  • slice_module (#to_s)

    Usually a string of version of the slice module name.



89
90
91
92
93
94
95
96
97
98
99
# File 'lib/merb-slices/module.rb', line 89

def activate(slice_module)  
  unless slice_file = self.files[slice_module.to_s]
    module_name_underscored = slice_module.to_s.snake_case.escape_regexp
    module_name_dasherized  = module_name_underscored.tr('_', '-').escape_regexp
    regexp = Regexp.new(/\/(#{module_name_underscored}|#{module_name_dasherized})\/lib\/(#{module_name_underscored}|#{module_name_dasherized})\.rb$/)
    slice_file = slice_files_from_search_path.find { |path| path.match(regexp) } # from search path(s)
  end
  activate_by_file(slice_file) if slice_file
rescue => e
  Merb.logger.error!("Failed to activate slice #{slice_module} (#{e.message})")
end

.activate_by_file(slice_file) ⇒ Object Also known as: register_and_load

Register a Slice by its gem/lib init file path and activate it at runtime

Normally slices are loaded using BootLoaders on application startup. This method gives you the possibility to add slices at runtime, all without restarting your app. Together with #deactivate it allows you to enable/disable slices at any time. The router is reloaded to incorporate any changes. Disabled slices will be skipped when routes are regenerated.

Examples:

Merb::Slices.activate_by_file(‘/path/to/gems/slice-name/lib/slice-name.rb’)

Parameters:

  • slice_file (String)

    The path of the gem ‘init file’



113
114
115
116
117
118
119
120
121
122
123
# File 'lib/merb-slices/module.rb', line 113

def activate_by_file(slice_file)
  Merb::Slices::Loader.load_classes(slice_file)
  slice = register(slice_file, false) # just to get module by slice_file
  slice.load_slice # load the slice
  Merb::Slices::Loader.reload_router!
  slice.init     if slice.respond_to?(:init)
  slice.activate if slice.respond_to?(:activate) && slice.routed?
  slice
rescue
  Merb::Slices::Loader.reload_router!
end

.configHash

Returns The configuration loaded from Merb.root / “config/slices.yml” or, if the load fails, an empty hash.

Returns:

  • (Hash)

    The configuration loaded from Merb.root / “config/slices.yml” or, if the load fails, an empty hash.



192
193
194
195
196
197
198
199
200
201
202
# File 'lib/merb-slices/module.rb', line 192

def config
  @config ||= begin
    empty_hash = Hash.new { |h,k| h[k] = {} }
    if File.exists?(Merb.root / "config" / "slices.yml")
      require "yaml"
      YAML.load(File.read(Merb.root / "config" / "slices.yml")) || empty_hash
    else
      empty_hash
    end
  end
end

.deactivate(slice_module) ⇒ Object

Deactivate a Slice module at runtime

Parameters:

  • slice_module (#to_s)

    The Slice module to unregister.



129
130
131
132
133
134
# File 'lib/merb-slices/module.rb', line 129

def deactivate(slice_module)
  if slice = self[slice_module]
    slice.deactivate if slice.respond_to?(:deactivate) && slice.routed?
    unregister(slice)
  end
end

.deactivate_by_file(slice_file) ⇒ Object

Deactivate a Slice module at runtime by specifying its slice file

Parameters:

  • slice_file (String)

    The Slice location of the slice init file to unregister.



139
140
141
142
143
# File 'lib/merb-slices/module.rb', line 139

def deactivate_by_file(slice_file)
  if slice = self.slices.find { |s| s.file == slice_file }
    deactivate(slice.name)
  end
end

.each_slice {|module| ... } ⇒ Object

Iterate over all registered slices

By default iterates alphabetically over all registered modules. If Merb::Plugins.config[:queue] is set, only the defined modules are loaded in the given order. This can be used to selectively load slices, and also maintain load-order for slices that depend on eachother.

Yields:

  • Iterate over known slices and pass in the slice module.

Yield Parameters:

  • module (Module)

    The Slice module.



254
255
256
257
258
259
260
261
# File 'lib/merb-slices/module.rb', line 254

def each_slice(&block)
  loadable_slices = Merb::Plugins.config[:merb_slices].key?(:queue) ? Merb::Plugins.config[:merb_slices][:queue] : slice_names
  loadable_slices.each do |module_name|
    if mod = self[module_name]
      block.call(mod)
    end
  end
end

.exists?(module_name) ⇒ Boolean

Check whether a Slice exists

Parameters:

  • The (#to_s)

    slice module to check for.

Returns:

  • (Boolean)


223
224
225
226
# File 'lib/merb-slices/module.rb', line 223

def exists?(module_name)
  const_name = module_name.to_s.camel_case
  slice_names.include?(const_name) && Object.const_defined?(const_name)
end

.filename2module(slice_file) ⇒ Object

Helper method to transform a slice filename to a module Symbol



15
16
17
# File 'lib/merb-slices/module.rb', line 15

def filename2module(slice_file)
  File.basename(slice_file, '.rb').gsub('-', '_').camel_case.to_sym
end

.filesHash

Note:

This is unaffected by deactivating a slice; used to reload slices by name.

A lookup for finding a Slice module’s slice file path

Returns:

  • (Hash)

    A Hash mapping module names to slice files.



240
241
242
# File 'lib/merb-slices/module.rb', line 240

def files
  @files ||= {}
end

.named_routesHash[Hash]

Returns A Hash mapping between slice identifiers and non-prefixed named routes.

Returns:

  • (Hash[Hash])

    A Hash mapping between slice identifiers and non-prefixed named routes.



185
186
187
# File 'lib/merb-slices/module.rb', line 185

def named_routes
  @named_routes ||= {}
end

.pathsHash

Note:

Whenever a slice is deactivated, its path is removed from the lookup.

A lookup for finding a Slice module’s path

Returns:

  • (Hash)

    A Hash mapping module names to root paths.



232
233
234
# File 'lib/merb-slices/module.rb', line 232

def paths
  @paths ||= {}
end

.register(slice_file, force = true) ⇒ Module

Register a Slice by its gem/lib path for loading at startup

This is referenced from gems/<slice-gem-x.x.x>/lib/<slice-gem>.rb Which gets loaded for any gem. The name of the file is used to extract the Slice module name.

Examples:

Merb::Slices::register(__FILE__)

Merb::Slices::register(‘/path/to/my-slice/lib/my-slice.rb’)

Parameters:

  • slice_file (String)

    The path of the gem ‘init file’

  • force (Boolean) (defaults to: true)

    Whether to overwrite currently registered slice or not.

Returns:

  • (Module)

    The Slice module that has been setup.



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/merb-slices/module.rb', line 32

def register(slice_file, force = true)
  # do what filename2module does, but with intermediate variables
  identifier  = File.basename(slice_file, '.rb')
  underscored = identifier.gsub('-', '_')
  module_name = underscored.camel_case
  slice_path  = File.expand_path(File.dirname(slice_file) + '/..')
  # check if slice_path exists instead of just the module name - more flexible
  if !self.paths.include?(slice_path) || force
    Merb.logger.verbose!("Registered slice '#{module_name}' located at #{slice_path}") if force
    self.files[module_name] = slice_file
    self.paths[module_name] = slice_path
    slice_mod = setup_module(module_name)
    slice_mod.identifier = identifier
    slice_mod.identifier_sym = underscored.to_sym
    slice_mod.root = slice_path
    slice_mod.file = slice_file
    slice_mod.registered
    slice_mod
  else
    Merb.logger.info!("Already registered slice '#{module_name}' located at #{slice_path}")
    Object.full_const_get(module_name)
  end
end

.register_slices_from_search_path!Object

Look for any slices in Merb.root / ‘slices’ (the default) or if given, Merb::Plugins.config[:search_path] (String/Array)



58
59
60
61
62
63
64
# File 'lib/merb-slices/module.rb', line 58

def register_slices_from_search_path!
  slice_files_from_search_path.each do |slice_file|
    absolute_path = File.expand_path(slice_file)
    Merb.logger.info!("Found slice '#{File.basename(absolute_path, '.rb')}' in search path at #{absolute_path.relative_path_from(Merb.root)}")
    Merb::Slices::Loader.load_classes(absolute_path)
  end
end

.reload(slice_module) ⇒ Object

Reload a Slice at runtime

Parameters:

  • slice_module (#to_s)

    The Slice module to reload.



148
149
150
151
152
153
# File 'lib/merb-slices/module.rb', line 148

def reload(slice_module)
  if slice = self[slice_module]
    deactivate slice.name
    activate_by_file slice.file
  end
end

.reload_by_file(slice_file) ⇒ Object

Reload a Slice at runtime by specifying its slice file

Parameters:

  • slice_file (String)

    The Slice location of the slice init file to reload.



158
159
160
161
162
# File 'lib/merb-slices/module.rb', line 158

def reload_by_file(slice_file)
  if slice = self.slices.find { |s| s.file == slice_file }
    reload(slice.name)
  end
end

.slice_files_from_search_pathObject

Slice file locations from all search paths; this default to host-app/slices.

Look for any slices in those default locations or if given, Merb::Plugins.config[:search_path] (String/Array). Specify files, glob patterns or paths containing slices.



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/merb-slices/module.rb', line 268

def slice_files_from_search_path
  search_paths = Array(Merb::Plugins.config[:merb_slices][:search_path] || [Merb.root / "slices"])
  search_paths.inject([]) do |files, path|
    # handle both Pathname and String
    path = path.to_s
    if File.file?(path) && File.extname(path) == ".rb"
      files << path
    elsif path.include?("*")
      files += glob_search_path(path)
    elsif File.directory?(path)
      files += glob_search_path(path / "**/lib/*.rb")
    end
    files
  end
end

.slice_namesArray[String]

All registered Slice module names

Returns:

  • (Array[String])

    A sorted array of all slice module names.



216
217
218
# File 'lib/merb-slices/module.rb', line 216

def slice_names
  self.paths.keys.sort
end

.slicesArray[Module]

All registered Slice modules

Returns:

  • (Array[Module])

    A sorted array of all slice modules.



207
208
209
210
211
# File 'lib/merb-slices/module.rb', line 207

def slices
  slice_names.map do |name|
    Object.full_const_get(name) rescue nil
  end.compact
end

.start_dynamic_loader!(interval = nil) ⇒ Object

Watch all specified search paths to dynamically load/unload slices at runtime

If a valid slice is found it’s automatically registered and activated; once a slice is removed (or renamed to not match the convention), it will be unregistered and deactivated. Runs in a Thread.

Examples:

Merb::BootLoader.after_app_loads { Merb::Slices.start_dynamic_loader! }

Parameters:

  • interval (Numeric) (defaults to: nil)

    The interval in seconds of checking the search path(s) for changes.



174
175
176
# File 'lib/merb-slices/module.rb', line 174

def start_dynamic_loader!(interval = nil)
  DynamicLoader.start(interval)
end

.stop_dynamic_loader!Object

Stop watching search paths to dynamically load/unload slices at runtime



179
180
181
# File 'lib/merb-slices/module.rb', line 179

def stop_dynamic_loader!
  DynamicLoader.stop
end

.unregister(slice_module) ⇒ Object

Unregister a Slice at runtime

This clears the slice module from ObjectSpace and reloads the router. Since the router doesn’t add routes for any disabled slices this will correctly reflect the app’s routing state.

Parameters:

  • slice_module (#to_s)

    The Slice module to unregister.



73
74
75
76
77
78
79
80
81
82
# File 'lib/merb-slices/module.rb', line 73

def unregister(slice_module)
  if (slice = self[slice_module]) && self.paths.delete(module_name = slice.name)
    slice.loadable_files.each { |file| Merb::Slices::Loader.remove_classes_in_file file }
    Object.send(:remove_const, module_name)
    unless Object.const_defined?(module_name)
      Merb.logger.info!("Unregistered slice #{module_name}")
      Merb::Slices::Loader.reload_router!
    end
  end
end