module Ratonvirus::Support::Backend
The backend implementation allows us to set different backends on the main Ratonvirus
configuration, e.g. scanner and storage backends. This makes the library agnostic of the actual implementation of these both and allows the developer to configure
The solution is a bit hacky monkey patch type of solution as it adds code to the underlying implementation through class_eval. The reason for this is to define arbitrary getter, setter and destroye methods that are nicer to use for the user. Wrapping this functionality to its own module makes the resulting code less prone to errors as all of the backends are defined exactly the same way.
Modifying this may be tough, so be sure to test properly in case you make any modifications.
Public Instance Methods
First argument “backend_cls”:
The subclass that refers to the backend's namespace, e.g. `"Scanner"`.
Second argument “backend_type”:
The backend type in the given namespace, e.g. `:eicar`
The returned result will be e.g.
Ratonvirus::Scanner::Eicar Ratonvirus::Storage::ActiveStorage
# File lib/ratonvirus/support/backend.rb, line 30 def backend_class(backend_cls, backend_type) return backend_type if backend_type.is_a?(Class) subclass = ActiveSupport::Inflector.camelize(backend_type.to_s) ActiveSupport::Inflector.constantize( "#{name}::#{backend_cls}::#{subclass}" ) end
Private Instance Methods
Defines the “backend” methods.
For example, this:
define_backend :foo, 'Foo'
Would define the following methods:
# Getter for foo def self.foo @foo ||= create_foo end # Setter for foo def self.foo=(foo_type) set_backend( :foo, 'Foo', foo_type ) end # Destroys the currently active foo. # The foo is re-initialized when the getter is called. def self.destroy_foo @foo = nil end private def self.create_foo if @foo_defs.nil? raise NotDefinedError.new("Foo not defined!") end @foo_defs[:klass].new(@foo_defs[:config]) end
Usage (getter):
Ratonvirus.foo
Usage (setter):
Ratonvirus.foo = :bar Ratonvirus.foo = :bar, {option: 'value'} Ratonvirus.foo = Ratonvirus::Foo::Bar.new Ratonvirus.foo = Ratonvirus::Foo::Bar.new({option: 'value'})
Usage (destroyer):
Ratonvirus.destroy_foo
# File lib/ratonvirus/support/backend.rb, line 88 def define_backend(backend_type, backend_subclass) class_eval <<-CODE, __FILE__, __LINE__ + 1 # Getter for #{backend_type} def self.#{backend_type} # def self.foo @#{backend_type} ||= create_#{backend_type} # @foo ||= create_foo end # end # Setter for #{backend_type} def self.#{backend_type}=(#{backend_type}_value) # def self.foo=(foo_value) set_backend( # set_backend( :#{backend_type}, # :foo "#{backend_subclass}", # "Foo" #{backend_type}_value # foo_value ) # ) end # end # Destroys the currently active #{backend_type}. # The #{backend_type} is re-initialized when the getter is called. def self.destroy_#{backend_type} # def self.destroy_foo @#{backend_type} = nil # @foo = nil end # end # Creates a new backend instance # private def self.create_#{backend_type} # def self.create_foo if @#{backend_type}_defs.nil? # if @foo_defs.nil? raise NotDefinedError.new("#{backend_subclass} not defined!") # raise NotDefinedError.new("Foo not defined") end # end # @#{backend_type}_defs[:klass].new( # @foo_defs[:klass].new( @#{backend_type}_defs[:config] # @foo_defs[:config] ) # ) end # end private_class_method :create_#{backend_type} # private_class_method :create_foo CODE end
Sets the backend to local variables for the backend initialization. The goal of this method is to get the following configuration set to local `@x_defs` variable, where 'x' is the type of backend.
For example, for a backend with type “scanner”, this would be @scanner_defs.
The first argument, “backend_type” is the type of backend we are configuring, e.g. `:scanner`.
The second argument “backend_cls” is the backend subclass that is used in the module's namespace, e.g. “Scanner”. This would refer to subclasses `Ratonvirus::Scanner::…`.
The third argument “backend_value” is the actual value the user provided for the setter method, e.g. `:eicar` or `Ratonvirus::Scanner::Eicar.new`. The user may also provide a second argument to the setter method e.g. like `Ratonvirus.scanner = :eicar, {conf: 'option'}`, in which case these both arguments are provided in this argument as an array.
# File lib/ratonvirus/support/backend.rb, line 145 def set_backend(backend_type, backend_cls, backend_value) base_class = backend_class(backend_cls, "Base") if backend_value.is_a?(base_class) # Set the instance instance_variable_set(:"@#{backend_type}", backend_value) # Store the class (type) and config for storing them below to local # variable in case it needs to be re-initialized at some point. subtype = backend_value.class config = backend_value.config else case backend_value when Array subtype = backend_value.shift config = backend_value.shift || {} raise InvalidError, "Invalid #{backend_type} type: #{subtype}" unless subtype.is_a?(Symbol) when Symbol subtype = backend_value config = {} else raise InvalidError, "Invalid #{backend_type} provided!" end # Destroy the current one send(:"destroy_#{backend_type}") end instance_variable_set( :"@#{backend_type}_defs", klass: backend_class(backend_cls, subtype), config: config ) end