Unable to run after_action callback for turbo_stream actions - ruby-on-rails

So I have a 'ThaaliTakhmeens' controller where some actions have their corresponding turbo_stream templates to lazy load instances from databases using hotwire with pagination (pagy). And in those actions, there's a similar logic that I want to factor out into an after_action callback, (following the DRY principle).
After factoring out the code into an after_action, the instances don't show up on the page in fact the after_action doesn't get executed at all which I verified by giving the debugger in it. I have also provided a before_action to those actions which works perfectly fine.
Here's the code:
after_action :set_pagy_thaalis_total, only: [:complete, :pending, :all]
def complete
#tt = ThaaliTakhmeen.includes(:sabeel).completed_year(#year)
end
def pending
#tt = ThaaliTakhmeen.includes(:sabeel).pending_year(#year)
end
def all
#tt = ThaaliTakhmeen.includes(:sabeel).in_the_year(#year)
end
private
def set_pagy_thaalis_total
#total = #tt.count
#pagy, #thaalis = pagy_countless(#tt, items: 8)
debugger
end
Here's the log on visiting the 'complete' action:
Started GET "/takhmeens/2022/complete" for ::1 at 2023-01-21 10:07:35 +0530
Processing by ThaaliTakhmeensController#complete as HTML
Parameters: {"year"=>"2022"}
Rendering layout layouts/application.html.erb
Rendering thaali_takhmeens/complete.html.erb within layouts/application
Rendered shared/_results.html.erb (Duration: 2.4ms | Allocations: 2088)
Rendered thaali_takhmeens/complete.html.erb within layouts/application (Duration: 3.7ms | Allocations: 2396)
Rendered layout layouts/application.html.erb (Duration: 3.9ms | Allocations: 2477)
Completed 500 Internal Server Error in 6ms (ActiveRecord: 0.0ms | Allocations: 3027)
ActionView::Template::Error (undefined method `any?' for nil:NilClass
'.freeze; if instances.any?
^^^^^):
1: <%= turbo_frame_tag :results, data: { turbo_action: "advance" } do %>
2: <div class="container text-center mt-5">
3: <% if instances.any? %>
4: <%= render partial: "theader" %>
5: <div id=<%="#{id}"%> ></div>
6: <%= turbo_frame_tag :pagination, loading: :lazy, src: path %> %>
app/views/shared/_results.html.erb:3
app/views/shared/_results.html.erb:1
app/views/thaali_takhmeens/complete.html.erb:8
Since the after_action callback doesn't run, the instances (#thaalis) object is not set hence this error shows up, and also debugger didn't get executed either.
Here the complete action has both HTML and turbo_steam templates. And just to be clear that the content loads perfectly fine without the need for an after_action callback but that would be against the DRY principle.
So what would be the workaround for this? Is there another way to refactor the code or should I have to set something explicitly in the callback method to get it executed?

Good question. I actually not use after_action often and had to check. https://guides.rubyonrails.org/action_controller_overview.html#after-filters-and-around-filters What I think happen is that the rendering of the view is part of the action.
In your case this is inferred, you have no respond to block like this:
respond_to do |format|
if #record.update(record_params)
format.html { redirect_to a_record_route }
else
format.html { render :edit, status: :unprocessable_entity }
end
end
But the rendering of the template still happens in the action. Before you set some instance variables useful to the view.
What you can do if you really want to dry up your controller is adding set_pagy_thaalis_total at the end of each of your actions and removing the after_action.
EDIt: Also the fact your view is an html.erb or turbo_stream.erb file doesn't really matter.

Related

Ruby-on-rails: Get working but Post isn't

In very short.
When I have a form set as POST, the controller picks up the request, processes it and even starts to render the correct view. But the browser stays on the form page
When I switch the form to GET, it works
(yes, I remembered to change the route from get to post and back)
Here is the log:
Started POST "/sooth/search" for 127.0.0.1 at 2022-07-02 13:43:40
-0700 Processing by SoothController#search as TURBO_STREAM Parameters: {"authenticity_token"=>"[FILTERED]",
"search"=>{"name"=>"search keywords"}, "commit"=>"Save Search"}
Rendering layout layouts/application.html.erb Rendering
sooth/search.html.erb within layouts/application
I am rendering the search html page
The line above is a log message in the search.html.erb page
Rendered sooth/search.html.erb within
layouts/application (Duration: 1.0ms | Allocations: 148) Rendered
layout layouts/application.html.erb (Duration: 6.6ms | Allocations:
2710) Completed 200 OK in 15ms (Views: 11.3ms | ActiveRecord: 0.0ms |
Allocations: 4040)
BUT the search page is not displayed. Browser stays on the search form page.
Any hints deeply appreciated.
(And as you have probably guessed, I am day 1 with rails)
EDIT:
class SoothController < ApplicationController
include SoothHelper
def index
puts "sooth index"
template = get_query_template('sooth_search')
puts(template)
end
def search
form_params = params[:search]
puts 'searching' + form_params[:name].to_s
render "sooth/search"
end
end
ROUTES
Rails.application.routes.draw do
Rails.application.routes.draw do
get "/nlp_data", to: "nlp_data#index"
get "/sooth", to: "sooth#index"
post "/sooth/search", to: "sooth#search"
end
Your problem is you are trying to render the same page instead of redirecting to to the sooth page ,and secondly you cannot acces params directly in a post request, instead you must acces it from a strong param method
class SoothController < ApplicationController
include SoothHelper
def index
puts "sooth index"
template = get_query_template('sooth_search')
puts(template)
end
def search
form_params = sooth_params[:search]
puts 'searching' + form_params[:name].to_s
redirect_to "/sooth"
end
private
def sooth_params
params.require(:sooth).permit(:search)
end
end

Making Turbo Demo app work with local Rails server

I've set up a local Rails app that uses Turbo. I'm trying to shim the Turbo Demo iOS app to display my local Rails app. Has anyone been able to do a similar thing?
I switched Demo.current to use the local address, but all the requests show the "Error loading page" screen, with no useful logs coming out of the Demo app.
The Rails app shows my base route as being attempted, with some form of persistence with retrying a 401 error. I changed the SceneController default URL to load a /networks/all-people, which requires an authorized user (via Devise) to hopefully see how the authentication logic would go. Below is the Rails output when running the Demo app:
Started GET "/networks/all-people" for 127.0.0.1 at 2021-01-21 19:50:26 -0500
Processing by NetworksController#all_people as HTML
Completed 401 Unauthorized in 0ms (Allocations: 256)
Started GET "/users/sign_in" for 127.0.0.1 at 2021-01-21 19:50:26 -0500
Processing by Devise::SessionsController#new as HTML
Rendering layout layouts/application.html.erb
Rendering devise/sessions/new.html.erb within layouts/application
Rendered devise/shared/_links.html.erb (Duration: 0.3ms | Allocations: 218)
Rendered devise/sessions/new.html.erb within layouts/application (Duration: 1.7ms | Allocations: 1056)
Rendered layout layouts/application.html.erb (Duration: 9.4ms | Allocations: 7318)
Completed 200 OK in 10ms (Views: 9.8ms | Allocations: 7945)
Has anyone been able to shim the Demo app into working with a local Rails server? I'm entirely unsure of what's happening wrong here, or whether shimming is even a good idea here.
Turbo is being loaded in, as verified by the following event listener fires when visiting from my browser.
<script>
document.addEventListener("turbo:load", function(e) {
console.log("TURBO LOADED");
});
</script>
There are a few things that are needed to get Devise to work with Turbo.
Add app/controllers/turbo_controller:
# frozen_string_literal: true
class TurboController < ApplicationController
class Responder < ActionController::Responder
def to_turbo_stream
controller.render(options.merge(formats: :html))
rescue ActionView::MissingTemplate => e
raise e if get?
if has_errors? && default_action
render rendering_options.merge(formats: :html, status: :unprocessable_entity)
else
redirect_to navigation_location
end
end
end
self.responder = Responder
respond_to :html, :turbo_stream
end
Add data: { turbo: "false" } to your devise login form:
# app/views/devise/sessions/new.html.erb
<%= form_for(resource, as: resource_name, url: session_path(resource_name), data: { turbo: "false" }) do
Update config/initializers/devise.rb:
class TurboFailureApp < Devise::FailureApp
def skip_format?
%w[html turbo_stream */*].include?(request_format.to_s)
end
end
config.parent_controller = "TurboController"
config.navigational_formats = ["*/*", :html, :turbo_stream]
config.warden do |manager|
manager.failure_app = TurboFailureApp
end
Detailed explanation thanks to GoRails!

Rails render in controller#create is not loading the page

I have a form being submitted by the resident of a condominium to apply a pass for a visitor.
Upon submission I have setup the controller to render a template or redirect it to another path depending on the input of the form and display a flash message on the top of the page after clicking the submit button.
Somehow redirect works fine but render does nothing to the page.
Did try flash.now with render but no flash and it looks like it is not loading anything new on the page.
class VisitorPassesController < ApplicationController
def new
#visitor_pass = VisitorPass.new
#controller = "visitor_passes"
end
def create
unless resident && correct_resident_key?
flash[:danger] = "Invalid resident key."
# render 'new' doesn't work
# render action: 'new' doesn't work
redirect_to new_visitor_pass_path works
redirect_to '/visitor_passes/new' works
return
end
.
.
.
end
end
Here is the output of the console.
Rendering visitor_passes/new.html.erb within layouts/application
Rendered visitor_passes/new.html.erb within layouts/application
Rendered layouts/_rails_default.html.erb
Rendered layouts/_shim.html.erb
Resident Load (0.3ms) SELECT "residents".* FROM "residents" WHERE "residents"."id" = ? LIMIT ? [["id", 155], ["LIMIT", 1]] app/helpers/sessions_helper.rb:17:in 'current_user'
Rendered layouts/_header.html.erb (Duration: 3.1ms | Allocations: 760)
Completed 200 OK in 429ms (Views: 59.6ms | ActiveRecord: 2.3ms | Allocations: 33936)
Flash messages are displayed in the next response cycle (ie after a redirect). If you want to display a flash message in response to the current request use ActionDispatch::Flash::FlashHash#now.
class VisitorPassesController < ApplicationController
def new
#visitor_pass = VisitorPass.new
# just use the controller_name method provided by rails instead
end
def create
# ...
# prefer positive conditions instead of negative
if resident && correct_resident_key?
# do something awesome
else
flash.now[:danger] = "Invalid resident key."
render :new
end
end
end
Also make sure you are using the local: true option on the form if using form_with as it defaults to those pesky XHR remote: true requests.

Error making duplicate get requests

I have a problem where my controller action is creating duplicate records. I have a search form that checks the database for a record, or if its not in the database, scrapes a website to create the record. This same action (not restful i know) is also creating a record of the search itself with a value of "success" or "failure" depending on if the record could be found. However, it is consistently duplicating the record of the search. Here is the form_tag:
<%= form_tag new_search_path, remote: true, method: :get, id: 'etf-lookup-form' do %>
...
<%= text_field_tag :etf, params[:etf], placeholder: "ETF ticker symbol EX: SPY", autofocus: true, class: 'form-control search-box input-lg', style: "text-transform: uppercase" %>
and the controller:
class SearchesController < ApplicationController
def new
if params[:etf]
#etf = Etf.find_by_ticker(params[:etf].upcase)
#etf ||= Scraper.new_from_lookup(params[:etf].upcase)
end
if #etf.present?
Search.create(ticker: params[:etf].upcase, user_id: current_user.id, status: "success" )
render :js => "window.location = '#{etf_path(#etf)}'"
else
Search.create(ticker: params[:etf].upcase, user_id: current_user.id, status: "failure" )
render :js => "window.location = '#{search_path}'"
end
end
The terminal output always shows the get request being made twice before rendering the new page. How can I solve this problem? And/or what is a better way to organize these controller actions? I also have an EtfsController which contains a show action. Here are a couple fragments of from the terminal:
Started GET "/search/new?utf8=%E2%9C%93&etf=spy&button=" for ::1 at 2017-05-01 20:05:49 -0400
Processing by SearchesController#new as JS
Parameters: {"utf8"=>"✓", "etf"=>"spy", "button"=>""}
.......
Completed 200 OK in 10ms (Views: 0.1ms | ActiveRecord: 2.0ms)
Started GET "/search/new?utf8=%E2%9C%93&etf=spy&button=" for ::1 at 2017-05-01 20:05:49 -0400
Processing by SearchesController#new as JS
...
Completed 200 OK in 9ms (Views: 0.1ms | ActiveRecord: 2.7ms)
Started GET "/etfs/1" for ::1 at 2017-05-01 20:05:49 -0400
Processing by EtfsController#show as HTML
...
Completed 200 OK in 126ms (Views: 117.9ms | ActiveRecord: 1.2ms)
Probably you can refactor your code to look like this and the problem should be gone:
before_action :set_etf, only: [:new]
after_action :create_search, only: [:new]
def new
render js: window_location
end
private
def create_search
Search.create(ticker: etf_params, user_id: current_user.id, status: "success" )
end
def etf_params
#etf_params ||= params[:etf].to_s.upcase
end
def set_etf
#etf = (Etf.find_by_ticker(etf_params) || Scraper.new_from_lookup(etf_params) if etf_params.present?
end
def window_location
return "window.location = '#{etf_path(#etf)}'" if #etf.present?
"window.location = '#{search_path}'"
end
This should do the trick, let me know how it goes!

Render not rendering right page

This might be impossible to answer since there are probably too many variables here, but I thought I'd give it a shot since I always find other answers here. I am still fairly new to rails.
So, I have a bills model/controller/view. I want to create a new bill. I will be editing out the things that shouldn't matter much, but if they are needed I can add them in - just don't want a wall of text.
In the route:
map.resources :bills
My new method in the controller:
def new
#bill = Bill.new
#submit_txt = "Create"
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #bill }
end
end
my form:
<% form_for(#bill) do |f| %>
<%= f.error_messages %>
### form elements here, this all seems fine ####
<p>
<%= f.submit #submit_txt %>
</p>
<% end %>
my create method in the controller:
def create
is_weekly = false
is_monthly = false
#bill = current_user.recurring_bills.build(params[:bill])
#bill.year = #current_year
#errors = 'checking this out'
if #errors.blank?
logger.info "no errors, supposedly; going to save"
### do saving stuff here####
else
logger.info "errors not blank"
render :action => :new
end
end
For some reason this always renders /bills instead of /bills/new. It used to work and I don't know what I did wrong, but now it's not. I get the same response with render :template => 'bills/new'. It goes to the right page with a redirect, but then it won't fill in the form with old values.
The log:
Processing BillsController#create (for 127.0.0.1 at 2010-07-21 21:00:47) [POST]
Parameters: {"commit"=>"Create", "action"=>"create", "authenticity_token"=>"Kc7/iPKbfJBKHHVARuN7K6207tW6Jx4OUn7Xb4uSB8A=", "bill"=>{"name"=>"rent", "month"=>"", "amount"=>"200", "alternator"=>"odd", "day"=>"35", "frequency"=>"monthly", "weekday"=>""}, "controller"=>"bills"}
User Load (0.6ms) SELECT * FROM "users" WHERE ("users"."remember_token" = 'dd7082c56f5a252d14e4e68c528eb26551875c647f998c15d16a064cb075d63c') LIMIT 1
errors not blank
Rendering template within layouts/application
Rendering bills/new
Rendered bills/_form (14.5ms)
Rendered layouts/_stylesheets (3.3ms)
Rendered layouts/_header (5.7ms)
Rendered layouts/_footer (0.3ms)
Completed in 174ms (View: 30, DB: 1) | 200 OK [http://localhost/bills]
Hopefully someone has an idea of what I've done wrong, or I guess I'm starting over.
run a rake:routes from your command line and you will see how they map.
bills GET /bills(.:format) {:controller=>"bills", :action=>"index"}
POST /bills(.:format) {:controller=>"bills", :action=>"create"}
new_bill GET /bills/new(.:format) {:controller=>"bills", :action=>"new"}
edit_bill GET /bills/:id/edit(.:format) {:controller=>"bills", :action=>"edit"}
bill GET /bills/:id(.:format) {:controller=>"bills", :action=>"show"}
PUT /bills/:id(.:format) {:controller=>"bills", :action=>"update"}
DELETE /bills/:id(.:format) {:controller=>"bills", :action=>"destroy"}
The RESTful resources take a little getting used to but in your case \bills with a post method goes to the create action. You are specifying in the create action to render the contents of the new template when you call render :action => :new - you do not actually run the action.
Try this:
render :new
From the docs:
Using render with :action is a frequent source of confusion for Rails newcomers. The specified action is used to determine which view to render, but Rails does not run any of the code for that action in the controller. Any instance variables that you require in the view must be set up in the current action before calling render.
Give this a shot and let us know how it goes. Also, if you render "new", remember that your new action creates a new Bill object and there won't be any old values for it to fill in. I think what you really want to do is render :edit. And in your edit action find the Bill object with the params that you pass to the action.

Resources