cerebris / jsonapi-resources

A resource-focused Rails library for developing JSON:API compliant servers.
http://jsonapi-resources.com
MIT License
2.32k stars 529 forks source link

STI with non-default inheritance_column set on the class breaks has_many assoc #683

Open will3216 opened 8 years ago

will3216 commented 8 years ago

Looks like the issue is here: https://github.com/cerebris/jsonapi-resources/blob/v0.7.0/lib/jsonapi/resource_serializer.rb#L307

With a cursory glance, it looks like the STI attribute is hard-coded to 'type', though the with the problem I am seeing personally, it could be that or a polymorphic association (unlikely, but maybe both). Either way, the issue can be reproduced with the setup below.

Also, may not be the best idea to assume the model has an :id field, instead you should be reading type from the parent's inheritance_column attribute and its primary_key from it's primary_key attribute.

If I get a chance today, I will look into fixing this, but thought I would report it in case I am unable to get to it before I forget about it lol

Create a new rails app:

$ rvm use 2.1.2
$ rails --version 
=> Rails 4.1.14
$ rails new testtest
$ cd testtest/
$ rails g model ParentModel i_got_an_sti_once:string poly_rel_uid:string poly_rel_type:string
$ rails g model HasManyChildModel uid:string
$ rake db:migrate

Add jsonapi-resources

# Gemfile
...
gem 'jsonapi-resources'
...

Add your routes

#./config/routes.rb
Rails.application.routes.draw do
  jsonapi_resources :has_many_child_models, param: :uid
end

Add your controller

# ./app/controllers/has_many_child_controller.rb
class HasManyChildModelsController < ApplicationController
  include JSONAPI::ActsAsResourceController
end

Add your parent model

# ./app/models/parent_model.rb
class ParentModel < ActiveRecord::Base
  self.inheritance_column = 'i_got_an_sti_once' # That's not true :'(
end

Add your child model

# ./app/models/child_model.rb
class ChildModel < ParentModel
  belongs_to :poly_rel, polymorphic: true 
end

Add your model that has_many child models

# ./app/models/has_many_child_model.rb
class HasManyChildModel < ActiveRecord::Base
  # attributes: :uid
  has_many :child_models, as: :poly_rel, primary_key: :uid, foreign_key: :poly_rel_uid
end

Add your has_many_child_models resource

# ./app/resources/has_many_child_model_resource.rb
class HasManyChildModelResource < JSONAPI::Resource
  has_many :child_models, polymorphic: true, foreign_key_on: :related
end

Add your child_model resource

# ./app/resources/child_model_resource.rb
class ChildModelResource < JSONAPI::Resource
end

Now, start you server:

$ rails s -p 3001

And navigate to:

http://localhost:3001/has_many_child_models?include=child_models

To see the error:

{  
   "errors":[  
      {  
         "title":"Internal Server Error",
         "detail":"Internal Server Error",
         "id":null,
         "href":null,
         "code":500,
         "source":null,
         "links":null,
         "status":"500",
         "meta":{  
            "exception":"SQLite3::SQLException: no such column: type: SELECT type, \"parent_models\".\"id\" FROM \"parent_models\"  WHERE \"parent_models\".\"i_got_an_sti_once\" IN ('ChildModel') AND \"parent_models\".\"poly_rel_uid\" = ? AND \"parent_models\".\"poly_rel_type\" = ?",
            "backtrace":[  
               "/Users/***/.rvm/gems/ruby-2.1.2/gems/sqlite3-1.3.11/lib/sqlite3/database.rb:91:in `initialize'",
               "/Users/***/.rvm/gems/ruby-2.1.2/gems/sqlite3-1.3.11/lib/sqlite3/database.rb:91:in `new'",
               "/Users/***/.rvm/gems/ruby-2.1.2/gems/sqlite3-1.3.11/lib/sqlite3/database.rb:91:in `prepare'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.14/lib/active_record/connection_adapters/sqlite3_adapter.rb:311:in `block in exec_query'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.14/lib/active_record/connection_adapters/abstract_adapter.rb:378:in `block in log'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/activesupport-4.1.14/lib/active_support/notifications/instrumenter.rb:20:in `instrument'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.14/lib/active_record/connection_adapters/abstract_adapter.rb:372:in `log'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.14/lib/active_record/connection_adapters/sqlite3_adapter.rb:298:in `exec_query'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.14/lib/active_record/connection_adapters/sqlite3_adapter.rb:510:in `select'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.14/lib/active_record/connection_adapters/abstract/database_statements.rb:24:in `select_all'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.14/lib/active_record/connection_adapters/abstract/query_cache.rb:68:in `block in select_all'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.14/lib/active_record/connection_adapters/abstract/query_cache.rb:83:in `cache_sql'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.14/lib/active_record/connection_adapters/abstract/query_cache.rb:68:in `select_all'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.14/lib/active_record/relation/calculations.rb:172:in `pluck'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.14/lib/active_record/associations/collection_proxy.rb:31:in `pluck'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/jsonapi-resources-0.7.0/lib/jsonapi/resource_serializer.rb:307:in `foreign_key_types_and_values'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/jsonapi-resources-0.7.0/lib/jsonapi/resource_serializer.rb:262:in `to_many_linkage'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/jsonapi-resources-0.7.0/lib/jsonapi/resource_serializer.rb:285:in `link_object_to_many'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/jsonapi-resources-0.7.0/lib/jsonapi/resource_serializer.rb:293:in `link_object'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/jsonapi-resources-0.7.0/lib/jsonapi/resource_serializer.rb:189:in `block in relationship_data'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/jsonapi-resources-0.7.0/lib/jsonapi/resource_serializer.rb:181:in `each'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/jsonapi-resources-0.7.0/lib/jsonapi/resource_serializer.rb:181:in `each_with_object'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/jsonapi-resources-0.7.0/lib/jsonapi/resource_serializer.rb:181:in `relationship_data'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/jsonapi-resources-0.7.0/lib/jsonapi/resource_serializer.rb:130:in `object_hash'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/jsonapi-resources-0.7.0/lib/jsonapi/resource_serializer.rb:102:in `block in process_primary'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/jsonapi-resources-0.7.0/lib/jsonapi/resource_serializer.rb:96:in `each'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/jsonapi-resources-0.7.0/lib/jsonapi/resource_serializer.rb:96:in `process_primary'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/jsonapi-resources-0.7.0/lib/jsonapi/resource_serializer.rb:39:in `serialize_to_hash'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/jsonapi-resources-0.7.0/lib/jsonapi/response_document.rb:111:in `results_to_hash'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/jsonapi-resources-0.7.0/lib/jsonapi/response_document.rb:11:in `contents'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/jsonapi-resources-0.7.0/lib/jsonapi/acts_as_resource_controller.rb:155:in `render_results'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/jsonapi-resources-0.7.0/lib/jsonapi/acts_as_resource_controller.rb:64:in `process_request'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/jsonapi-resources-0.7.0/lib/jsonapi/acts_as_resource_controller.rb:13:in `index'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/actionpack-4.1.14/lib/action_controller/metal/implicit_render.rb:4:in `send_action'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/actionpack-4.1.14/lib/abstract_controller/base.rb:189:in `process_action'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/actionpack-4.1.14/lib/action_controller/metal/rendering.rb:10:in `process_action'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/actionpack-4.1.14/lib/abstract_controller/callbacks.rb:20:in `block in process_action'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/activesupport-4.1.14/lib/active_support/callbacks.rb:113:in `call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/activesupport-4.1.14/lib/active_support/callbacks.rb:113:in `call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/activesupport-4.1.14/lib/active_support/callbacks.rb:552:in `block (2 levels) in compile'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/activesupport-4.1.14/lib/active_support/callbacks.rb:502:in `call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/activesupport-4.1.14/lib/active_support/callbacks.rb:502:in `call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/activesupport-4.1.14/lib/active_support/callbacks.rb:86:in `run_callbacks'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/actionpack-4.1.14/lib/abstract_controller/callbacks.rb:19:in `process_action'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/actionpack-4.1.14/lib/action_controller/metal/rescue.rb:29:in `process_action'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/actionpack-4.1.14/lib/action_controller/metal/instrumentation.rb:32:in `block in process_action'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/activesupport-4.1.14/lib/active_support/notifications.rb:159:in `block in instrument'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/activesupport-4.1.14/lib/active_support/notifications/instrumenter.rb:20:in `instrument'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/activesupport-4.1.14/lib/active_support/notifications.rb:159:in `instrument'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/actionpack-4.1.14/lib/action_controller/metal/instrumentation.rb:30:in `process_action'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/actionpack-4.1.14/lib/action_controller/metal/params_wrapper.rb:250:in `process_action'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.14/lib/active_record/railties/controller_runtime.rb:18:in `process_action'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/actionpack-4.1.14/lib/abstract_controller/base.rb:136:in `process'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/actionview-4.1.14/lib/action_view/rendering.rb:30:in `process'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/actionpack-4.1.14/lib/action_controller/metal.rb:196:in `dispatch'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/actionpack-4.1.14/lib/action_controller/metal/rack_delegation.rb:13:in `dispatch'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/actionpack-4.1.14/lib/action_controller/metal.rb:232:in `block in action'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/actionpack-4.1.14/lib/action_dispatch/routing/route_set.rb:82:in `call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/actionpack-4.1.14/lib/action_dispatch/routing/route_set.rb:82:in `dispatch'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/actionpack-4.1.14/lib/action_dispatch/routing/route_set.rb:50:in `call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/actionpack-4.1.14/lib/action_dispatch/journey/router.rb:73:in `block in call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/actionpack-4.1.14/lib/action_dispatch/journey/router.rb:59:in `each'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/actionpack-4.1.14/lib/action_dispatch/journey/router.rb:59:in `call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/actionpack-4.1.14/lib/action_dispatch/routing/route_set.rb:692:in `call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/rack-1.5.5/lib/rack/etag.rb:23:in `call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/rack-1.5.5/lib/rack/conditionalget.rb:25:in `call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/rack-1.5.5/lib/rack/head.rb:11:in `call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/actionpack-4.1.14/lib/action_dispatch/middleware/params_parser.rb:27:in `call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/actionpack-4.1.14/lib/action_dispatch/middleware/flash.rb:254:in `call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/rack-1.5.5/lib/rack/session/abstract/id.rb:225:in `context'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/rack-1.5.5/lib/rack/session/abstract/id.rb:220:in `call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/actionpack-4.1.14/lib/action_dispatch/middleware/cookies.rb:562:in `call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.14/lib/active_record/query_cache.rb:36:in `call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.14/lib/active_record/connection_adapters/abstract/connection_pool.rb:621:in `call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.14/lib/active_record/migration.rb:380:in `call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/actionpack-4.1.14/lib/action_dispatch/middleware/callbacks.rb:29:in `block in call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/activesupport-4.1.14/lib/active_support/callbacks.rb:82:in `run_callbacks'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/actionpack-4.1.14/lib/action_dispatch/middleware/callbacks.rb:27:in `call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/actionpack-4.1.14/lib/action_dispatch/middleware/reloader.rb:73:in `call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/actionpack-4.1.14/lib/action_dispatch/middleware/remote_ip.rb:76:in `call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/actionpack-4.1.14/lib/action_dispatch/middleware/debug_exceptions.rb:17:in `call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/actionpack-4.1.14/lib/action_dispatch/middleware/show_exceptions.rb:30:in `call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/railties-4.1.14/lib/rails/rack/logger.rb:38:in `call_app'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/railties-4.1.14/lib/rails/rack/logger.rb:20:in `block in call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/activesupport-4.1.14/lib/active_support/tagged_logging.rb:68:in `block in tagged'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/activesupport-4.1.14/lib/active_support/tagged_logging.rb:26:in `tagged'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/activesupport-4.1.14/lib/active_support/tagged_logging.rb:68:in `tagged'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/railties-4.1.14/lib/rails/rack/logger.rb:20:in `call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/actionpack-4.1.14/lib/action_dispatch/middleware/request_id.rb:21:in `call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/rack-1.5.5/lib/rack/methodoverride.rb:21:in `call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/rack-1.5.5/lib/rack/runtime.rb:17:in `call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/activesupport-4.1.14/lib/active_support/cache/strategy/local_cache_middleware.rb:26:in `call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/rack-1.5.5/lib/rack/lock.rb:17:in `call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/actionpack-4.1.14/lib/action_dispatch/middleware/static.rb:84:in `call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/rack-1.5.5/lib/rack/sendfile.rb:112:in `call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/railties-4.1.14/lib/rails/engine.rb:514:in `call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/railties-4.1.14/lib/rails/application.rb:144:in `call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/rack-1.5.5/lib/rack/lock.rb:17:in `call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/rack-1.5.5/lib/rack/content_length.rb:14:in `call'",
               "/Users/******/.rvm/gems/ruby-2.1.2/gems/rack-1.5.5/lib/rack/handler/webrick.rb:60:in `service'",
               "/Users/******/.rvm/rubies/ruby-2.1.2/lib/ruby/2.1.0/webrick/httpserver.rb:138:in `service'",
               "/Users/******/.rvm/rubies/ruby-2.1.2/lib/ruby/2.1.0/webrick/httpserver.rb:94:in `run'",
               "/Users/******/.rvm/rubies/ruby-2.1.2/lib/ruby/2.1.0/webrick/server.rb:295:in `block in start_thread'"
            ]
         }
      }
   ]
}
will3216 commented 8 years ago

Here is a fix for this issue

https://github.com/cerebris/jsonapi-resources/pull/684