Catalyst - How to skip rendering a view - perl

In one of my controllers, I'm doing some SSE async streaming (see here), and I have it working great in a barebones test project. In the test project, I don't have a default view set, so it seems to just pass through - perfect!
Now I'm trying to put it into my existing larger project, however, I'm finding that forwarding it to any view messes it up and I can't figure out how to simply skip the rendering of a view. Because I have a default view now, it refuses to just pass through.
I've blindly tried a few things: $c->detach, $c->forward(undef), overriding the "end" method. None have succeeded in skipping the view rendering - it always passes it on to my default view.
Any ideas?
Edit
Not super relevant, but the action in question:
sub time_server : Path('/events') {
my ( $self, $c ) = #_;
$c->res->content_type('text/event-stream');
$timer_model->( $c, $c->response->write_fh );
}

Catalyst::Action::Renderview has a small set of criteria it uses when deciding whether or not to call the view. It will skip running the view if:
The request was a HEAD request.
Something has already set $c->response->body to a defined value.
$c->response->status is set to 204 ("No Content") or any 3xx (redirection).
$c->error contains one or more errors and $c->stash->{template} hasn't been set (so that finalize_error can do its job instead).
Honestly this isn't the best possible arrangement, but what I would try in your situation is setting $c->res->body(""); in your time_server action. An empty body won't write anything, and your headers are already finalized since you've called write_fh, but an empty string is still defined so it'll keep RenderView from doing anything.

This is my solution to force the function in default Catalyst controller response JSON type. You should change the content type to stream.
I hope it can help you.
sub my_function{
my ( $self, $c ) = #_;
my $payload = {
"yourkey" => "backtooldschool",
"yourkey2" => "2020"
};
my $result = encode_json($payload);
$c->response->body($result);
$c->response->content_type('application/json');
$c->response->status(200);
}

Related

How do I update a custom Bugzilla field in a custom Bugzilla extension using Perl?

I have a custom field in Bugzilla that I need to update after making a JSON call to another server after a bug has been updated. I am able to make the call and get the response back, but attempting to update the bug is failing.
I have tried setting the field in the hooks bug_end_of_update and object_end_of_set_all and it hasn't worked at all. If I attempt to do it in bug_end_of_update, the object itself gets updated in memory, but it will never get set in the database. Calling update on the $bug object in that method sends Bugzilla into an infinite loop that requires a complete restart to fix. In the code below, I am able to update the assigned_to field correctly. Using the same exact call doesn't work for a custom field though.
sub object_end_of_set_all {
my ($self, $args) = #_;
my $object = $args->{'object'};
if ($object->isa('Bugzilla::Bug')) {
$object->{'assigned_to'} = $object->{'reporter_id'}; #this works
$object->{'cf_custom_field'} = 'hello world'; #this doesn't
my $blessedField = {cf_custom_field};
bless $blessedField;
$object->set($blessedField, 'hello world'); #also doesn't work
$object->update; #puts bugzilla into an infinite loop that never returns
}
}
I would expect setting a custom field would work exactly like assigned_to, but it doesn't and the documentation on this is extremely lacking.

In a Bugzilla extension, how does one detect a new comment being added?

Which hook does one need to hook into to determine when a new comment is being added to a bug?
My use case is that whenever a comment is being added I need to process all comments, and perform some action based on that. However, this action is “expensive”, so I don't want to perform the action unless a comment has really been added.
The only way that I have found so far to determine this is to hook into object_end_of_update with the code:
sub object_end_of_update {
my ($self, $args) = #_;
my ($object, $old_object, $changes) = #$args{qw(object old_object changes)};
print STDERR "--- Object Type: " . ref $object;
if ($object->isa('Bugzilla::Bug')) {
# Load comments on the old object here, otherwise by the time we get
# to bug_end_of_update it is too late, and we cannot determine if a
# new comment has been added or not.
$old_object->comments({order=>'oldest_to_newest'});
}
}
and then to hook into bug_end_of_update, whereupon I can do something like:
sub bug_end_of_update {
my ($self, $args) = #_;
my ($bug, $old_bug, $timestamp, $changes) = #$args{qw(bug old_bug timestamp changes)};
# Note that this will only work if the old comments have already been
# loaded in object_end_of_update, otherwise when we get the old comments
# here, it just goes to the DB, and gets all of the comments, including
# the new one, if there is one.
my $oldComments = $old_bug->comments({order=>'oldest_to_newest'});
my $newComments = $bug->comments({order=>'oldest_to_newest'});
if (scalar(#$newComments) > scalar(#$oldComments)) {
# If we added a new comment, then perform processing.
do_slow_action($bug);
}
}
However, this feels fragile, and even if it isn't, is definitely not clear code.
What is the correct way to determine that a comment has been added to a bugzilla bug?
Instead of adding a hook to the Bugzilla source code.
You could add a database trigger that is fired whenever an update occurred on the field comments.
The advantage is that as long as the database schema doesn't change too much with newer versions, the trigger will keep working. (more information: Database triggers).
Here is an example:
use bugs;
create TRIGGER new_comment
AFTER UPDATE ON bugs_fulltext
FOR EACH ROW BEGIN
IF NEW.comments <> OLD.comments
THEN
.... //execute your script
END IF
END;

Difference between uri_for and uri_for_action

What is the difference between $c->uri_for and $c->uri_for_action methods of Catalyst.
Which one to use? And why?
#Devendra I think your examples could be somehow misleading if someone reads them.
uri_for expects path (and not action). It return an absolute URI object, so for example it's useful for linking to static content or in case you don't expect your paths to change.
So for example, let say you've deployed your application on domain example.com and subdir abc (example.com/abc/): $c->uri_for('/static/images/catalyst.png') would return example.com/abc/static/images/catalyst.pn, or for example: $c->uri_for('/contact-us-today') would return example.com/abc/contact-us-today. If you decide later to deploy your application under another subdirectory or at / you'll still end up with correct links.
Let say that your contact-us action looks like: sub contact :Path('/contact-us-today') :Args(0) {...} and you decide later that /contact-us-today should become just /contact-us. If you've used uri_for('/contact-us-today') you'll need to find and change all lines which points to this url. However you can use $c->uri_for_action('/controller/action_name') which will return the correct url.
dpetrov_ in #catalyst says:
If the paths are likely to change, uri_for_action is better idea.
I found below difference between $c->uri_for and $c->uri_for_action
Consider
Othercontroller.pm
__PACKAGE__->config(namespace => 'Hello');
.
.
.
sub bar: Local{
$c->res->redirect($c->uri_for('yourcontroller/method_name'));
$c->res->redirect($c->uri_for_action('yourcontroller/method_name'));
}
Yourcontroller.pm
sub method_name: Local{
print "In Yourcontroller:: method_name"
}
In case of $c->uri_for the url changes to
http://localhost:3000/Hello/yourcontroller/method_name
However for $c->uri_for_action the url changes to
http://localhost:3000/yourcontroller/method_name
So the namespace gets added in case of uri_for.

Why does this conditional redirect in Catalyst not work?

I have a Catalyst application and would like to redirect based on a conditional statement. I am having trouble with this and I'm wondering if anyone might have insight into why this seemingly easy task is proving difficult.
In my Root.pm module I have a sub begin and can redirect to another website, e.g. www.perl.org, but I am unable to redirect to a page within my application. Any thoughts on how to do a conditional redirect?
sub begin : Private {
my ( $self, $c ) = #_;
$c->stash->{client_id} = somenumber; # I'm setting this manually for testing
$c->res->redirect('http://www.perl.org/') unless $c->stash->{client_id};
$c->res->redirect('http://www.mysite.com/success') if $c->stash->{client_id}; #does not
}
Maybe you're getting stuck in an infinite loop, in which your begin sub redirects the user to another page in your Catalyst application; once "the controller that will run has been identified, but before any URL-matching actions are called" (from the Catalyst::Manual::Intro man page), begin will be called again, causing another redirect and so on.
Try moving this code out of begin entirely; perhaps, as Htbaa suggested, auto might be what you're looking for. The standard $c->detach case (in controller controller) is:
sub check_login :Local {
# do something
$c->detach('controller/login_successful') if($success);
# display error message
}
sub login_successful :Local {
# do something with the logged in user.
}
In this case, doing a $c->res->redirect('http://example.com/login_successful') should work perfectly as well. Hope that helps!

How do I cleanup at request end in Catalyst?

I'm trying to get some code called after each request completes using Catalyst. Basically, I want to run some code as part of finalize. Supposedly Catalyst::Plugin::Observe will do this, but it appears completely broken (just loading the plugin breaks Catalyst).
I'm trying to fix the Observe plugin, but that's proving stubborn.
So, is there a better way to do get some cleanup code called at the end of each request?
(Note: This is in a model, not a controller, so I can't just use sub end { ... })
You can actually just add the code directly to your "MyApp" class:
package MyApp;
use Catalyst ...;
...
sub finalize {
my $c = shift;
$c->NEXT::finalize(#_);
# do your thing
}
This is how all plugins work; they are just methods that become part of your app.
I do agree that making "finalize" generate an event to observe is cleaner... but this is what we have to work with for now :) Join #catalyst on irc.perl.org, and we can discuss further. (I am jrockway, as you may guess.)
Edited to reply to:
(Note: This is in a model, not a controller, so I can't just use sub end { ... })
You do know that you have $c in end, right?
package Your::Model;
sub cleanup {
my $self = shift;
...
}
package Your::Controller;
sub end :Private {
my ($self, $c) = #_;
$c->model('Your::Model')->cleanup( ... )
}
Or you can do it from MyApp::finalize, as I suggested above.
The real question is, why does your model need to know about the request cycle? That sounds like awfully tight coupling.