= Rodauth Internals

Rodauth's implementation heavily uses metaprogramming in order to DRY up the codebase, which can be a little intimidating to developers who are not familiar with the codebase.  This guide explains how Rodauth is built, which should make the internals easier to understand.

== Object Model

First, let's talk about the basic parts of Rodauth.

=== Rodauth::Auth

Rodauth::Auth is the core of rodauth.  If a user calls +rodauth+ inside their Roda application, they get a Rodauth::Auth subclass instance.  Rodauth's configuration DSL is designed to build a Rodauth::Auth subclass appropriate to the application, by loading only the features that are needed, and overriding defaults as appropriate.

=== Rodauth::Configuration

Inside the block you pass to <tt>plugin :rodauth</tt>, +self+ is an instance of this class.  This class is mostly empty, as most of Rodauth is implemented as separate features, and the configuration for each feature is loaded as a separate module into this instance.

=== Rodauth::Feature

Each of the parts of rodauth that you can use is going to be a separate feature.  Rodauth::Feature is a Module subclass, and every rodauth feature you load is an instance of this class, which is included in the Rodauth::Auth subclass used by the Roda application.  Rodauth::Feature has many methods designed to make building Rodauth features easier by defining methods in the Rodauth::Feature instance.

=== Rodauth::FeatureConfiguration

Just as each feature is a module included in the Rodauth::Auth subclass for the application, each feature also contains a configuration module that is an instance of Rodauth::FeatureConfiguration (also a module subclass).  For each feature you load into the Rodauth configuration, the Rodauth::Configuration instance is extended with the feature's Rodauth::FeatureConfiguration instance, which is what makes the feature's configuration methods available inside the <tt>plugin :rodauth</tt> block.  This is why you need to enable the features in Rodauth before configuring them.


== Object Model Example

Here's some commented output hopefully showing the relation between the different parts

   Roda.plugin :rodauth do
     self                       # => #<Rodauth::Configuration> (instance)
     auth                       # => Rodauth::Auth subclass

     singleton_class.ancestors  # => [#<Class:#<Rodauth::Configuration>> (singleton class of self),
                                #     Rodauth::FeatureConfiguration::Base (instance of Rodauth::FeatureConfiguration),
                                #     Rodauth::Configuration,
                                #     ...]
     auth.ancestors             # => [Rodauth::Auth subclass,
                                #     Rodauth::Base (instance of Rodauth::Feature),
                                #     Rodauth::Auth,
                                #     ...]

     enable :login

     singleton_class.ancestors  # => [#<Class:#<Rodauth::Configuration>> (singleton class of self),
                                #     Rodauth::FeatureConfiguration::Login (instance of Rodauth::FeatureConfiguration),
                                #     Rodauth::FeatureConfiguration::Base (instance of Rodauth::FeatureConfiguration),
                                #     Rodauth::Configuration,
                                #     ...]
     auth.ancestors             # => [Rodauth::Auth subclass,
                                #     Rodauth::Login (instance of Rodauth::Feature),
                                #     Rodauth::Base (instance of Rodauth::Feature),
                                #     Rodauth::Auth,
                                #     ...]
   end

   Roda.rodauth                 # => Rodauth::Auth subclass
   Roda.rodauth.ancestors       # => [Rodauth::Auth subclass,
                                #     Rodauth::Login (instance of Rodauth::Feature),
                                #     Rodauth::Base (instance of Rodauth::Feature),
                                #     Rodauth::Auth,
                                #     ...]

   Roda.route do |r|
     rodauth                    # => Rodauth::Auth subclass instance
   end

== Feature Creation Example

Here's a heavily commented example showing what is going on inside a Rodauth feature.

  module Rodauth
    # Feature.define takes a symbol, specifying the name of the feature. This
    # is the same symbol you would pass to enable when loading the feature into
    # the Rodauth configuration.  Feature is a module subclass, and Feature.define
    # is a class method that creates an instance of Feature (a module) and executes
    # the block in the context of the Feature instance.
    #
    # The second argument is optional, and sets the Feature instance and related
    # FeatureConfiguration instance to a constant in the Rodauth namespace, which
    # makes it easier to locate via inspect.
    Feature.define(:foo, :Foo) do
      # Inside this block, self is an instance of Feature.  As this instance of
      # Feature will be included in the Rodauth::Auth subclass instance if
      # the feature is loaded into the rodauth configuration, methods you define
      # in this block (via def or define_method) will be callable on any
      # rodauth object if this feature is loaded into the rodauth configuration.

      # Feature has many instance methods that define methods in the Feature
      # instance.  This is one of those methods, which sets the text of the notice
      # flash, shown after successful submission of the form. It's basically
      # equivalent to executing this code in the feature:
      #
      #   def foo_notice_flash
      #     "It worked!"
      #   end
      #
      # while also adding a method to the configuration which does:
      #
      #   def foo_notice_flash(v=nil, &block)
      #     block ||= proc{v}
      #     @auth.class_eval do
      #       define_method(:foo_notice_flash, &block)
      #     end
      #   end
      #
      # This is what easily allows you to modify any part of Rodauth during
      # configuration.  The Rodauth::Auth subclass has the default behavior
      # added via a method in an included module (the Feature instance), and the
      # Rodauth::Configuration instance has a method that when called defines
      # a method in the Rodauth::Auth subclass itself, which will take precedence
      # over the default method, which defined in the included Feature instance.
      notice_flash "It worked!"

      # The rest of these method calls are fairly similar to notice_flash.
      # This defines the foo_error_flash method, for the error flash message to
      # show if the form submission wasn't successful.
      error_flash "There was an error"

      # This defines the foo_view method to use template 'foo.str' in the templates
      # folder, and set the title of the page to 'Foo'.
      view 'foo', 'Foo'

      # This defines the foo_additional_form_tags method, which would generally be called
      # inside the foo.str template.
      additional_form_tags

      # This defines the foo_button method, for the text to use on the submit button
      # for the form in foo.str.
      button 'Submit'

      # This defines the foo_redirect method, for where to redirect after successful submission
      # of the form.
      redirect

      # This defines the before_foo method, called before performing the foo action.
      before

      # This defines the after_foo method, called after successfully performing the foo action.
      after

      # This defines a loaded_templates method that calls super and adds 'foo' as one of the
      # templates.  This is necessary for precompilation of templates to work.
      loaded_templates ['foo']

      # This defines the following methods related to sending email:
      #
      # * foo_email_subject: uses given subject
      # * foo_email_body: renders foo-email template
      # * create_foo_email: creates Mail::Message using subject and body
      # * send_foo_email: sends created email
      #
      # The foo-email template should be included in the loaded_templates call to make sure
      # template precompilation works.
      email :foo, 'Foo Subject'

      # auth_value_method is a generic method that takes two arguments, a method to define
      # and a default value.  It is similar to the methods above, except that it allows
      # arbitrary method names.  The notice_flash, error_flash, button, and additional_form_tags
      # methods are actually defined in terms of this method.
      #
      # So this particular method defines a foo_error_status method that will return 401 by
      # default, but also adds a configuration method that allows you to override the default.
      auth_value_method :foo_error_status, 401

      # This is similar to auth_value_method, but it only adds the configuration method.
      # Using this should only be done if you have defining the method in the feature
      # separately (see below).
      auth_value_methods :foo_bar

      # This is similar to auth_value_methods, but it changes the configuration method so that
      # a block is required and you cannot provide an argument.  This is used for the cases
      # where a statically defined value would never make sense, such as when any correct
      # behavior would depend on accessing request-specific information.
      auth_methods :foo

      # Record the instance variables that the feature sets (either directly or through
      # attr_writer/attr_accessor/cached_auth_method. Values should be instance variable
      # symbols. This is used by Rodauth to ensure created Rodauth::Auth instances are
      # shape-friendly.
      uses_instance_variables(:@foo, :@bar)

      # route defines a route used for the feature.  This is the code that will be executed
      # if a user goes to /foo in the Roda app.
      route do |r|
        # Inside the block, you are in the context of the Rodauth::Auth subclass instance.
        # r is the Roda::RodaRequest subclass instance, just as it would be for a Roda
        # route block.

        # route adds a before_foo_route method that by default does nothing. It also
        # adds a configuration method that you can call to set behavior that will be
        # executed before routing.
        before_foo_route

        # Just like in Roda, r.get is called for GET requests
        r.get do
          # This will render a view to the user, using the foo.erb template from the
          # templates directory (unless the user has overridden it), inside the Roda
          # application's layout.
          foo_view
        end

        # Just like in Roda, r.post is called for POST requests
        r.post do
          # This is called before performing the foo action
          before_foo

          # This assumes foo returns false or nil on failure, or otherwise on
          # success.
          if foo 
            # In general, Rodauth only calls after_foo if foo is successful.
            after_foo

            # Successful form submission will usually set the notice flash,
            # the redirect to the appropriate page.
            set_notice_flash foo_notice_flash
            redirect foo_redirect
          else
            # Unsuccessful form subsmission will usually set the error flash,
            # the redisplay the page so that the submission can be fixed.
            set_error_flash foo_error_flash
            foo_view
          end
        end
      end

      # This is the default behavior for the foo method, if a user doesn't
      # call the foo method inside the configuration block.
      def foo
        # Do Something
      end

      # This is the default behavior for the foo_bar method, if a user doesn't
      # call the foo_bar method inside the configuration block.
      def foo_bar
        42
      end
    end
  end
