jeremyevans / rodauth

Ruby's Most Advanced Authentication Framework
http://rodauth.jeremyevans.net
MIT License
1.69k stars 95 forks source link

respond configuration method #369

Closed HoneyryderChuck closed 12 months ago

HoneyryderChuck commented 1 year ago

most/all success responses to POST actions involve setting a flash message and a redirect response. This abstract it by having a method which does both, and can be overridden if the user wants to do something else, like rendering a success page, run some js, smth else.

from https://github.com/jeremyevans/rodauth/discussions/368

HoneyryderChuck commented 1 year ago

@jeremyevans what do you think of this approach? If you agree, I can apply the same substitution inn other routes.

jeremyevans commented 1 year ago

I'm not sure how applicable this is to all features. From my earlier review of the login feature, it wouldn't work there. Can you do an analysis of the places where you could use this?

HoneyryderChuck commented 1 year ago

I've seen the same being applied in all of the other cases where a "flash then redirect" pattern is applied. You're right, login is the exception here. I guess one could redefine the function in it:

def login_response(saved_redirect)
  return super unless saved_redirect
  # otherwise, set flash and redirect here
end

The name for such a pattern couldd be improved ("smth_response" is a bit too broad). "flash_redirect_login"? Smth else?

Another thing that came up was that all of this would now be done via a function with define_method, which is (correct me if I'm wrong) slower than using def. Is this a concern? Should class_eval be used instead?

jeremyevans commented 1 year ago

define_method is slower than def all else being equal (less so starting in Ruby 3.3 due to optimizations I worked on). The other reason class_eval would be faster is you could skip using send. However, I'm not sure the optimization is worth it in this case. I would stick with define_method.

If login is the only odd duck here, let's apply this pattern to the other features, and define login_response manually instead of using response (let's switch the feature method from respond to response because it defines methods ending in response).

HoneyryderChuck commented 1 year ago

done.

jeremyevans commented 12 months ago

Thanks for the changes. I'll try to test and merge on Monday.

jeremyevans commented 12 months ago

@HoneyryderChuck I merged this and added some more changes on top of it. Can you review 97477cee999780d429a691bcf7d63fc24c1bcc6a and 608b329e91f6538b8da77fc045fbffd5abd7eaa6 and provide your thoughts?

HoneyryderChuck commented 12 months ago

The first commit looks ok. The second commit... I understand why you're doing it. I've dealt with that limitation when first writing the test. Still, it's something that I tripped over in the past regarding cleanly returning from a route. In a nutshell, the problem is the following:

change_account_view
puts "1" #=> executes

redirect some_path
puts "2" #=> never does

I guess this quirk is more of a roda render plugin than rodauth itself, but ideally (IMO) calling a _view method should immediately yield control back to the router, just like redirect does. I at least find it always surprising when the former doesn't. Just to provide anecdotal comparison, it's a behaviour I also find confusing in rails, where neither rendering a view nor redirecting prevent user code from executing (and potentially overriding the previous statement). While roda does it better for redirects, not doing it for views as well feels as confusing.

Nevertheless, this is just an opinion, and even if you agree, it'd be a bigger change than this MR would require, and the second commit does prevent users from shooting themselves in the foot, so 👍

jeremyevans commented 12 months ago

Rendering does not necessarily imply anything to do with the response body. A Roda route block may want to render, use the result of the render to send an email, include the result as part of a database query or web request, and then redirect afterward. It is common to use the result of a render as a response body, and Roda makes it easy, but rendering is not tied to the response like redirecting is. That's an important fundamental difference between rendering and redirecting.

In Rodauth, the view methods are always the last methods called in a route block, so the return value is used as the response body, and therefore an explicit halt is not needed. The reason for the second commit is to avoid undefined behavior if the user overrides a response method without understanding what is required. I think it is less likely a user will misunderstand what is needed when overriding a view method. Additionally, overriding a view method incorrectly does not have the same failure cases as overriding a response method incorrectly. Therefore, I don't think we need to take similar precautions for view methods that we take for response methods.

HoneyryderChuck commented 12 months ago

It is common to use the result of a render as a response body, and Roda makes it easy, but rendering is not tied to the response like redirecting is.

That may be so, it's just that perhaps I'd be expecting to have something that'd make the "render html and return response" more straightforward, so I wouldn't be caught accidentally doing:

if smth
  error_page_view
end
original_page_view

But I'm ok with it not really being considered a problem. Something that I should perhaps address as a discussion later on.

Thx again!