Class: Avo::PluginManager

Inherits:
Object
  • Object
show all
Defined in:
lib/avo/plugin_manager.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializePluginManager

Returns a new instance of PluginManager.



8
9
10
11
12
13
# File 'lib/avo/plugin_manager.rb', line 8

def initialize
  @plugins = []
  @engines = []
  @building_plugins = []
  @building_engines = []
end

Instance Attribute Details

#enginesObject (readonly)

Returns the value of attribute engines.



4
5
6
# File 'lib/avo/plugin_manager.rb', line 4

def engines
  @engines
end

#pluginsObject (readonly) Also known as: all

Returns the value of attribute plugins.



3
4
5
# File 'lib/avo/plugin_manager.rb', line 3

def plugins
  @plugins
end

Instance Method Details

#as_json(*arg) ⇒ Object



128
129
130
131
132
133
134
135
# File 'lib/avo/plugin_manager.rb', line 128

def as_json(*arg)
  plugins.map do |plugin|
    {
      klass: plugin.to_s,
      priority: plugin.priority,
    }
  end
end

#begin_reloadObject

Starts a re-registration cycle without touching the currently published @plugins/@engines, so concurrent readers (e.g. mount_avo's route-drawing loop) keep seeing the complete pre-reload list until #commit_reload publishes the new one. Called by Avo.boot inside its Mutex, so only one reload is ever building at a time.



20
21
22
23
# File 'lib/avo/plugin_manager.rb', line 20

def begin_reload
  @building_plugins = []
  @building_engines = []
end

#commit_reloadObject

Publishes the registrations collected since #begin_reload. Each of the two reassignments below is individually an atomic pointer swap, so a reader of .engines alone (or .plugins alone) always sees the complete old list or the complete new one, never a partially rebuilt one. The two fields are not published jointly -- a reader combining both in one operation could observe them from different reload generations. No current reader does that (mount_avo reads only .engines; installed? reads only .plugins).



33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/avo/plugin_manager.rb', line 33

def commit_reload
  @plugins = @building_plugins
  @engines = @building_engines
  # Reset to fresh arrays rather than nil: register/mount_engine must
  # stay callable even outside a begin_reload/commit_reload window --
  # e.g. avo-permissions calls Avo.plugin_manager.register at Rails
  # initializer time, before Avo.boot ever runs. Such a call is simply
  # discarded by the next #begin_reload rather than raising, matching
  # the pre-atomic-publish behavior where register/mount_engine never
  # crashed regardless of timing.
  @building_plugins = []
  @building_engines = []
end

#installed?(name) ⇒ Boolean

Returns:

  • (Boolean)


145
146
147
148
149
# File 'lib/avo/plugin_manager.rb', line 145

def installed?(name)
  plugins.any? do |plugin|
    plugin.name.to_s == name.to_s
  end
end

#mount_engine(klass, **options) ⇒ Object



151
152
153
154
155
156
157
# File 'lib/avo/plugin_manager.rb', line 151

def mount_engine(klass, **options)
  # Dedup by class so a plugin hook that (by bug) calls mount_engine
  # twice for the same engine within one boot can't leave a duplicate
  # entry for #commit_reload to publish.
  @building_engines.delete_if { |engine| engine[:klass] == klass }
  @building_engines << {klass:, options:}
end

#register(name, priority: 10) ⇒ Object



47
48
49
50
51
52
53
54
# File 'lib/avo/plugin_manager.rb', line 47

def register(name, priority: 10)
  # Capture the file that called `register` so the plugin can later resolve
  # the gem it actually ships in. Plugins register under nicknames
  # (e.g. `:rhino` for `avo-rhino_field`), so the name alone isn't enough.
  registered_from = caller_locations(1, 1)&.first&.path

  @building_plugins << Plugin.new(name:, priority: priority, registered_from: registered_from)
end

#register_configuration(name, klass) ⇒ Object

Register a plugin's configuration namespace on Avo::Configuration so host apps configure the plugin from config/initializers/avo.rb:

Avo.plugin_manager.register_configuration :intelligence, Avo::Intelligence::Configuration

Avo.configure do |config|
config.intelligence.thinking_effort = "medium"
end

Call it from an engine initializer ordered before: :load_config_initializers (or at require time) — the host's avo.rb reads the accessor, so it must be defined before config/initializers run. An :avo_boot hook is too late. Instances are memoized per Configuration object, so replacing Avo.configuration resets plugin configs along with everything else.



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/avo/plugin_manager.rb', line 96

def register_configuration(name, klass)
  name = name.to_sym
  @configuration_namespaces ||= {}

  # Guard against a plugin silently shadowing a core option or another
  # plugin's namespace. Re-registering the same namespace with the same
  # class is fine — boot runs more than once.
  existing = @configuration_namespaces[name]
  if existing.nil? && Avo::Configuration.method_defined?(name)
    raise ArgumentError, "Avo::Configuration already defines ##{name}; pick a different configuration namespace."
  end
  if !existing.nil? && existing != klass
    raise ArgumentError, "Configuration namespace :#{name} is already registered with #{existing}; pick a different namespace."
  end

  @configuration_namespaces[name] = klass

  Avo::Configuration.define_method(name) do
    (@plugin_configurations ||= {})[name] ||= klass.new
  end

  Avo::Configuration.define_method(:"#{name}=") do |value|
    (@plugin_configurations ||= {})[name] = value
  end
end

#register_field(method_name, klass) ⇒ Object



60
61
62
63
64
65
66
67
68
# File 'lib/avo/plugin_manager.rb', line 60

def register_field(method_name, klass)
  # Avo.boot method is executed multiple times.
  # During the first run, it correctly loads descendants of Avo::Fields::Base.
  # Plugins are then loaded, introducing additional descendants to Avo::Fields::Base.
  # On subsequent runs, Avo::Fields::Base descendants now include these plugin fields.
  # This field_name_attribute assign forces the field name to retain the registered name instead of being computed dynamically from the field class.
  klass.field_name_attribute = method_name
  Avo.field_manager.load_field method_name, klass
end

#register_menu_item(name, &block) ⇒ Object

Register a custom menu DSL method (e.g. form) contributed by a plugin, so it can be used inside config.main_menu and friends. Delegates to avo-menu's builder when it's installed; a no-op otherwise, so plugins can register unconditionally. The block is evaluated in the menu builder's context, so it can call link, resource, etc. See Avo::Menu::Builder.register_item.



76
77
78
79
80
# File 'lib/avo/plugin_manager.rb', line 76

def register_menu_item(name, &block)
  return unless defined?(Avo::Menu::Builder)

  Avo::Menu::Builder.register_item(name, &block)
end

#register_resource_toolObject



122
123
# File 'lib/avo/plugin_manager.rb', line 122

def register_resource_tool
end

#register_toolObject



125
126
# File 'lib/avo/plugin_manager.rb', line 125

def register_tool
end

#register_view_type(name, component:, icon:, active_icon:, translation_key: nil) ⇒ Object



56
57
58
# File 'lib/avo/plugin_manager.rb', line 56

def register_view_type(name, component:, icon:, active_icon:, translation_key: nil)
  Avo.view_type_manager.register(name, component:, icon:, active_icon:, translation_key:)
end

#to_sObject



137
138
139
140
141
142
143
# File 'lib/avo/plugin_manager.rb', line 137

def to_s
  plugins.map do |plugin|
    plugin.to_s
  end.join(",")
rescue
  "Failed to fetch plugins."
end