Dancer2 icon indicating copy to clipboard operation
Dancer2 copied to clipboard

Error use()ing a Dancer2 package in a cron script then calling template()

Open d5ve opened this issue 2 years ago • 2 comments

I have a Dancer2 app with the route handler functions spread around various packages. These packages all have a stanza of use Dancer2 appname => 'App::Totally::Awesome'; which imports the dancer2 DSL, and uses the same runner for all the different packages.

I'd like to use() some of those packages in a cron scripts which sends emails. The email bodies are generated by calls to the dancer2 DSL's template()

If I use App::Totally::Awesome::HighFive; in the cron script, then calling template() in the script gives the error below:

Hook error: Can't call method "var" on an undefined value at /data/perlbrew/perls/perl-5.26.0/lib/site_perl/5.26.0/Dancer2/Plugin/Deferred.pm line 108.
 at /data/perlbrew/perls/perl-5.26.0/lib/site_perl/5.26.0/Dancer2/Plugin.pm line 597.

This error is where a function in Deferred.pm is calling $plugin->app->request->var() and request() is undefined.

This error also happens if the cron script itself has a use Dancer2 appname => 'App::Totally::Awesome'; stanza.

However, the cron script, and the call to template() work if the script has a bare use Dancer2;, where no appname is specified (so I think the runner is called main). But only as long as the script doesn't include any of the handler packages.

I'd like the script to be able to use() some packages from the app, as they contain useful business logic that I don't want to duplicate in the script itself.

How can I do this?

Cheers.

d5ve avatar Feb 16 '22 01:02 d5ve

I guess it comes down to it working OK, as long as the cron script only use()s packages which don't use Dancer2::Plugin::Deferred; either directly or indirectly.

d5ve avatar Feb 17 '22 04:02 d5ve

Actually it is a good idea to separate the business logic from the Dancer2 app itself IMHO. In my projects the business logic is usually covered by a DBIx::Class schema and other modules/role outside of the Dancer2 app.

Aside from that which plugins do you use in your app?

racke avatar Feb 17 '22 13:02 racke

@d5ve seconding what @racke is saying here. You'd be better served by putting code that is used in Dancer2 and cron scrips in some sort of model layer, be it DBIx::Class objects, or one or Moo(se) objects that encapsulate that object. Not only is the code more usable in more situations, but it is significantly easier to test.

Does cron need to output HTML, or data, or neither?

cromedome avatar Jul 26 '23 02:07 cromedome

Thanks for the suggestions, and sorry for the delay responding.

The cron scripts in question generate and send HTML emails based on various events in the database. For example emailing a website admin a summary view of what's coming up for them next week.

I am useing Dancer2 in the scripts for a few reasons: it gives access to the Dancer2 DSL like template(), schema(), and email(); it reads from the same config file as the webserver, so there is only one place to change DB connection strings, SMTP config, and other settings; it allows easy reuse of existing website business logic, template and partial templates to generate the HTML email contents.

I know I can do all the above "manually", but Dancer2's lovely DSL has made me lazy and contented. I've also eyed up having the cron script make actual HTTP requests to the webserver itself to generate the email message HTML, however then I'd need to worry about security/cookies/sessions etc.

d5ve avatar Jul 26 '23 03:07 d5ve

Thanks for explaining your use case better. I understand your "why" better now, but it's not a use that we necessarily constructed Dancer2 for.

That being said, we might be able to help you navigate some of that with some adjustments to your app. I do not, unfortunately, have the tuits for it right now. I'm leaving this open in case others would like to chime in, and as a reminder to myself to come back to this.

Thanks for being such a fan, and for wanting to use Dancer2 in All The Things :)

cromedome avatar Jul 31 '23 01:07 cromedome

I feel dirty even suggesting this, however:

You could look at something like Module::Runtime to conditionally load Dancer2::Plugin::Deferred when run from the web, and not from cron. We have an example of it in our cookbook (though not exactly the way you'd use it), or look at the docs for Module::Runtime.

I do not officially endorse this solution. It may not even work. But it's a possible route to explore.

Good luck!

cromedome avatar Aug 11 '23 01:08 cromedome

Love it, thanks @cromedome!

I was wondering about something even more hacky - monkey-patching a dummy Request object in there somehow. With (digging myself even deeper into a hole) a bit of autoload magic to have it not fall over when random methods are called.

This question is well and truly answered, thanks both, and all further madness will be confined to my codebases.

d5ve avatar Aug 30 '23 06:08 d5ve