Get all ids from a collection - ruby-on-rails

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)

Related

How to query to return the most common foreign key in a join table with rails

I have 3 models. Project, ProjectMaterial, and Material
A Project has_many ProjectMaterials and many Materials through ProjectMaterials.
This is bidirectional, with ProjectMaterial acting as a join table with user-submittable attributes.
I'd like to query the ProjectMaterial model to find the most frequent value of material_id. This way I can use it to find the most frequently used material.
Any help with a query would be greatly appreciated. I'm stuck. Thanks in advance!
You can chain group, count and sort methods on your ActiveRecord query like this:
ProjectMaterial.group(:material_id).count.values.sort.last
The first part ProjectMaterial.group(:material_id).count gives you the hash of each {material_id0 => rows_count0, material_id1 => rows_count1, ...}. Then, you can just get the values of the hash in an array, sort it and get the last item.
One way could be pluck ids to get the array, then count the most frequent.
ids = ProjectMaterial.pluck[:material_id]
For example: Ruby: How to find item in array which has the most occurrences?
Or better, by query to get a hash with counts:
counts = ProjectMaterial.group(:material_id).count
Once you know that you get a hash, you can sort by any ruby method, picking the most frequent or the n most frequent. Example of sorting:
counts.sort_by { |_, v| v }

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))

Rails: select unique values from a column

I already have a working solution, but I would really like to know why this doesn't work:
ratings = Model.select(:rating).uniq
ratings.each { |r| puts r.rating }
It selects, but don't print unique values, it prints all values, including the duplicates. And it's in the documentation: http://guides.rubyonrails.org/active_record_querying.html#selecting-specific-fields
Model.select(:rating)
The result of this is a collection of Model objects. Not plain ratings. And from uniq's point of view, they are completely different. You can use this:
Model.select(:rating).map(&:rating).uniq
or this (most efficient):
Model.uniq.pluck(:rating)
Rails 5+
Model.distinct.pluck(:rating)
Update
Apparently, as of rails 5.0.0.1, it works only on "top level" queries, like above. Doesn't work on collection proxies ("has_many" relations, for example).
Address.distinct.pluck(:city) # => ['Moscow']
user.addresses.distinct.pluck(:city) # => ['Moscow', 'Moscow', 'Moscow']
In this case, deduplicate after the query
user.addresses.pluck(:city).uniq # => ['Moscow']
If you're going to use Model.select, then you might as well just use DISTINCT, as it will return only the unique values. This is better because it means it returns less rows and should be slightly faster than returning a number of rows and then telling Rails to pick the unique values.
Model.select('DISTINCT rating')
Of course, this is provided your database understands the DISTINCT keyword, and most should.
This works too.
Model.pluck("DISTINCT rating")
If you want to also select extra fields:
Model.select('DISTINCT ON (models.ratings) models.ratings, models.id').map { |m| [m.id, m.ratings] }
Model.uniq.pluck(:rating)
# SELECT DISTINCT "models"."rating" FROM "models"
This has the advantages of not using sql strings and not instantiating models
Model.select(:rating).uniq
This code works as 'DISTINCT' (not as Array#uniq) since rails 3.2
Model.select(:rating).distinct
Another way to collect uniq columns with sql:
Model.group(:rating).pluck(:rating)
If I am going right to way then :
Current query
Model.select(:rating)
is returning array of object and you have written query
Model.select(:rating).uniq
uniq is applied on array of object and each object have unique id. uniq is performing its job correctly because each object in array is uniq.
There are many way to select distinct rating :
Model.select('distinct rating').map(&:rating)
or
Model.select('distinct rating').collect(&:rating)
or
Model.select(:rating).map(&:rating).uniq
or
Model.select(:name).collect(&:rating).uniq
One more thing, first and second query : find distinct data by SQL query.
These queries will considered "london" and "london " same means it will neglect to space, that's why it will select 'london' one time in your query result.
Third and forth query:
find data by SQL query and for distinct data applied ruby uniq mehtod.
these queries will considered "london" and "london " different, that's why it will select 'london' and 'london ' both in your query result.
please prefer to attached image for more understanding and have a look on "Toured / Awaiting RFP".
If anyone is looking for the same with Mongoid, that is
Model.distinct(:rating)
Some answers don't take into account the OP wants a array of values
Other answers don't work well if your Model has thousands of records
That said, I think a good answer is:
Model.uniq.select(:ratings).map(&:ratings)
=> "SELECT DISTINCT ratings FROM `models` "
Because, first you generate a array of Model (with diminished size because of the select), then you extract the only attribute those selected models have (ratings)
You can use the following Gem: active_record_distinct_on
Model.distinct_on(:rating)
Yields the following query:
SELECT DISTINCT ON ( "models"."rating" ) "models".* FROM "models"
In my scenario, I wanted a list of distinct names after ordering them by their creation date, applying offset and limit. Basically a combination of ORDER BY, DISTINCT ON
All you need to do is put DISTINCT ON inside the pluck method, like follow
Model.order("name, created_at DESC").offset(0).limit(10).pluck("DISTINCT ON (name) name")
This would return back an array of distinct names.
Model.pluck("DISTINCT column_name")

How do I match two arrays with duplicates in Ruby?

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..)

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...

Resources