ActiveJob perform_later from inside Redis subscription block - ruby-on-rails

I want to use the ruby redis client to subscribe to a channel. When subscription was successful I want to start an ActiveJob (Sidekiq is used) job using perform_later. This Job will then do some work and produce a result which will be published to the channel Redis channel.
This produces the following error:
Processing by Api::LocationController#yyy as */*
Parameters: {"zip_code"=>"503400"}
[ActiveJob] Failed enqueuing XXXJob to Sidekiq(default): NoMethodError (undefined method `call_pipeline' for #<Redis::SubscribedClient:0x00007fd9222cdf78 #client=#<Redis::Client:0x00007fd921e1b890 #options={:url=>"redis://localhost?db=xxx", :scheme=>"redis", :host=>"localhost", :port=>6379, :path=>nil, :read_timeout=>0, :write_timeout=>5.0, :connect_timeout=>5.0, :timeout=>5.0, :username=>nil, :password=>nil, :db=>0, :driver=>Redis::Connection::Ruby, :id=>nil, :tcp_keepalive=>0, :reconnect_attempts=>1, :reconnect_delay=>0.0, :reconnect_delay_max=>0.5, :inherit_socket=>false, :logger=>nil, :sentinels=>nil, :role=>:master, :_parsed=>true}, #reconnect=true, #logger=nil, #connection=#<Redis::Connection::Ruby:0x00007fd921df08c0 #sock=#<Redis::Connection::TCPSocket:fd 26>>, #command_map={}, #pending_reads=-3, #connector=#<Redis::Client::Connector:0x00007fd921e1a2d8 #options={:url=>"redis://localhost?db=xxx", :scheme=>"redis", :host=>"localhost", :port=>6379, :path=>nil, :read_timeout=>5.0, :write_timeout=>5.0, :connect_timeout=>5.0, :timeout=>5.0, :username=>nil, :password=>nil, :db=>0, :driver=>Redis::Connection::Ruby, :id=>nil, :tcp_keepalive=>0, :reconnect_attempts=>1, :reconnect_delay=>0.0, :reconnect_delay_max=>0.5, :inherit_socket=>false, :logger=>nil, :sentinels=>nil, :role=>:master, :_parsed=>true}>, #pid=54943>>)
Completed 500 Internal Server Error in 26ms (Allocations: 2374)
NoMethodError (undefined method `call_pipeline' for #<Redis::SubscribedClient:0x00007fd9222cdf78 #client=#<Redis::Client:0x00007fd921e1b890 #options={:url=>"redis://localhost?db=xxx", :scheme=>"redis", :host=>"localhost", :port=>6379, :path=>nil, :read_timeout=>5.0, :write_timeout=>5.0, :connect_timeout=>5.0, :timeout=>5.0, :username=>nil, :password=>nil, :db=>0, :driver=>Redis::Connection::Ruby, :id=>nil, :tcp_keepalive=>0, :reconnect_attempts=>1, :reconnect_delay=>0.0, :reconnect_delay_max=>0.5, :inherit_socket=>false, :logger=>nil, :sentinels=>nil, :role=>:master, :_parsed=>true}, #reconnect=true, #logger=nil, #connection=#<Redis::Connection::Ruby:0x00007fd921df08c0 #sock=nil>, #command_map={}, #pending_reads=-3, #connector=#<Redis::Client::Connector:0x00007fd921e1a2d8 #options={:url=>"redis://localhost?db=xxx", :scheme=>"redis", :host=>"localhost", :port=>6379, :path=>nil, :read_timeout=>5.0, :write_timeout=>5.0, :connect_timeout=>5.0, :timeout=>5.0, :username=>nil, :password=>nil, :db=>0, :driver=>Redis::Connection::Ruby, :id=>nil, :tcp_keepalive=>0, :reconnect_attempts=>1, :reconnect_delay=>0.0, :reconnect_delay_max=>0.5, :inherit_socket=>false, :logger=>nil, :sentinels=>nil, :role=>:master, :_parsed=>true}>, #pid=54943>>):
app/controllers/api/location_controller.rb:10:in `block (3 levels) in yyy'
app/controllers/api/location_controller.rb:8:in `block in yyy'
app/controllers/api/location_controller.rb:5:in `yyy'
The yyy function is part of a controller that gets called on a GET request:
def yyy
REDIS_POOL.with { |r|
data = r.get("key")
if data.nil?
r.subscribe("channel") { |on|
on.subscribe {
XXXJob.perform_later(params)
}
on.message { |_, message|
puts message
render json: message
r.unsubscribe("channel")
}
}
else
render json: data
end
}
end
It seems to me like there's some context being lost from the Redis subscribe block which is important for the perform_later function. The same code works if I move the XXXJob.perform_later(params) call above the r.subscribe("channel") call and thus outside of the block. I however want to make sure that the job is only started after the subscription to the Redis channel was successful. Is there any way to achieve this?

It's difficult. It will not work because Sidekiq's default connection pool always reuses the thread's current connection, which is handling the subscribe call and that call is not reentrant. You need to manually create a separate connection pool and use it directly.
ANOTHER_POOL = ConnectionPool.new { Redis.new }
on.subscribe do
Sidekiq::Client.via(POOL) do
SomeWorker.perform_async(1,2,3)
SomeOtherWorker.perform_async(1,2,3)
end
end

Related

Twilio Webhooks in Rails - 401 error - no lines run in method?

I am trying to create a record in a model based on a incoming SMS. This is all in development now. I have the webhooks setup so that the incoming message triggers a webhook which then is processed by the controller. However, the method in the controller doesn't do anything and in the cmd line I get a 401 error.
Here is the code from the webhook controller and the error message:
class TwilioController < ApplicationController
skip_before_action :verify_authenticity_token
def sms_received
#daily_journal = DailyJournal.new
#daily_journal.user_id = current_user.id
#daily_journal.message_sid = params["MessageSid"]
#daily_journal.entry_text = params["Body"]
#daily_journal.save
end
Started POST "/twilio/sms_received" for 54.81.71.35 at 2022-12-04 20:36:57 +0200
Cannot render console from 54.81.71.35! Allowed networks: 127.0.0.0/127.255.255.255, ::1
Processing by TwilioController#sms_received as */*
Parameters: {"ToCountry"=>"US", "ToState"=>"TX", "SmsMessageSid"=>"SMa57a8f9422708b4c3336d33f9be17179", "NumMedia"=>"0", "ToCity"=>"ARLINGTON", "FromZip"=>"22191", "SmsSid"=>"SMa57a8f9422708b4c3336d33f9be17179", "FromState"=>"VA", "SmsStatus"=>"received", "FromCity"=>"ARLINGTON", "Body"=>"R", "FromCountry"=>"US", "To"=>"+18171234567", "MessagingServiceSid"=>"MG4b459339d046009b64e02ad50becab05", "ToZip"=>"76012", "NumSegments"=>"1", "ReferralNumMedia"=>"0", "MessageSid"=>"SMa57a8f9422708b4c3336d33f9be17179", "AccountSid"=>"deleted_but_there_was_something_here", "From"=>"+17031234567", "ApiVersion"=>"2010-04-01"}
Completed 401 Unauthorized in 7ms (Allocations: 690)

Can't catch ActiveRecord::RecordNotFound with rescue

I'm new to Ruby, please bear with me if this is a stupid question, or if I'm not following the best practice.
I'm finding an object in the DB using find(), and expect it to throw RecordNotFound in case the object of the id does not exist, like this.
begin
event = Event.find(event_id)
rescue ActiveRecord::RecordNotFound => e
Rails.logger.debug "Event does not exist, id: " + event_id
return {
# return "unauthorized" to avoid testing existence of event id
# (some redacted codes)
}
end
But somehow it is not caught (the log in the rescue block is not printed) and the entire program just return internal server error. Here's the stack trace:
Completed 500 Internal Server Error in 22ms (ActiveRecord: 1.0ms)
ActiveRecord::RecordNotFound (Couldn't find Event with 'id'=999):
lib/sync/create_update_event_handler.rb:78:in `handleRequest'
app/controllers/sync_controller.rb:36:in `block in sync'
app/controllers/sync_controller.rb:31:in `each'
app/controllers/sync_controller.rb:31:in `sync'
Rendering /usr/local/rvm/gems/ruby-2.4.0/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb within rescues/layout
Rendering /usr/local/rvm/gems/ruby-2.4.0/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_source.html.erb
Rendered /usr/local/rvm/gems/ruby-2.4.0/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_source.html.erb (6.4ms)
Rendering /usr/local/rvm/gems/ruby-2.4.0/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb
Rendered /usr/local/rvm/gems/ruby-2.4.0/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb (2.3ms)
Rendering /usr/local/rvm/gems/ruby-2.4.0/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb
Rendered /usr/local/rvm/gems/ruby-2.4.0/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb (1.9ms)
Rendered /usr/local/rvm/gems/ruby-2.4.0/gems/actionpack-5.0.6/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb within rescues/layout (36.6ms)
The only thing I can think of is there are two different ActiveRecord::RecordNotFound, and I'm catching the wrong one, but I don't know if it is the case or how I can verify it.
What did I do wrong?
======================================
Update
The problem is in the rescue block, I was concatenating event_id (an integer) to a string.
The RecordNotFound exception was indeed caught, but when the type error was thrown in the rescue block, the wrong error message was printed.
You won't get an error if you do
event = Event.find_by(id: event_id)
In this case if the record can't be found by ID it will just event == nil be nil.
In this case if the record can't be found by ID it will just event == nil be nil.
The code you pasted works fine for me. If you don't see output in the log, check your environment and log level settings INFO, WARN, DEBUG etc. 500 error indicates some kind of controller action raising the error.
see Set logging levels in Ruby on Rails
To be sure your rescue block is executing try doing something besides log. If you're running a development server you can try :
begin
event = Event.find(event_id)
rescue ActiveRecord::RecordNotFound => e
msg = "Event does not exist, id: #{event_id.to_s}"
Rails.logger.debug msg.
puts msg
binding.pry # if you have gem 'pry' in your development gems.
File.open('test.log', 'w') {|f| f.write msg} #check if this appears in root of your app
return {
# return "unauthorized" to avoid testing existence of event id
# (some redacted codes)
}
end
UPDATE: I changed the string interpolation according to your answer. You can also call .to_s inside interpolation instead of closing quotes and appending.
Turned out the error message is wrong.
The problem is that I was concentating the event_id (an integer) to a string.
But somehow Rails prints out the RecordNotFound exception.
The problem is fixed by replacing
Rails.logger.debug "Event does not exist, id: " + event_id
with
Rails.logger.debug "Event does not exist, id: " + event_id.to_s
Thanks #lacostenycoder for bringing my attention to the error message.
#event = Event.find(params[:id]). you should write instead params[:id] .That's the cause of an error.

Weird error when testing a Rails + AngularJS app with Rspec and PhantomJS

I have an app that lists tickets. It uses AngularJS. Here's the controller action:
def index
#tickets = apply_scopes(#tickets)
response.headers['x-total-count'] = #tickets.total_count
response.headers['x-per-page'] = Ticket.default_per_page
end
The Angular controller (Coffeescript):
$scope.fetch = ->
Ticket.query($scope.search).then (response) ->
$scope.tickets = response.data
$scope.totalCount = response.headers('x-total-count')
$scope.perPage = response.headers('x-per-page')
$scope.fetch()
I'm using angular-rails-resource to fetch the records. Everything works smoothly if I test by hand.
Here is the spec:
let(:user) { create :user }
scenario 'User lists tickets', js: true do
login_as user, scope: :user
ticket = create :ticket, user: user
visit root_path
click_on 'Support Requests'
expect(page).to have_content(ticket.subject)
end
When I run this spec, I just get the regular Rspec failure message because the condition was not met, but it should have:
expected to find text "ticket 000" in...
I figured it had something to do with concurrency and Capybara not waiting for Angular to fetch and display the records. Then I went ahead and added a sleep 2 right above the expectation just to test that. When I do it, I get a different error:
Capybara::Poltergeist::JavascriptError:
One or more errors were raised in the Javascript code on the page. If you don't care about these errors, you can ignore them by setting js_errors: false in your Poltergeist configuration (see documentation for details).
Possibly unhandled rejection: {"data":"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\">\n<HTML>\n <HEAD><TITLE>Internal Server Error</TITLE></HEAD>\n <BODY>\n <H1>Internal Server Error</H1>\n undefined method `split' for 1:Fixnum\n <HR>\n <ADDRESS>\n WEBrick/1.3.1 (Ruby/2.3.3/2016-11-21) at\n 127.0.0.1:54674\n </ADDRESS>\n </BODY>\n</HTML>\n","status":500,"config":{"method":"GET","transformRequest":[null],"transformResponse":[null],"jsonpCallbackParam":"callback","url":"/tickets","params":{},"headers":{"Accept":"application/json","Content-Type":"application/json"},"data":{}},"statusText":"Internal Server Error "}
Possibly unhandled rejection: {"data":"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\">\n<HTML>\n <HEAD><TITLE>Internal Server Error</TITLE></HEAD>\n <BODY>\n <H1>Internal Server Error</H1>\n undefined method `split' for 1:Fixnum\n <HR>\n <ADDRESS>\n WEBrick/1.3.1 (Ruby/2.3.3/2016-11-21) at\n 127.0.0.1:54674\n </ADDRESS>\n </BODY>\n</HTML>\n","status":500,"config":{"method":"GET","transformRequest":[null],"transformResponse":[null],"jsonpCallbackParam":"callback","url":"/tickets","params":{},"headers":{"Accept":"application/json","Content-Type":"application/json"},"data":{}},"statusText":"Internal Server Error "}
at http://127.0.0.1:54674/assets/application-713835b1641be632b29f7502c00a879e171bca5d6d06a5f264afcd819c123e76.js:14363
Here is my stack:
rails (5.0.2)
capybara (2.12.1)
poltergeist (1.13.0)
rspec-core (3.5.4)
phantomjs 2.1.1
Additional info:
If I output something right before ending the controller action, it gets outputted. The execution is going through the entire action;
If I console.log something right before fetching tickets, it's outputted as well. However, the Promise is not being resolved.
I found out the issue was with my pagination headers (x-total-count and x-per-page). Converting them to String does the trick. The weird part is that it was working OK in development, but not in test environment. So, if anyone has this issue in the future, the solution in my case was:
def index
#tickets = apply_scopes(#tickets)
response.headers['x-total-count'] = #tickets.total_count.to_s
response.headers['x-per-page'] = Ticket.default_per_page.to_s
end
Notice .to_s being called when assigning the headers.

NoMethodError users_url with devise (ajax)

I use devise 2.2.2 with rails 3.2.11
I use devise with ajax requests
I changed the following configuration in initializers/devise.rb
config.navigational_formats = [:json, :html]
config.http_authenticatable_on_xhr = false
when I submit an empty sign in request, I expect to get a json response with errors hash, but i get a 500 instead (see below for the trace) (it works fine with sign up request)
here are my routes (nothing special)
devise_for :users
the trace:
Started POST "/users/sign_in.json" for 127.0.0.1 at 2013-01-27 13:33:45 +0100
Processing by Devise::SessionsController#create as JSON
Parameters: {"user"=>{"email"=>"", "password"=>"[FILTERED]"}}
Completed 401 Unauthorized in 1ms
Processing by Devise::SessionsController#new as JSON
Parameters: {"user"=>{"email"=>"", "password"=>"[FILTERED]"}}
Completed 500 Internal Server Error in 40ms
NoMethodError (undefined method `users_url' for #<Devise::SessionsController:0x007fe88ddd9550>):
You are probably overriding after_sign_in_path_for and have a code path in there that returns nil.
This causes devise to fall back to its default behaviour and call users_url to get the path to redirect to.
Why do I think this? Because you are having the same error I had (and lost some hair over) and also this bug report contains the github usernames of many other people who have been humbled by this particular issue.

Problem with Rails 3 and AMF , rails3-amf, RocketAMF

im trying to get AMF to work with Rails3.
I have succesfully installed rails3-amf-0.1.0 gem and the RocketAMF-0.2.1 gem.
In my app there is a controller with the following code:
def getRandomCards
#incoming = params[0]
#cards = Cardvo.first
respond_with(#cards) do |format|
format.amf { render :amf => #cards.to_amf}
end
end
through a call from Actionscript i would like to return some data in amf format.
further more, as mentioned in the instructions for rails3-amf i did the following.
in my production.rb under config/environment i added the line
config.rails3amf.map_params :controller => 'CardvosController', :action => 'getRandomCards'
an my amf gateway got
config.rails3amf.gateway_path = "/gateway"
The problem is:
Any call from Actionscript / Flash raises the following
(taken from the log )
Started POST "/gateway" for 192.178.168.1 at Fri Nov 19 15:13:28 +0100 2010
Processing by CardvosController#getRandomCards as AMF
Parameters: {0=>100.0}
[1m[36mSQL (0.4ms)[0m [1mSHOW TABLES[0m
[1m[35mCardvo Load (0.2ms)[0m SELECT `cardvos`.* FROM `cardvos` LIMIT 1
Completed 200 OK in 13ms (Views: 0.9ms | ActiveRecord: 0.5ms)
NoMethodError (undefined method `constructed?' for #<RocketAMF::Envelope:0x39ba868>):
The Amf file is created but the method, which is in remoting.rb from RocketAMF could not be found.
I think the error is thrown at request_parser.rb from Rails3AMF asking for constructed?
# Wrap request and response
env['rack.input'].rewind
env['rails3amf.request'] = RocketAMF::Envelope.new.populate_from_stream(env['rack.input'].read)
env['rails3amf.response'] = RocketAMF::Envelope.new
# Pass up the chain to the request processor, or whatever is layered in between
result = #app.call(env)
# Calculate length and return response
if env['rails3amf.response'].constructed?
For me it seems it is looking at the wron class for the method.
Where
NoMethodError (undefined method `constructed?' for #RocketAMF::Envelope:0x39ba868):
the essential part is
RocketAMF::Envelope:0x39ba868
which should be
RocketAMF:ANOTHER_CLASS:Envelope:0x39ba868
Am i right and where the heck is the error ?
Any help would be appreciated!
chris

Resources