0

I have three models using Active Record associations:

Book Model
has_many :checkouts

User Model
has_many :checkouts

Checkout Model
belongs_to :book
belongs_to :user

In my view, I need the book, checkout, and user names from the checkouts.

By using Book.first.checkouts I get:

#<ActiveRecord::AssociationRelation
  [#<Checkout id: 30,
    checkout_date: "2017-04-13",
    return_date: nil,
    book_id: 118,
    user_id: 1,
    created_at: "2017-04-13 17:43:07",
    updated_at: "2017-04-13 17:43:07"
  >,#<Checkout id: 50,
    checkout_date: "2017-04-13",
    return_date: nil,
    book_id: 118,
    user_id: 1,
    created_at: "2017-04-14 00:33:34",
    updated_at: "2017-04-14 00:33:34">
  ]>

But, I would like to the user name, not just the id. I've tried Book.first.checkouts.map { |c| c.user.name } but that returns only the name, and I need the rest of the checkout information. Ideally, my data (converted to json) looks something like:

{
  name: "Book Name",
  checkouts:  [
    checkout_data: "Today",
    user_name: "Mary"
  ]
}

How can I add the user name to my checkout data?

5
  • Are you displaying the checkouts individually, i.e. an .each loop? You should be able to display checkout.user.name Commented Apr 25, 2017 at 17:44
  • @Okomikeruko yes, individually through an each loop. checkout.user.name works, but it only returns the user name. I need both the checkout and the user name. I'm wondering if I can add it to a virtual attribute, or something like that. Commented Apr 25, 2017 at 17:48
  • I guess I'm trying to combine the data of 2 objects: checkouts and user Commented Apr 25, 2017 at 17:54
  • Are you saying you want checkout.username to do the same thing as checkout.user.name? Commented Apr 25, 2017 at 18:11
  • 1
    If the goal is to optimize your JSON output, this thread had some answers regarding that: stackoverflow.com/questions/13678092/… Commented Apr 25, 2017 at 18:13

3 Answers 3

1

You can try this at your controller:

render json: @books, include: { checkout: { only: :checkout_date, include: { user: { only: :name } } }}

Sign up to request clarification or add additional context in comments.

1 Comment

Didn't know about this! It's really nice
0

You should preload the data to prevent (N+1) query problem, For Display possible checkouts for particular book:

book_id = <given book id>
book = Book.find(book_id)
checkouts = book.checkouts.includes(:user)
return_hash = {name: book.name, checkouts: []}
checkouts.each do |checkout|
    return_hash[:checkouts] << { checkout_data: checkout.checkout_date,
                                 user_name: checkout.user.name
                               }
end

2 Comments

This is kind of what I was trying out, but I wondered if there was a simpler way. What's the N+1 query problem?
checkout.user will fire 1 query to database for each checkout, if you use includes :user then all the corresponding users will be pre fetched in single IN query
0

To include other solutions, I found this worked pretty well:

checkouts = book.checkouts.unreturned.map do |checkout|
  checkout.attributes.merge({ user_name: checkout.user.name })
end

{ checkouts: checkouts, available: book.available?, book_id: book.id }

attributes.merge did the trick.

1 Comment

Dont use. attributes.merge in a loop, it will take hell lot of execution time as your application grows, i faced the same performance issue in my application, use ruby-prof to see the bottleneck in your code

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.