Rails - Pass object as argument without serializing - ruby-on-rails

I have a controller from which I have to pass an object instance as an argument to a Worker class of Sidekiq.
My controller with the call WegWorker.synchronize(#service)
class Api::V2::Events::WegSessionsController < Api::V2::Events::ApplicationController
before_action :load_service
def synchronize
#service.keep_cancelled = params[:keep_cancelled].to_b
if #service.valid_connection?
WegWorker.perform_async(#service)
render json: {
status: :ok,
message: #service.message
}, status: :ok
else
render json: {
status: :unprocessable_entity,
errors: #event.errors.full_messages
}, status: :ok
end
end
.. some code with service object instantiation
end
My worker class
class WegWorker < ::CarrierWave::Workers::ProcessAsset
include Sidekiq::Worker
sidekiq_options retry: false
def perform(service)
service.synchronize
end
end
But I am currently getting the error as
WARN: NoMethodError: undefined method `synchronize' for "#<WegService:0x0000000013f9abb0>":String
How can I pass the object without serializing it to the worker?

Sidekiq jobs can only take simple parameters, like string, number, etc. To work around this, the recommended approach is to have the following methods defined on your WegService:
class WegService
def self.from_params(params)
new(...) #rehydrate your service using params
end
def to_params
[43, 'foo', 'bar'] # pickle your service
end
# ...
end
Then, you would enqueue like this:
WegWorker.perform_async(#service.to_params)
And the worker would have:
def perform(params)
WebService.from_params(params).synchronize
end

You are queuing a complex Ruby object with this WegWorker.perform_async(#service). This won't be serialized properly and you will end up with a string which looks something like this "#<WegService:0x0000000013f9abb0>". While deserializing Sidekiq won't convert it into the original class like you are expecting it to.
You are supposed to queue simple objects only. So you can push identifiers to the async job and reinstantiate the service yourself inside the perform method.
Also, my advice would be to go through Sidekiq docs. They are quite small and you would save yourself from such kind of errors. Relevant link for this section.

Related

Why is yield not passing the result to block (Rails)?

I know there are several SO questions as well as online articles on using yield in Rails. But I'm still having trouble understanding what's wrong with my code below, and would appreciate any advice.
In my app, I have:
A controller that passes data to the command class's run method, and returns the request status based on the result of the Command.run (true/false)
A command class that deals with the actual meat of the process, then yields true if it succeeded, or false if it failed
However, the command class seems to be failing to yield the results to my controller. According to the error messages when I run my tests, it seems like my block in the controller isn't being recognized as a block:
# If I use "yield result":
LocalJumpError: no block given (yield)
# If I use "yield result if block_given?":
# (This is because I have "assert_response :success" in my tests)
Expected response to be a <2XX: success>, but was a <400: Bad Request>
How should I rewrite the block (do ... end part in the controller below) so that yield works correctly? Or if the issue lies elsewhere, what am I doing wrong?
I've provided a simplified version of my code below. Thank you in advance!
# controller
def create
Command.run(params) do
render json: { message: 'Successfully processed request' }
return
end
render json: { message: 'Encountered an error' }, status: :bad_request
end
# command class
def run(params)
# Do some stuff, then send HTTP request
# "result" below returns true or false
result = send_http_request.parsed_response == 'ok'
yield result
end
def self.run(params)
new.run(params)
end
Note: This code works if I use if true... else... in the controller instead of a block, and just return the boolean result instead of yielding it. But here I'd like to know how to make yield work.
In your controller you need to have a variable for the result.
def create
Command.run(params) do |result|
if result
render json: { message: 'Successfully processed request' }, status: :success
else
render json: { message: 'Encountered an error' }, status: :bad_request
end
return
end
render json: { message: 'Encountered an error' }, status: :bad_request
end
(EDIT)
Also, you are calling the class method which call the instance method. You have to pass the block from the calling code to the instance method you are calling.
def self.run(params, &block)
new.run(params, &block)
end
EDIT: ah, so you have a class method run and instance method run.
Either do as Marlin has suggested and supply the block explicitly from class method to the instance method.
Or use only the class method as I've initially suggested (it doesn't
seem like there's any reason to instantiate Command in your case):
def self.run(params, &block)
result = send_http_request.parsed_response == 'ok'
block.yield(result)
end

Rails API render all responses in camelCase

I'm trying to get my Rails API to render all JSON responses in camelCase. Currently I am using Netflix Fast JSON API for my serializer and rendering errors like so:
render json: { errors: command.errors }, status: :unauthorized
For the Netflix Fast JSON API serializers I've been adding set_key_transform :camel_lower to every serializer, which seems to do the trick (although if anyone knows how to make that a default it would be much appreciated).
For rendering errors however I'm not sure the best way to go about camel casing. If anyone has any experience with this please let me know how you go about it! Ideally there is a method of doing this that doesn't add too much syntax to every render call being made.
UPDATE
In serializing errors I added a helper method on the application controller:
def render_error(errors_params, status)
render json: {
errors: errors_params
}.deep_transform_keys { |key| key.to_s.camelize(:lower) }, status: status
end
For the Netflix Fast JSON API I took the suggestion of #spickermann and added an application serializer for the other serializers to inherit from:
class ApplicationSerializer
include FastJsonapi::ObjectSerializer
set_key_transform :camel_lower
end
class SomeSerializer < ApplicationSerializer
attributes :attribute, :other_attribute
end
You could create an ApplicationSerializer and all other serializers could inherit from it:
class ApplicationSerializer
include FastJsonapi::ObjectSerializer
set_key_transform :camel_lower
end
class FooBarSerializer < ApplicationSerializer
attributes :buzz, :fizz
# ...
end
You could monkey patch the serializer
Rails.application.config.to_prepare do
FastJsonapi::ObjectSerializer.class_eval do
set_key_transform :camel_lower
end
end
and for handling errors you can probably create an error serializer
render serializer: ErrorSerializer, json: {status: : unauthorized, errors: resource.errors
Have a look here and here

Ancestry Gem subtree errors (undefined method & object rendering)

I am very new to Ruby, so I might just be missing a simple thing here.
I am using the rails Ancestry gem to produce JSON output for an API I'm writing. Here is the controller:
class ParentsController < ApplicationController
def show
#vendor = Vendor.find(params[:id]).subtree
render json: #vendor, status: 200
end
end
This produces the correct output showing the ID, vendor, ancestry etc.
However when I add .arrange, as such:
class ParentsController < ApplicationController
def show
#vendor = Vendor.find(params[:id]).subtree.arrange
render json: #vendor, status: 200
end
end
The output I get is:
{"#\u003CVendor:0x4b9e628\u003E":
{"#\u003CVendor:0x4b9e3e8\u003E":
{},
"#\u003CVendor:0x4b9e1f0\u003E":
{}}}
which looks like memory locations for the objects. What fundamental thing am I missing to get this to actually output the objects in an arranged form?
I've also tried using arrange_serializable instead of arrange, this gives me a
undefined method 'arrange_serializable' for #<ActiveRecord::Relation::ActiveRecord_Relation_Vendor:0x4b9b910>
error

View for JSON errors on specific model

I have a model called Entity and the create action in the controller looks like this:
# enitities_controller.rb
def create
# loading params, etc...
#entity.save
respond_with #entity
end
I am using jbuilder for custom JSON views rather than rendering #entity.to_json, which works great. I have one last issue, which is when the model won't save due to validation errors I get the following response (with status 422 Unprocessable Entity):
{"errors":{"parent_share":["can't be blank","is not a number"]}}
I would like to override this json with my own. I am aware of he possibility to replace respond_with #entity with:
respond_with #entity do |format|
if #entity.errors.any?
format.json {
render "entities/create", :status => :unprocessable_entity
}
end
end
But shouldn't there be a more auto-magic way by defining some sort of errors view or something? This feels a bit dirty AND it makes me have to write more code each time I need this rather than allowing me to use respond_with. Is there another way?
Meanwhile I have found the answer:
You have to create the file lib/application_responder.rb and add the following:
class ApplicationResponder < ActionController::Responder
include Responders::FlashResponder
include Responders::HttpCacheResponder
def to_json
set_flash_message! if set_flash_message?
if !has_errors? || response_overridden?
default_render
else
controller.default_render( status: :unprocessable_entity )
end
end
end
And add the following to your application responder:
self.responder = ApplicationResponder
What this does is add a to_json method that will copy the behaviour of the to_js responder.

Simple respond_with in rails that avoids 204 from PUT

I want to PUT to rails and avoid getting a 204. I am using this pattern:
class SomeController < ApplicationController
respond_to :json
def update
# ...
respond_with(some_object)
end
end
However, when I do a put to update, I get a 204 back. I realize this is completely valid etc, but I explicitly want the content back. I can override it to some extent like this:
def update
respond_with(some_object) do |format|
format.json{render json: some_object}
end
end
but this seems a bit too hands-on for rails. Is there any more idiomatic way of avoiding a 204 and requesting the full content to be sent back? This is Rails 3.2.
In summary: I want maximally idiomatic rails that avoids a 204.
I made a custom responder which always returns my JSON encoded resource even on PUT/POST.
I put this file in lib/responders/json_responder.rb. Your /lib dir should be autoloaded.
module Responders::JsonResponder
protected
# simply render the resource even on POST instead of redirecting for ajax
def api_behavior(error)
if post?
display resource, :status => :created
# render resource instead of 204 no content
elsif put?
display resource, :status => :ok
else
super
end
end
end
Now, explicitly modify the controller which requires this behavior, or place it in the application controller.
class ApplicationController < ActionController::Base
protect_from_forgery
responders :json
end
You should now get JSON encoded resources back on PUT.
As a less invasive alternative, you can pass a json: option to the respond_with method invocation inside your controller update action, like this:
def update
# ...
respond_with some_object, json: some_object
end
Granted it seems a bit unDRY having to repeat the object twice in the arguments, but it'll give you what you want, the json representation of the object in the response of a PUT request, and you don't need to use the render json: way, which won't give you the benefits of responders.
However, if you have a lot of controllers with this situation, then customizing the responders, as jpfuentes2 showed in the accepted anwser, is the way to go. But for a quick single case, this alternative may be easier.
Source: https://github.com/plataformatec/responders/pull/115#issuecomment-72517532
This behavior seems intentional to fall in line with the HTTP spec, and "ideally" you should be firing off an additional GET request to see the results. However, I agree in the real world I'd rather have it return the JSON.
#jpfuentes2's solution above should do the trick (it's very similar to the pull request below), but I'm hesitant to apply anything that's patching rails internals, as it could be a real pain to upgrade between major versions, especially if you don't have tests for it (and let's face it, developers often skimp on controller tests).
References
https://github.com/rails/rails/issues/9862
https://github.com/rails/rails/pull/9887
Just to clarify, you do not need the responders gem to do this... You can just do:
config/initializers/responder_with_put_content.rb
class ResponderWithPutContent < ActionController::Responder
def api_behavior(*args, &block)
if put?
display resource, :status => :ok
else
super
end
end
end
and then either (for all updates actions to be affected):
class ApplicationController < ActionController::Base
def self.responder
ResponderWithPutContent
end
end
or in your action:
def update
foo = Foo.find(params[:id])
foo.update_attributes(params[:foo])
respond_with foo, responder: ResponderWithPutContent
end
What's wrong with simply doing:
def update
some_object = SomeObject.update()
render json: some_object
end
Not a big fan of this behavior. To get around it, I had to avoid using the respond_with method:
class SomeController < ApplicationController
respond_to :json
def update
# ...
respond_to do |format|
format.json { render(json: some_object, status: 200) }
end
end
end

Resources