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, Support Classes: Activate, DynamicLoader, Initialize, Loader

Constant Summary collapse

VERSION =
"0.9.8"

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.



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

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.



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.



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

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)


225
226
227
228
# File 'lib/merb-slices/module.rb', line 225

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



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.



242
243
244
# File 'lib/merb-slices/module.rb', line 242

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.



187
188
189
# File 'lib/merb-slices/module.rb', line 187

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.



234
235
236
# File 'lib/merb-slices/module.rb', line 234

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.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)



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.



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

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.



218
219
220
# File 'lib/merb-slices/module.rb', line 218

def slice_names
  self.paths.keys.sort
end

.slicesArray[Module]

All registered Slice modules

Returns:

  • (Array[Module])

    A sorted array of all slice modules.



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

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