method_missing
will catch sent messages for which there is no method defined, and the call to super
at the end will pass it up to Ruby's standard method_missing
behavior, which is what you are seeing (NoMethodError
). However, this only happens if the if
condition is not met, which is what I suspect is happening here, and would explain why it works in some situations but not in others. The call to :client
, having found no matching methods along the inheritance chain, will look for a template called "requests/client" - try adding this template and see if that fixes the issue.
Rails - NoMethodError
-
30-05-2022 - |
Question
I am receiving NoMethodErrors when my DeltaSynWorker runs. This happens in an application that was built as a revision of a currently working application. I am not the original programmer, and I am coming at it from a Java background (I mention this because it is possible I am missing something very obvious to others). I cannot figure out why NoMethodeError
is being thrown when the code is very similar to code that is currently working fine in a different web application.
The Error:
NoMethodError: undefined method `client' for #<ApiRequestBuilder:0x0000000705a8f0>
delta_sync_worker.rb
class DeltaSyncWorker < SyncWorker
include Sidekiq::Worker
sidekiq_options queue: "delta_syncs"
def perform(subscriber_id, client_id)
sleep(10)
current_subscriber = ApiSubscriberDecorator.decorate(Subscriber.find(subscriber_id))
Time.zone = current_subscriber.time_zone
client = ApiClientDecorator.decorate(Client.find(client_id))
arb = ApiRequestBuilder.new(URI.parse(SR_HOST + '/servlet/sync/process'))
if arb.client(:subscriber => current_subscriber, :client => client)
arb.transmit
if arb.success?
current_subscriber.touch(:sync_updated_at)
decorated_client = ClientDecorator.decorate(client.model)
ConfirmationsSyncWorker.perform_in(1.hours, current_subscriber.id)
else
error_params = {:subscriber => current_subscriber.id, :response_body => arb.response.body, :request_body => arb.request.body, :sync_type => "DeltaSyncWorker"}
Airbrake.notify(:error_class => "sync_error", :error_message => "Sync Error: #{arb.response.message}", :params => error_params)
end
end
end
end
api_request_builder.rb
require 'nokogiri'
class ApiRequestBuilder < AbstractController::Base
include AbstractController::Rendering
include AbstractController::Layouts
include AbstractController::Helpers
include AbstractController::Translation
include AbstractController::AssetPaths
self.view_paths = "app/api"
attr_accessor :request_body, :request, :response, :append_request_headers, :request_method, :url
def initialize(url, *args)
@url = url
if args
args.each do |arg|
arg.each_pair{ |k,v| instance_variable_set("@#{k.to_s}", v) }
end
end
end
# this will search for an api request template in api/requests, render that template and set any instance variables
def method_missing(meth, *args, &block)
if lookup_context.template_exists?("requests/#{meth.to_s}")
if args
args.each do |arg|
arg.each_pair{|k,v| instance_variable_set("@#{k.to_s}", v) }
end
end
@request_body = (render :template => "requests/#{meth.to_s}")
else
super
end
end
def transmit
@request_method ||= "Post"
@request = "Net::HTTP::#{@request_method}".constantize.new(@url.path)
@request['x-ss-user'] = @subscriber.sr_user if @subscriber && @subscriber.sr_user.present?
@request['x-ss-pwd'] = @subscriber.sr_password if @subscriber && @subscriber.sr_password.present?
unless @append_request_headers.nil?
@append_request_headers.each_pair{ |k,v| @request[k] = v }
end
@request.body = @request_body if request_body? && @request.request_body_permitted?
@http = Net::HTTP.new(@url.host, @url.port)
@http.use_ssl = true
@http.verify_mode = OpenSSL::SSL::VERIFY_NONE
@response = @http.request(@request)
end
def success?
if @response.code == 200.to_s
return true
else
return false
end
end
def request_body?
unless @request_body.nil?
return true
else
return false
end
end
end
I have been looking at other NoMethodError
questions here, but I cannot find an answer I feel applies to my situation. Any help is greatly appreciated. Thanks!
Solution
OTHER TIPS
I know Ive seen this before and I feel like it wasn't what it appeared to be, but ya basically method missing is just intercepting the method call and when you call arb.client, it is caught by method missing and therefore tries to render api/client.xml.arb or api whatever the file type is. -- Note that there should be a file in the initializers directory named somethig like api_template_handler.rb or arb_temmplate_handler.rb, which is what allows rails to see that template type in the view directory -- make sure that is there first. Also sidenote, _client.xml.api is a partial used by the other api request in that directory (full sync),
To debug Id start by, above the following line
if lookup_context.template_exists?("requests/#{meth.to_s}")
Add a log statement
Rails.logger.debug "Can I see View?" + lookup_context.template_exists?("requests/#{meth.to_s}")
If true, then the problem is the error isnt getting caught properly because of method missing. If false, then the sidekiq worker isnt loading rails properly, or the view path isn't being added onto the rails view paths.
If true, Im guessing it might have something to do with the client model not being loaded, or an attribute on the client model not existing, that the builder is trying to call, and the error is somehow bubbling up to the api request builder class.
Oh also, just general stuff, but make sure redis and sidekiq are running, and restart passenger if its non local environment.
Let me know how it goes.