How do I match two arrays with duplicates in Ruby? - ruby-on-rails

I know this removes duplicates :
#email.distributions.map(&:zip_code) & CardSignup.all.map(&:zip_code)
But I want to do the same thing, where I find anything that matches, but it also shows me duplicates.
Any ideas?
I am trying to find the amount of people who signed up for a card that have a matching zip code to a zip code preference I placed.

Array#reject to the rescue, again! Like Array#map, it accepts blocks, allowing you to do something like this:
zip_codes = CardSignup.all.map(&:zip_code)
#email.distributions.reject{|o| !zip_codes.include?(o.zip_code)}
Oh, but of course, if you like finding more elegant ways, always consider the operators like you already did. & will return a new array with objects that are in both, | will join and remove duplicates.
ruby-1.9.2-p0 > [1,2] | [2,3]
=> [1, 2, 3]
ruby-1.9.2-p0 > [1,2] & [2,3]
=> [2]
Edit: as Tokland said in the comments, since this is applied on a Rails Model, you may want to consider doing it as a select. Like this -
zip_codes = CardSignup.all.map(&:zip_code)
#email.distributions.where('zip_code IN (?)', zip_codes)
Or, do it with an INNER JOIN. Doesn't look as pretty though.
#email.distributions.joins('INNER JOIN card_signups ON card_signups.zip_code = email_distributions.zip_code').all
(If the table for #email.distributions is email_distributions..)

Related

Subtracting two queries from each other in rails

I'm new to rails, and even ruby, so I'm having some trouble figuring this out.
I want to find the difference between two queries. This particular query should return a single record, since I've set it up such that Recipe is missing one of the IDs from recipes.
Current code:
q = Recipe.all - Recipe.where(recipe_id: recipes)
Where recipes is an array of IDs.
From my limited understanding of the language, this would work if both Recipe.all and Recipe.where both returned arrays.
I've spent some time searching the web, with nothing coming up to aid me.
Other things I've tried:
q = [Recipe.all] - [Recipe.where(recipe_id: recipes)]
q = Recipe.where.not(recipe_id: recipes) # Wouldn't work because the array is the one with the extra id
Though neither proved helpful.
Try this:
q = Recipe.where('recipe_id NOT IN (?)', recipes)
Turns out I was asking the wrong question.
Since the array of IDs is the one with extra elements, not the database query, I should have been comparing the difference of it to the query.
My answer is as follows:
q = recipes - Recipe.where(recipe_id: recipes).ids
Which returns the missing IDs.
If you are using Rails 4, you can use the not query method
q = Recipe.where.not(id: recipes)
this will generator following query:
SELECT "recipes".* FROM "recipes" WHERE ("recipes"."id" NOT IN (12, 8, 11, 5, 6, 7))

Does ActiveRecord find_all_by_X preserve order? If not, what should be used instead?

Suppose I have a non-empty array ids of Thing object ids and I want to find the corresponding objects using things = Thing.find_all_by_id(ids). My impression is that things will not necessarily have an ordering analogous to that of ids.
Is my impression correct?
If so, what can I used instead of find_all_by_id that preserves order and doesn't hit the database unnecessarily many times?
Yes
Use Array#sort
Check it out:
Thing.where(:id => ids).sort! {|a, b| ids.index(a.id) <=> ids.index(b.id)}
where(:id => ids) will generate a query using an IN(). Then the sort! method will iterate through the query results and compare the positions of the id's in the ids array.
#tybro0103's answer will work, but gets inefficient for a large N of ids. In particular, Array#index is linear in N. Hashing works better for large N, as in
by_id = Hash[Thing.where(:id => ids).map{|thing| [thing.id, thing]}]
ids.map{|i| by_id[i]}
You can even use this technique to arbitrarily sort by any not-necessarily unique attribute, as in
by_att = Thing.where(:att => atts).group_by(&:att)
atts.flat_map{|a| by_att[a]}
find_all_by_id is deprecated in rails 4, which is why I use where here, but the behavior is the same.

Get all ids from a collection

I got my collection items like this :
hotels = Hotel.where('selection = ?', 1).limit(4)
How can I get all ids of this items without a loop? Can i use something like :
hotels.ids ?
Thank you
What about trying hotels.map(&:id) or hotels.map{|h| h.id }?
They both mean the same thing to Ruby, the first one is nicer to accustomed ruby-ists usually, whilst the second one is easier to understand for beginners.
If you only need an array with all the ids you should use pluck as it makes the right query and you don't have to use any ruby. Besides it won't have to instantiate a Hotel object for each record returned from the DB. (way faster).
Hotel.where(selection: 1).pluck(:id)
# SELECT hotels.id FROM hotels WHERE hotels.selection = 1
# => [2, 3]
If you are using Rails > 4, you can use the ids method:
Person.ids # SELECT people.id from people
More info: http://apidock.com/rails/ActiveRecord/Calculations/ids
You can also pull just the id's.
hotels.select(:id).where(selection: 1)

rails: get items with associated with multiple given tags

noob question here. I have a has_many :through relationship between items and tags. Finding all items associated with a given tag is simple enough:
things=Tag.find(1).items
But what if I want to find all items associated with more than one given tag? I was thinking something like:
things=Tag.find(1).items
Tag.find(2).things # wrong, but you get the idea
You can use the array union operator.
things = Tag.find(1).items | Tag.find(2).items
That will create an object for every item for both tags, which could be way too much depending on what you're trying to do. If you want something a little more scalable, you can do the lookup on the join table.
things = ItemTags.find_by_sql("
SELECT item_id, COUNT(tag_id) AS tag_count
FROM item_tags
WHERE tag_id IN (1, 2)
GROUP_BY item_id
HAVING tag_count = 2;
").map(&:item)
Just wrote that in browser, so it could be completely wrong. Also, there is probably a way to do that with activerecord finders that would be nicer that a find_by_sql.
things = Things.where(:tag => [1, 2])
things = Things.where('tag in :tags', [1, 2])
etc...

Ruby/Rails Collection to Collection

I have a two tables joined with a join table - this is just pseudo code:
Library
Book
LibraryBooks
What I need to do is if i have the id of a library, i want to get all the libraries that all the books that this library has are in.
So if i have Library 1, and Library 1 has books A and B in them, and books A and B are in Libraries 1, 2, and 3, is there an elegant (one line) way todo this in rails?
I was thinking:
l = Library.find(1)
allLibraries = l.books.libraries
But that doesn't seem to work. Suggestions?
l = Library.find(:all, :include => :books)
l.books.map { |b| b.library_ids }.flatten.uniq
Note that map(&:library_ids) is slower than map { |b| b.library_ids } in Ruby 1.8.6, and faster in 1.9.0.
I should also mention that if you used :joins instead of include there, it would find the library and related books all in the same query speeding up the database time. :joins will only work however if a library has books.
Perhaps:
l.books.map {|b| b.libraries}
or
l.books.map {|b| b.libraries}.flatten.uniq
if you want it all in a flat array.
Of course, you should really define this as a method on Library, so as to uphold the noble cause of encapsulation.
If you want a one-dimensional array of libraries returned, with duplicates removed.
l.books.map{|b| b.libraries}.flatten.uniq
One problem with
l.books.map{|b| b.libraries}.flatten.uniq
is that it will generate one SQL call for each book in l. A better approach (assuming I understand your schema) might be:
LibraryBook.find(:all, :conditions => ['book_id IN (?)', l.book_ids]).map(&:library_id).uniq

Resources