I am trying to set up some Rails model working on top of the active_resource gem (ActiveResource)
module Core
class RestResource < ActiveResource::Base
self.format = :json
end
end
module Core
module API
class BaseModel < ::Core::RestResource
self.site = ENV['API_URI']
self.headers['Api-Token'] = ENV['API_TOKEN']
end
end
end
module Core
module API
class MyModel < ::Core::API::BaseModel
end
end
end
And I am trying to run the rails console as following
2.3.1 :001 > MyModel = Core::API::MyModel
=> Core::API::MyModel
2.3.1 :002 > MyModel.all
ArgumentError: expected an attributes Hash, got ["data", [{"first_name"=>"walter", "last_name"=>"white", "timestamp"=>1478515925}]]
from /Users/nakwa/.rvm/gems/ruby-2.3.1/bundler/gems/activeresource-f4bec7f8f389/lib/active_resource/base.rb:1377:in `load'
from /Users/nakwa/.rvm/gems/ruby-2.3.1/bundler/gems/activeresource-f4bec7f8f389/lib/active_resource/base.rb:1124:in `initialize'
from /Users/nakwa/.rvm/gems/ruby-2.3.1/bundler/gems/activeresource-f4bec7f8f389/lib/active_resource/base.rb:1051:in `new'
from /Users/nakwa/.rvm/gems/ruby-2.3.1/bundler/gems/activeresource-f4bec7f8f389/lib/active_resource/base.rb:1051:in `instantiate_record'
from /Users/nakwa/.rvm/gems/ruby-2.3.1/bundler/gems/activeresource-f4bec7f8f389/lib/active_resource/base.rb:1047:in `block in instantiate_collection'
from /Users/nakwa/.rvm/gems/ruby-2.3.1/bundler/gems/activeresource-f4bec7f8f389/lib/active_resource/collection.rb:68:in `block in collect!'
from /Users/nakwa/.rvm/gems/ruby-2.3.1/bundler/gems/activeresource-f4bec7f8f389/lib/active_resource/collection.rb:8:in `each'
from /Users/nakwa/.rvm/gems/ruby-2.3.1/bundler/gems/activeresource-f4bec7f8f389/lib/active_resource/collection.rb:8:in `each'
from /Users/nakwa/.rvm/gems/ruby-2.3.1/bundler/gems/activeresource-f4bec7f8f389/lib/active_resource/collection.rb:68:in `collect!'
from /Users/nakwa/.rvm/gems/ruby-2.3.1/bundler/gems/activeresource-f4bec7f8f389/lib/active_resource/base.rb:1047:in `instantiate_collection'
from /Users/nakwa/.rvm/gems/ruby-2.3.1/bundler/gems/activeresource-f4bec7f8f389/lib/active_resource/base.rb:1016:in `find_every'
from /Users/nakwa/.rvm/gems/ruby-2.3.1/bundler/gems/activeresource-f4bec7f8f389/lib/active_resource/base.rb:923:in `find'
from /Users/nakwa/.rvm/gems/ruby-2.3.1/bundler/gems/activeresource-f4bec7f8f389/lib/active_resource/base.rb:949:in `all'
from (irb):2
from /Users/nakwa/.rvm/gems/ruby-2.3.1/gems/railties-5.0.0.1/lib/rails/commands/console.rb:65:in `start'
from /Users/nakwa/.rvm/gems/ruby-2.3.1/gems/railties-5.0.0.1/lib/rails/commands/console_helper.rb:9:in `start'
from /Users/nakwa/.rvm/gems/ruby-2.3.1/gems/railties-5.0.0.1/lib/rails/commands/commands_tasks.rb:78:in `console'
from /Users/nakwa/.rvm/gems/ruby-2.3.1/gems/railties-5.0.0.1/lib/rails/commands/commands_tasks.rb:49:in `run_command!'
from /Users/nakwa/.rvm/gems/ruby-2.3.1/gems/railties-5.0.0.1/lib/rails/commands.rb:18:in `<top (required)>'
from bin/rails:4:in `require'
from bin/rails:4:in `<main>'
2.3.1 :003 >
On my rails console (rails instance powering the API) I can see
Started GET "/v1/my_model.json" for ::1 at 2016-11-08 16:42:11 +0100
Processing by V1::MyModelController#index as JSON
MyModel Load (0.2ms) SELECT "my_model".* FROM "my_model"
[active_model_serializers] Rendered V1::MyModelSerializer with MyModelSerializer::Adapter::Attributes (2.2ms)
Completed 200 OK in 13ms (Views: 2.8ms | ActiveRecord: 1.8ms)
My understanding is that Rails activeresource is expecting the JSON API to return something like :
[
{"first_name": "walter", "last_name": "white"},
{"first_name": "chuck", "last_name": "norris"}
]
When my collections looks like this:
{
"data": [
{"first_name": "walter", "last_name": "white"},
{"first_name": "chuck", "last_name": "norris"}
],
"collection": {
"total_entries": 2
}
}
I am now look for a nice way of customising the ActiveResource Collection parser, probably something like backbone.js is doing (http://backbonejs.org/#Collection-parse) but somehow I can't find out an obvious answer to that question.
Any idea? Thanks!
ActiveResource by default has very strong expectations concerning format of data returned by the API, but you can provide your own format for scenarios like this.
Any Ruby object implementing a few expected methods can be used as a format. ActiveResource itself implements formats as modules, so we will stick to this convention.
Being a lazy man, I will reuse the standard JsonFormat implementation and only override method decode
:
module CustomJsonFormat
include ActiveResource::Formats::JsonFormat
extend self
def decode(json)
ActiveSupport::JSON.decode(json)['data']
end
end
Now we just tell the ActiveResource subclass to use it:
class BaseModel < ActiveResource::Base
self.format = CustomJsonFormat
# ... set self.site, self.headers etc.
end