0

I am struggling to find the best way to render records. So far, I did it the following way but, despite having includes when I fetch the main object, I get tons of DB queries when calling as_json for the child record that was included. What am I missing? Is there even a better way too do what I want to do?

I don't see how to have a better render since I want to decide what attributes and methods to serialise and use custom scopes on arrays of associate records.

  • My controller
def show
  # The include below seems to be useless, the DB is queried again on render.
  @grandParent = GrandParent.includes(parents: { children: %i[grand_children friends] })
    .find_by_name(params[:name])

  return head :not_found unless @grandParent

  render json: grand_parent_as_json, status: :ok
end

private

def grand_parent_as_json
  json = @grandParent.as_json(
    only: %i[attr1 attr2],
    methods: %i[meth1 meth2]
  )
  # I don't see a better way to render it since I want to use a custom scope on parents
  json[:parents] = @grandParent.parents.ordered_by_birthdate(:desc).map do |parent|
    parent_as_json parent
  end
 
  json
end

# The include below seem to be the one responsible for querying the DB again.
def parent_as_json(parent)
  parent.as_json(
    only: %i[attr1 attr2],
    methods: %i[meth1 meth2],
    include: [
      children: {
        only: %i[attr1 attr2],
        include: [
          grand_children: { %i[attr1 attr2] }
        ]
      }
    ]
  )
end

1 Answer 1

1

Pretty sure there is a more elegant way to fix this but the issue is indeed the scope being used here:

@grandParent.parents.ordered_by_birthdate(:desc)

The reason being is the scope is guaranteed to return a new ActiveRecord::Relation which when accessed hits the DB.

It may not be the best answer but it will work by changing your initial query, to include an .order for the birthdate field.

@grandParent = GrandParent
  .includes(parents: { children: %I[grand_children friends] })
  .order("parents.birthdate DESC")
  .find_by_name(params[:name])

Then remove the .ordered_by_birthdate as you map the parent objects as they are already in the order you wanted. This has the disadvantage not using the scope ordered_by_birthdate defined on Parent. This might be ok depending on how you view responsibilities of controller vs model.

Alternatively the above code snippet could also be part part of a scope on GrandParent e.g.

class GrandParent
  scope :family_tree, -> { includes(parents: { children: %I[grand_children friends] }).order("parents.birthdate DESC") }
end

Then you could do:

GrandParent.family_tree.find_by_name(params[:name])
Sign up to request clarification or add additional context in comments.

3 Comments

I didn't think about the scope doing the includes, I'm going to check this out, thanks. In fact, the ordering scope is ordering on another associated table's attribute.
Yeah in my example it orders the associated parents by their birth date. Is that what you want to happen?
I took that example to make it simpler to understand. As I said, it's associated to another table. Let's say, for the sake of this example, that the birthdate is in the birthday_events table associated to parent. It should be ordered this way: parents.birthday_events.date DESC.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.