107

So I perform a query to the db and I have a complete array of objects:

@attachments = Job.find(1).attachments

Now that I have an array of objects I don't want to perform another db query, but I would like to filter the array based on the Attachment object's file_type so that I can have a list of attachments where the file type is 'logo' and then another list of attachments where the file type is 'image'

Something like this:

@logos  = @attachments.where("file_type = ?", 'logo')
@images = @attachments.where("file_type = ?", 'image')

But in memory instead of a db query.

1
  • Seems like a good use case for partition - example here. Commented Jun 14, 2019 at 8:46

6 Answers 6

192

Try :

This is fine :

@logos = @attachments.select { |attachment| attachment.file_type == 'logo' }
@images = @attachments.select { |attachment| attachment.file_type == 'image' }

but for performance wise you don't need to iterate @attachments twice :

@logos , @images = [], []
@attachments.each do |attachment|
  @logos << attachment if attachment.file_type == 'logo'
  @images << attachment if attachment.file_type == 'image'
end
Sign up to request clarification or add additional context in comments.

3 Comments

As @Vik's solution is pretty much ideal, I'll just add that in binary cases, you could use a 'partition' function to make things sweet. ruby-doc.org/core-1.9.3/Enumerable.html#method-i-partition
Thanks @Vlad, thats cool, but it support only if we need to collect only two things from object.
Yes, that's why I said "binary" :). In the question, there was apparently a choice of logo or image, so I added this for completeness.
11

If your attachments are

@attachments = Job.find(1).attachments

This will be array of attachment objects

Use select method to filter based on file_type.

@logos = @attachments.select { |attachment| attachment.file_type == 'logo' }
@images = @attachments.select { |attachment| attachment.file_type == 'image' }

This will not trigger any db query.

Comments

2

have you tried eager loading?

@attachments = Job.includes(:attachments).find(1).attachments

3 Comments

Sorry I am not being clear: how do I filter by an object attribute's value without looping through the array?
If I understood correctly, you want less db query, especially, once a query such as @attachments = Job.first.attachments executed, you want to loop the @attachments meanwhile you don't want any more db queries. is this what you want to do?
I do a db query and receive an array of objects. I then want to create two separate lists from that one array by filtering the objects based on the value of their attributes (See original post) - cheers
2

For newer versions of ruby, you can use

@logos = @attachments.filter { |attachment| attachment.file_type == 'logo' }

Reference: https://apidock.com/ruby/Array/filter

Comments

1

I'd go about this slightly differently. Structure your query to retrieve only what you need and split from there.

So make your query the following:

#                                vv or Job.find(1) vv
attachments = Attachment.where(job_id: @job.id, file_type: ["logo", "image"])
# or 
Job.includes(:attachments).where(id: your_job_id, attachments: { file_type: ["logo", "image"] })

And then partition the data:

@logos, @images = attachments.partition { |attachment| attachment.file_type == "logo" }

That will get the data you're after in a neat and efficient manner.

Comments

0

You can filter using where

Job.includes(:attachments).where(file_type: ["logo", "image"])

Comments

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.