0

When I assign a database find to an instance variable in Rails, why do future requests to that variable also hit the database? Can this be avoided?

For example, I have 3 models: User, Resource, Opinion, with has_many :through on Opinion

@opinions = current_user.opinions # pulls in all of the user's opinions, which include respective resource ids
1. Calling for resource_id directly does not hit the database:
@opinions.each do |opinion|
  opinion.resource_id  # does not hit the database (as expected)
end
2. Performing a query does hit the database (even though variable has been assigned):
@opinions.find_by_resource_id(1) # DOES hit the database

Why does #2 hit the database? Is there a way to perform the same find without hitting the database?

The information is already contained in the @opinions variable, so a db call does not seem necessary.

Tyler
  • 11,272
  • 9
  • 65
  • 105
  • What are you actually trying to do in #2? Are you trying to extract the opinion in that collection with the specific id? – jstim Sep 28 '12 at 17:39
  • jstim: yes, that's what I want to do. I'm looping through a subset of the Resource model and want to display the user's opinion for each one. – Tyler Sep 28 '12 at 18:13

1 Answers1

0

If you don't need anything else in the @opinions array, I would scope your original query to only include opinions with that resource_id

@opinions = current_user.opinions.where("resource_id = ?", resource_id)

If you already have @opinions and just want to create a new array of objects that match for a specific key/value:

@opinions_with_resource_id = @opinions.select { |opinion| opinion.resource_id == 1234 }

Check out this other answer for another explanation or if you want to split the answer into multiple arrays.

Thoughts

Comment on your last piece of code

Methods like you called find_by_* are dynamic finders that use method_missing to hit the database and look inside of the column specified by the *.

Remaining comments from previous answer

If this object will ever need to access data on the Resource model, don't forget about the #includes() method, which will keep you from having to run additional queries down the road.

@opinions = current_user.opinions.includes(:resources)

See http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations

jstim
  • 2,432
  • 1
  • 21
  • 28
  • "All #1 is doing is pulling the array of Opinions, which contains the foreign_key to Resources that haven't been loaded yet." <-- The array of Opinions has all the information I need, which is the complete set of information for each Opinion (not just foreign_key as you mentioned). Also I am not trying to load any Resource objects; I am trying to find Opinions that have a given resource_id. – Tyler Oct 01 '12 at 17:03
  • ah ok, so say a user has 5 Opinions and 3 of them have resource_id equal to 124. You would like the query to pull back the 3 opinions with that value. – jstim Oct 02 '12 at 17:59
  • I have 1 user with several favorite resources, and I want to find the single opinion (which includes commentary, whether it is a favorite, etc) for each resource. I already have all of the user's opinions in the @opinions variable, and I just want to pull information from this variable without hitting the database. – Tyler Oct 02 '12 at 18:38
  • isn't the `include` method meant to be plural: `includes`? – BenKoshy Feb 23 '21 at 04:46
  • @BKSpurgeon yup, I think you're right. updated. – jstim Feb 24 '21 at 21:18