Module: Merb::Slices

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

Defined Under Namespace

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

Constant Summary collapse

VERSION =
"0.9.4"

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.



12
13
14
# File 'lib/merb-slices/module.rb', line 12

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.



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

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’



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

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.



188
189
190
191
192
193
# File 'lib/merb-slices/module.rb', line 188

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

.deactivate(slice_module) ⇒ Object

Deactivate a Slice module at runtime

Parameters:

  • slice_module (#to_s)

    The Slice module to unregister.



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

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.



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

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.



244
245
246
247
248
249
250
251
# File 'lib/merb-slices/module.rb', line 244

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)


214
215
216
# File 'lib/merb-slices/module.rb', line 214

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

.filename2module(slice_file) ⇒ Object

Helper method to transform a slice filename to a module Symbol



17
18
19
# File 'lib/merb-slices/module.rb', line 17

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.



230
231
232
# File 'lib/merb-slices/module.rb', line 230

def files
  @files ||= {}
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.



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

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.



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

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.info!("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)



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

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.



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

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.



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

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.



258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
# File 'lib/merb-slices/module.rb', line 258

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.



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

def slice_names
  self.paths.keys.sort
end

.slicesArray[Module]

All registered Slice modules

Returns:

  • (Array[Module])

    A sorted array of all slice modules.



198
199
200
201
202
# File 'lib/merb-slices/module.rb', line 198

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.



176
177
178
# File 'lib/merb-slices/module.rb', line 176

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



181
182
183
# File 'lib/merb-slices/module.rb', line 181

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.



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

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_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