How to exclude an array of ids from query in Rails (using ActiveRecord)? - activerecord

I would like to perform an ActiveRecord query that returns all records except those records that have certain ids. The ids I would like excluded are stored in an array. So:
ids_to_exclude = [1,2,3]
array_without_excluded_ids = Item. ???
I'm not sure how to complete the second line.
Background: What I've already tried:
I'm not sure background is necessary, but I've already tried various combinations of .find and .where. For example:
array_without_excluded_ids = Item.find(:all, :conditions => { "id not IN (?)", ids_to_exclude })
array_without_excluded_ids = Item.where( "items.id not IN ?", ids_to_exclude)
These fail. This tip might be on the right track, but I have not succeeded in adapting it. Any help would be greatly appreciated.

Rails 4 solution:
ids_to_exclude = [1,2,3]
array_without_excluded_ids = Item.where.not(id: ids_to_exclude)

This should work:
ids_to_exclude = [1,2,3]
items_table = Arel::Table.new(:items)
array_without_excluded_ids = Item.where(items_table[:id].not_in ids_to_exclude)
And it's fully object-oriented with no strings :-)

You can also use Squeel gem to accomplish such query.
Documentation of it, goes here

As nslocum wrote, the following works well:
Item.where.not(id: ids_to_exclude)
If your "ids to exclude" come from a query (here with an example condition), you can even take it a step further:
Item.where.not(id: Item.where(condition: true))
This is useful if you need to filter another model:
OtherModel.where.not(item_id: Item.where(condition: true))

Related

Rails combine multiple activerecord relations

I want to union multiple active record relation
For example,
apple_companies = Company.where("name like ?","%apple%")
banana_companies = Company.where("name like ?","%banana%")
I want to combine these two relation.
not merge, merge is
apple_companies.merge(banana_companies)
=> Company.where("name like ? and name like ?", "%apple%","%banana%")
I want to
Company.where("name like ? or name like ?", "%apple%","%banana%")
afterward,
I will code
companies = Company.none
company_name_list.each do |name|
like = "%"+name+"%"
companies += Company.where("name like ?",like)
end
but code which I wrote make companies to array.....
So I cannot order and page to companies... :(
thank you
apple_companies = Company.where("name like ?","%apple%")
banana_companies = Company.where("name like ?","%banana%")
apples = apple_companies.where_values.reduce(:and)
bananas = banana_companies.where_values.reduce(:and)
Company.where(apples.or(bananas))
See ActiveRecord Arel OR condition for more examples.
The best result for this that I've come across is to grab and merge the ids of the two queries and then search for them like this:
apple_companies = Company.where("name like ?","%apple%").pluck(:id)
banana_companies = Company.where("name like ?","%banana%").pluck(:id)
ids = apple_companies + banana_companies.uniq
Company.where(id: ids)
It's four lines that seems like it should be doable in one but it works.
In this case you can use any of the other answers. However in more general cases, I strongly recommend using any_of gem. With this gem you can do:
apple_companies = Company.where("name like ?","%apple%")
banana_companies = Company.where("name like ?","%banana%")
Company.where.any_of(apple_companies, banana_companies)
There already is a pull request to add this functionality to future rails releases.
Please try using
Company.where('name LIKE ? OR name LIKE ?','%apple%', '%banana%')
OR
Company.where('name IN (?)', ['%apple%', '%banana%'])
According to your code:
names = []
company_name_list.each do |name|
names << "%"+name+"%"
end
Then you can do:
companies = Company.where('name LIKE ANY(Array[?])', names)
Late answer, but Arel will solve it.
Company.where(Company.arel_table[:name].matches_any(["%apple%", "%banana%"]))
Use
Company.where(name: IN ('%apple%','%banana%'))

Rails 4 and Mongoid: programmatically build query to search for different conditions on the same field

I'm building a advanced search functionality and, thanks to the help of some ruby fellows on SO, I've been already able to combine AND and OR conditions programmatically on different fields of the same class.
I ended up writing something similar to the accepted answer mentioned above, which I report here:
query = criteria.each_with_object({}) do |(field, values), query|
field = field.in if(values.is_a?(Array))
query[field] = values
end
MyClass.where(query)
Now, what might happen is that someone wants to search on a certain field with multiple criteria, something like:
"all the users where names contains 'abc' but not contains 'def'"
How would you write the query above?
Please note that I already have the regexes to do what I want to (see below), my question is mainly on how to combine them together.
#contains
Regex.new('.*' + val + '.*')
#not contains
Regex.new('^((?!'+ val +').)*$')
Thanks for your time!
* UPDATE *
I was playing with the console and this is working:
MyClass.where(name: /.*abc.*/).and(name: /^((?!def).)*$/)
My question remains: how do I do that programmatically? I shouldn't end up with more than two conditions on the same field but it's something I can't be sure of.
You could use an :$and operator to combine the individual queries:
MyClass.where(:$and => [
{ name: /.*abc.*/ },
{ name: /^((?!def).)*$/ }
])
That would change the overall query builder to something like this:
components = criteria.map do |field, value|
field = field.in if(value.is_a?(Array))
{ field => value }
end
query = components.length > 1 ? { :$and => components } : components.first
You build a list of the individual components and then, at the end, either combine them with :$and or, if there aren't enough components for :$and, just unwrap the single component and call that your query.

ActiveRecord: Find through multiple instances

Say I have the following in my controller:
#category1
#category2
and I want to find all stores associated with those two categories...
#stores = #category1.stores + #category2.stores
this does work, but unfortunately returns an unaltered Array, rather than a AR::Base Array, and as such, I can't do things like pagination, scope, etc...
It seems to me like there's a built-in way of finding through multiple instance association... isn't there?
##stores = #category1.stores + #category2.stores
#if you want to call API methods you can just add conditions with the category id
#stores = Store.find(:all, :conditions => ['category_id=?', a || b])
With ActiveRecord, whenever you're finding a set of unique model objects, calling find on that model is usually your best bet.
Then all you need to do is constrain the join table with the categories you care about.
#stores = Store.all(:joins => :categories,
:conditions => ['category_stores.category_id in (?)', [#category1.id, #category2.id]])

how to say 'is not in' in an active record query

Hey I hope you can help me,
#courses = Course.where(:id != current_user.courses)
I want to get the courses, where the current user is not registered to
#courses = Course.where(:id => current_user.courses) gives me the courses where the user has registered to. But I want the opposite
Associations are just fine. I can use current_user.courses or current_user.assignments.
You probably need something like this:
#courses = Course.where("id NOT IN (?)", current_user.courses)
The reason you can't use a pure array or hash condition is because you're negating ("NOT IN") the query. As far as I know, this can't be done without using the string based syntax. If you just wanted to find matching ("IN") items, you could use the hash form:
#courses = Course.where(:id => current_user.courses)
It's the negating ("NOT IN") part that makes it tricky.
More information can be found in the Active Record Query Interface Guide.
If you wish to avoid using raw strings, you could use ARel
#courses = Course.where(Course.arel_table[:id].not_in(current_users.courses))
Unfortunately, ARel is not documented very well. I use this as a reference.

How can I retrieve a document by _id?

I'm trying to retrieve a document when I have an object id - however, the query does not work.
#collection = #db.collection('Mylist')
#result = #collection.find({"_id" => params[:id]})
I've tried variations of the query - it always yields empty - however when I try a query on the collection such as below, that would work.
#result = #collection.find({"Exist" => "True"})
Why? It is strange that complex queries work but a simple query by _id returns nothing.
If possible, I don't want to use MongoMapper.
Thanks
Found it - you need to wrap it like this -
find({"_id" => Mongo::ObjectId(params[:id])})
find(:_id => BSON::ObjectID(params[:id])
This would also work:
#coll.find_one(ObjectID.from_string(params[:id]))

Resources