0

I have an API with Rails 5.0.5 in API mode. On every controller request we respond with render json: using Active Model Serializers. We use postgresql as a database.

Lets suppose this is my database:

enter image description here

Given that all the queries need the user to be logged in, then i can get a current_user object:

I can get all the projects for the user from the HABTM relation with something like: render json: current_user.projects

I want to query Departments, ActivityTypes, and Status only for the projects the user belongs (without passing a params).

(In the real API there are 7 of this similar models belonging to Project, and at least 2 have at least a few hundred records. Also, the client needs to load the 7 of them when viewing a Project, to allow them to build new Activities from within the same view.)

From now i've come up with this to show on index, at least Departments from the organization users belong:

def index
    # @departments = Department.all
    @departments = Department.joins(project: 'organization').where(:project => { 'organizations' => {:id => current_user.organization.id }})

    render json: @departments
end

With makes 3 queries in the database:

Started GET "/api/v1/departments" for 127.0.0.1 at 2017-08-09 19:40:57 -0400
Processing by Api::V1::DepartmentsController#index as HTML
  User Load (0.4ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
  Organization Load (0.4ms)  SELECT  "organizations".* FROM "organizations" WHERE "organizations"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
  Department Load (6.8ms)  SELECT "departments".* FROM "departments" INNER JOIN "projects" ON "projects"."id" = "departments"."project_id" INNER JOIN "organizations" ON "organizations"."id" = "projects"."organization_id" WHERE "organizations"."id" = $1  [["id", 1]]
[active_model_serializers]   Project Load (0.5ms)  SELECT  "projects".* FROM "projects" WHERE "projects"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
[active_model_serializers]   CACHE (0.0ms)  SELECT  "projects".* FROM "projects" WHERE "projects"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
[active_model_serializers]   Project Load (0.3ms)  SELECT  "projects".* FROM "projects" WHERE "projects"."id" = $1 LIMIT $2  [["id", 3], ["LIMIT", 1]]
[active_model_serializers]   CACHE (0.0ms)  SELECT  "projects".* FROM "projects" WHERE "projects"."id" = $1 LIMIT $2  [["id", 3], ["LIMIT", 1]]
[active_model_serializers] Rendered ActiveModel::Serializer::CollectionSerializer with ActiveModelSerializers::Adapter::Attributes (21.79ms)
Completed 200 OK in 110ms (Views: 33.5ms | ActiveRecord: 20.4ms)

That's from one side.

Also, the equivalent to Activities in my real API, have in fact thousands of records for each organization, and if i can pass project_id as parameter, i can get get a more narrow subset of them, but i need to respond with a default query if no parameter is passed on.

All those queries in fact, have response times that have reached the order of several seconds in the production environment.

So, my questions when trying to optimize this are:

Is there a way to make those 3 queries only one?

Is there a way to make this default for all the #all queries of those models?

How can i respond with at least all the Activities from all the Projects the user have, if no 'project_id' is passed to Activities#index?

Or is out there some convention of how to code this better with Rails that i've been missing out?

I have also Pundit installed, but used only to authorize actions on users according to their role, i've seen it can be used to write custom scopes for models, but i have not got how to use it properly for anything complex than the "all projects for user's organization" query (use case for User with role "admin").

Also tried in the beginning to use "Apartment" gem, and separate schemas for each organization, in that case queries where simpler, but i gave me weird behaviors like randomly fail to create relations complaining some records didn't exist while creating the foreign keys. So i had to stop using it.

rccursach
  • 305
  • 3
  • 14

1 Answers1

0

I'm probably missing something here, but what is wrong with defining an instance method on @project:

def project_data
   {'project' => self,
    'departments' => departments,
    'activity_types' => activity_types,
    'status' => status
   }
end

Then in your controller:

render json: current_user.projects.map{|x| x.project_data}
guero64
  • 1,019
  • 1
  • 12
  • 18
  • wouldn't be slower to respond with all the objects mapped like that? We query every resource individually, because some aren't needed for the client UI in less than a second and are large collections. IE: when we query projects, we display that project list to select one. When you select one, a list of the project's "Activities" will be shown, and in parallel, "Department"; "ActivityType"; etc will be queried in case the user want to use a form to create a new "Activity". That won't happen between selecting the project and 200ms later, but displaying the Activities should. – rccursach Aug 10 '17 at 03:44