How to share internal request configuration when creating auth classes via inheritance

5 views
Skip to first unread message

Bo Jeanes

unread,
May 27, 2024, 3:56:39 AMMay 27
to Rodauth
Hey Jeremy and others,

I am trying to refactor our quite large and unwieldy rodauth app with all configuration inline towards using auth classes. We have two configs: user and admin; much config is shared between the two.

I am looking at "Share configuration via inheritance" doc for inspiration. Currently, the approach is something like:

common_config = -> do
  enable :foo
  bar { 123 }
  # ...
end
common_internal_config = -> do
  reset_password_email_link do
    _set_internal_request_return_value(super())
  end
  # ...
end

configure(:user) do
  instance_exec(&common_config)
  # ...
  internal_request_configuration do
    instance_exec(&common_internal_config)
    # ...
  end  
end
configure(:admin) do
  instance_exec(&common_config)
  # ...
  internal_request_configuration do
    instance_exec(&common_internal_config)
    enable :create_account
    create_account_set_password? false
    # ...
  end
end

In refactoring towards explicit auth classes and inheritance, it seems that internal_request_configuration in the base class is not executed (note, I am using rodauth-rails here, but Rodauth::Rails::Auth is a pretty faithful subclass of Rodauth::Auth):

class RodauthCommon < Rodauth::Rails::Auth
  configure do
    # ...

    internal_request_configuration do
      puts "==== common ==="
    end
  end
end

class RodauthUser < RodauthCommon
  configure do
    # ...

    internal_request_configuration do
      puts "==== user ==="
    end
  end
end

class RodauthAdmin < RodauthCommon
  configure do
    # ...

    internal_request_configuration do
      puts "==== admin ==="
    end
  end
end

When loading these configurations, I'll only see "=== user ===" and "=== admin ===" output, but no reference to the common.

I arrived at this crude debugging approach while pulling my hair out with sweeping spec failures and concluded it's because a critical before_rodauth hook in my common internal config is never being defined or called.

Is there a way to make this work in a way that internal config can be inherited too? Right now, I think my only option is to create some dedicated "features" that I enable in each concrete class and have those features execute common definitions.

Would that be your recommendation?

Cheers,
Bo

Jeremy Evans

unread,
May 27, 2024, 12:00:17 PMMay 27
to rod...@googlegroups.com
This is looks rodauth-rails specific, so hopefully Janko can answer.  My guess would be that internal_request_configuration is not called for RodauthCommon because nothing is instantiating an instance of it, as it is an abstract base class, and the two subclasses don't have the equivalent of super to call into it.  However, that's not an educated guess.

Thanks,
Jeremy

Bo Jeanes

unread,
May 27, 2024, 7:35:31 PMMay 27
to Rodauth
Hey Jeremy,

Thanks for the response. 

Why do you believe it is rodauth-rails specific? I am keen to see what Janko has to say. I initially was going to raise this as an issue in rodauth-rails but looking through its source code, it doesn't even contain the string internal_request_configuration, let alone change the behaviour of how auth classes work (that I can see). The Rodauth::Rails::Auth is a pretty basic subclass of Rodauth::Auth which merely does some light configuration (such as enable :rails feature).

I acknowledge I could be off the mark here, but it seems like this is due to the way the internal_request feature works, by setting ivars on the class to store the config and then executing them during post_configure in an internal request. If the configuration blocks were stored in a way that was inherited (which is complicated and we can't/shouldn't assume Rails' class_attribute is available).

Cheers,
Bo

Jeremy Evans

unread,
May 27, 2024, 9:04:08 PMMay 27
to rod...@googlegroups.com
On Mon, May 27, 2024 at 4:35 PM Bo Jeanes <m...@bjeanes.com> wrote:
Why do you believe it is rodauth-rails specific?

Your example used rodauth-rails.  If you think this is a problem in Rodauth itself, can you please put together a self contained reproducible example so I can investigate?
 
I am keen to see what Janko has to say. I initially was going to raise this as an issue in rodauth-rails but looking through its source code, it doesn't even contain the string internal_request_configuration, let alone change the behaviour of how auth classes work (that I can see). The Rodauth::Rails::Auth is a pretty basic subclass of Rodauth::Auth which merely does some light configuration (such as enable :rails feature).

I acknowledge I could be off the mark here, but it seems like this is due to the way the internal_request feature works, by setting ivars on the class to store the config and then executing them during post_configure in an internal request. If the configuration blocks were stored in a way that was inherited (which is complicated and we can't/shouldn't assume Rails' class_attribute is available).

In standard Rodauth, adding the Rodauth plugin to the Roda app calls post_configure before returning, so you shouldn't be able to get to a situation where the internal request configuration hasn't been called.  So even if this problem is not internal to rodauth-rails, it may be due to how rodauth-rails is using Rodauth in a way different from how Rodauth is used by default.

Thanks,
Jeremy

Bo Jeanes

unread,
May 27, 2024, 10:05:53 PMMay 27
to Rodauth
Hi Jeremy,

I tweaked a test case in Rodauth to demonstrate that this is not a rodauth-rails specific issue.

I opened a PR against my own fork to trigger a CI build. As you should be able to see, internal request configurations defined in a base class is not inherited or even called.

Cheers,
Bo

Jeremy Evans

unread,
May 28, 2024, 10:42:24 AMMay 28
to rod...@googlegroups.com
On Mon, May 27, 2024 at 7:05 PM Bo Jeanes <m...@bjeanes.com> wrote:
Hi Jeremy,

I tweaked a test case in Rodauth to demonstrate that this is not a rodauth-rails specific issue.

I opened a PR against my own fork to trigger a CI build. As you should be able to see, internal request configurations defined in a base class is not inherited or even called.

Thanks, I'll look into this issue today.

Jeremy 

Jeremy Evans

unread,
May 28, 2024, 12:16:42 PMMay 28
to rod...@googlegroups.com
I looked into this.  It isn't difficult to fix by looking into superclasses:

 diff --git a/lib/rodauth/features/internal_request.rb b/lib/rodauth/features/internal_request.rb
index 85745b9..2b51a99 100644
--- a/lib/rodauth/features/internal_request.rb
+++ b/lib/rodauth/features/internal_request.rb
@@ -384,16 +384,26 @@ module Rodauth

       return if is_a?(InternalRequestMethods)

+      superklasses = []
+      superklass = self.class
+      until superklass == Rodauth::Auth
+        superklasses << superklass
+        superklass = superklass.superclass
+      end
+
       klass = self.class
       internal_class = Class.new(klass)
       internal_class.instance_variable_set(:@configuration_name, klass.configuration_name)
+      configuration = internal_class.configuration

-      if blocks = klass.instance_variable_get(:@internal_request_configuration_blocks)
-        configuration = internal_class.configuration
-        blocks.each do |block|
-          configuration.instance_exec(&block)
+      superklasses.reverse_each do |superklass|
+        if blocks = superklass.instance_variable_get(:@internal_request_configuration_blocks)
+          blocks.each do |block|
+            configuration.instance_exec(&block)
+          end
         end
       end

However, be aware that this doesn't result in passing the spec in your pull request, because for auth1, the internal request configuration from the superclass overrides the regular configuration from the subclass.  Internal request classes are always subclasses of the current class, and not subclasses of an internal request class for a superclass.  The internal request creation will now process all internal request configuration blocks in serial starting with the first superclass block.  So use of super inside internal request methods will not call the implementation in the superclass's internal request class.

Thanks,
Jeremy

Bo Jeanes

unread,
May 30, 2024, 11:05:09 PMMay 30
to rod...@googlegroups.com
Hi Jeremy,

Thanks for this. I see you also committed the change and released a new version. Much appreciated!

As to it not passing my test, that makes sense. I actually meant to have the `auth1` subclass re-configure `login_page_title` within `internal_request_configuration`, but made an error when preparing the commit.

The change you’ve made has the behaviour I was expecting to find, I believe.

Thanks again for the responsiveness. 

All the best,
Bo

--
You received this message because you are subscribed to a topic in the Google Groups "Rodauth" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/rodauth/GJ3SHmw7Htg/unsubscribe.
To unsubscribe from this group and all its topics, send an email to rodauth+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/rodauth/CADGZSScy1BxvL7fY_4D42eVUNgCAE68FXCdM1pmSRwyPu6%3D91Q%40mail.gmail.com.

Reply all
Reply to author
Forward
0 new messages