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.

Peter DeWeese
  • 18,141
  • 8
  • 79
  • 101
joepour
  • 6,831
  • 10
  • 32
  • 29
  • Seems like a good use case for `partition` - [example here](https://stackoverflow.com/questions/10069993/rails-filtering-array-of-objects-by-attribute-value/56565339#answer-56565339). – SRack Jun 14 '19 at 08:46

6 Answers6

191

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
Vik
  • 5,931
  • 3
  • 31
  • 38
  • 2
    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. http://ruby-doc.org/core-1.9.3/Enumerable.html#method-i-partition – Vlad Jun 20 '16 at 21:04
  • Thanks @Vlad, thats cool, but it support only if we need to collect only two things from object. – Vik Aug 01 '16 at 07:52
  • 1
    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. – Vlad Aug 01 '16 at 19:04
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.

Soundar Rathinasamy
  • 6,658
  • 6
  • 29
  • 47
2

have you tried eager loading?

@attachments = Job.includes(:attachments).find(1).attachments
tommasop
  • 18,495
  • 2
  • 40
  • 51
Siwei
  • 19,858
  • 7
  • 75
  • 95
  • Sorry I am not being clear: how do I filter by an object attribute's value without looping through the array? – joepour Apr 09 '12 at 07:50
  • 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? – Siwei Apr 09 '12 at 08:46
  • 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 – joepour Apr 09 '12 at 08:55
2

For newer versions of ruby, you can use

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

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

prajeesh
  • 2,202
  • 6
  • 34
  • 59
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.

SRack
  • 11,495
  • 5
  • 47
  • 60
0

You can filter using where

Job.includes(:attachments).where(file_type: ["logo", "image"])
Darlan Dieterich
  • 2,369
  • 1
  • 27
  • 37