her icon indicating copy to clipboard operation
her copied to clipboard

Consumer App showing error - "no implicit conversion of Array into Hash" when API returns error json in a custom format

Open jiggneshhgohel opened this issue 11 years ago • 0 comments

Rails: 4.2.0
Ruby: 2.2.1
Her:   0.7.3
Devise: 3.4.1
Simple Token Authentication: 1.8.0

I have a Rails 4 application, ApiApp which exposes its API via relative endpoints /api/<resource_name_here>. Sample code snippets from this app (which exposes API)

module Api
  class ClientsController < ApiController
    def index
      clients = current_user.provider.clients
      render json: clients
    end

   ...
   ...
  end
end
module Api
  class ApiController < ActionController::Base
    acts_as_token_authentication_handler_for User, fallback_to_devise: false

    before_filter :check_authentication!

    private

    # Overridden version of Simple Token Authentication gem's authenticate_entity_from_token!(entity) method to handle token authentication in customized way
   #https://github.com/gonzalo-bulnes/simple_token_authentication/blob/1985632a36cef6a6b8182e3c45a75a2d39f7ee51/lib/simple_token_authentication/token_authentication_handler.rb#L29
    def authenticate_entity_from_token!(entity)
       ...
       ...
       ...

       perform_sign_in!(user, sign_in_handler) # when user is found and token is matched and verified to be valid
    end

    def check_authentication!
      # This variable is set in authenticate_entity_from_token!(entity) above to retain any
      # relevant error information to pass back to the API's client.
      if @authenticate_entity_from_token_error_message
         render_unauthorized_error_json(@authenticate_entity_from_token_error_message)
        return
      end
      render json: { status: :unauthorized } unless current_user
    end

    def render_unauthorized_error_json(error_message)
         render json: { 
            status: :unauthorized, 
            error: error_message
        }
    end

Now I am creating a new Rails 4, DataApp which needs to access ApiApp's data via latter's API. Code snippets from DataApp application:

Her Initializer

# https://github.com/remiprev/her#authentication

# Reference: 
# http://stackoverflow.com/questions/23402820/rails-server-bin-rails6-warning-already-initialized- constant-app-path-error#comment46052336_23424073

require "#{Rails.root}/lib/middleware/my_api_authentication_token"

# Settings is made available by rails_config gem
my_api_url =  Settings.api.url

Her::API.setup url: my_api_url do |config|
  # Request
  config.use MyApiAuthenticationToken
  config.use Faraday::Request::UrlEncoded
  config.use Her::Middleware::AcceptJSON

  # Response
  config.use Her::Middleware::DefaultParseJSON

  # Adapter
  config.use Faraday::Adapter::NetHttp
end

Her Model: data_app/app/models/api_app/client.rb

module ApiApp
  class Client
    include Her::Model
  end
end

data_app/app/controllers/api_app/clients_controller.rb

class ApiApp::ClientsController < ApplicationController
  before_action :authenticate_user!

  def index
    begin
      @clients = ApiApp::Client.all
      @clients.first.inspect
    rescue Exception => e
      Rails.logger.error e.message
      Rails.logger.error e.backtrace.join("\n")
    end
  end

end

To access ApiApp's data, DataApp needs to send an access token to it (which is expirable) . This token I am sending, using a Faraday::Middleware as documented here in a request header X-User-Token expected by Simple Token Authentication gem.

Api::ClientsController in ApiApp is successfully receiving the access token sent by DataApp request to it. However the token is found to be expired by the token verification logic in my ApiApp's Api::ApiController#authenticate_entity_from_token!(entity). Thus ApiApp is returning following json:

render json: {
   status: :unauthorized,
   error: "API Access Token is found expired. Please obtain a new access token."
}

Server logs from ApiApp after DataApp sent request to access ApiApp's Client resource (which is ActiveRecord based)

Started GET "/api/clients" for 127.0.0.1 at 2015-03-06 19:40:51 +0530
Processing by Api::ClientsController#index as JSON
  Parameters: {"subdomain"=>"api"}
Filter chain halted as :check_authentication! rendered or redirected
Completed 401 Unauthorized in 2ms (Views: 0.2ms | ActiveRecord: 0.0ms)

As can be seen from server logs the ApiApp is correctly returing an error json however in DataApp's I am seeing following error:

no implicit conversion of Array into Hash
~/.rvm/gems/ruby-2.2.1@data-app/gems/her-0.7.3/lib/her/model/relation.rb:15:in `merge'
~/.rvm/gems/ruby-2.2.1@data-app/gems/her-0.7.3/lib/her/model/relation.rb:15:in `apply_to'
~/.rvm/gems/ruby-2.2.1@data-app/gems/her-0.7.3/lib/her/model/attributes.rb:26:in `initialize'
~/.rvm/gems/ruby-2.2.1@data-app/gems/her-0.7.3/lib/her/model/attributes.rb:39:in `new'
~/.rvm/gems/ruby-2.2.1@data-app/gems/her-0.7.3/lib/her/model/attributes.rb:39:in `block in initialize_collection'
~/.rvm/gems/ruby-2.2.1@data-app/gems/her-0.7.3/lib/her/model/attributes.rb:35:in `each'
~/.rvm/gems/ruby-2.2.1@data-app/gems/her-0.7.3/lib/her/model/attributes.rb:35:in `map'
~/.rvm/gems/ruby-2.2.1@data-app/gems/her-0.7.3/lib/her/model/attributes.rb:35:in `initialize_collection'
~/.rvm/gems/ruby-2.2.1@data-app/gems/her-0.7.3/lib/her/model/attributes.rb:167:in `new_collection'
~/.rvm/gems/ruby-2.2.1@data-app/gems/her-0.7.3/lib/her/model/relation.rb:71:in `block in fetch'
~/.rvm/gems/ruby-2.2.1@data-app/gems/her-0.7.3/lib/her/model/http.rb:59:in `request'
~/.rvm/gems/ruby-2.2.1@data-app/gems/her-0.7.3/lib/her/model/relation.rb:70:in `fetch'
~/.rvm/gems/ruby-2.2.1@data-app/gems/her-0.7.3/lib/her/model/relation.rb:45:in `method_missing'
~/data_app/app/controllers/api_app/clients_controller.rb:7:in `index'

I tried a lot searching solutions for it but it seems there is something basic missing which is causing this error? Do I need to send the JSON from ApiApp in a pre-defined format which Her can understand? I did tried changing my Her model in DataApp to following however it shown me some other error:

module ApiApp
  class Client
    include Her::Model
    parse_root_in_json false, format: :json_api
  end
end

Can anybody please let me know what is the root cause behind the issue I am facing and what's the solution for getting rid of it?

Thanks, Jiggneshh

jiggneshhgohel avatar Mar 06 '15 15:03 jiggneshhgohel