Exclude conditions in active relations, how? - ruby-on-rails

I am seeking a way to do the opposite of :
Model.where(:name => 'julian')
Something like :
Model.where(:name => is_not('julian'))
I have found this post. But, find(:all, :conditions => {}) is now deprecated in rails 3 and moreover, I think a cleaner way to do this must exist.
Any suggestion ?

The simple answer is:
Model.where('models.name != ?', 'julian')
Unless, you want to get into building actual Arel conditions, in which case you would do something like:
Model.where(Model.arel_table[:name].not_eq('julian'))

Also, you can use meta_where gem: https://github.com/ernie/meta_where

Related

Ruby on Rails querying over dateTime

I need to rewrite some DB query so my Rails app can run on Heroku. In one query I need to fetch all entries which are not older then one day. I tried it like this=>
Sendnotifications.where(:actionID => actionID.to_s,
:time => ['? >= time => ?', DateTime.now,DateTime.now-1]).all.count
But this does not work, I guess because of a wrong syntax. So the question is how can I solve this query problem?
Thanks :D
No need to specify two boundaries, one will be more than enough (this way the code will look cleaner):
Sendnotifications.where('time > ?', DateTime.now - 1) #chain all your other methods
Sendnotifications.where(actionID: actionID.to_s)
.where("time between ? and ?", DateTime.now, DateTime.now-1)
.count

Active Record Query used "NOT IN" [duplicate]

I'm hoping there is a easy solution that doesn't involve find_by_sql, if not then I guess that will have to work.
I found this article which references this:
Topic.find(:all, :conditions => { :forum_id => #forums.map(&:id) })
which is the same as
SELECT * FROM topics WHERE forum_id IN (<#forum ids>)
I am wondering if there is a way to do NOT IN with that, like:
SELECT * FROM topics WHERE forum_id NOT IN (<#forum ids>)
Rails 4+:
Article.where.not(title: ['Rails 3', 'Rails 5'])
Rails 3:
Topic.where('id NOT IN (?)', Array.wrap(actions))
Where actions is an array with: [1,2,3,4,5]
FYI, In Rails 4, you can use not syntax:
Article.where.not(title: ['Rails 3', 'Rails 5'])
Using Arel:
topics=Topic.arel_table
Topic.where(topics[:forum_id].not_in(#forum_ids))
or, if preferred:
topics=Topic.arel_table
Topic.where(topics[:forum_id].in(#forum_ids).not)
and since rails 4 on:
topics=Topic.arel_table
Topic.where.not(topics[:forum_id].in(#forum_ids))
Please notice that eventually you do not want the forum_ids to be the ids list, but rather a subquery, if so then you should do something like this before getting the topics:
#forum_ids = Forum.where(/*whatever conditions are desirable*/).select(:id)
in this way you get everything in a single query: something like:
select * from topic
where forum_id in (select id
from forum
where /*whatever conditions are desirable*/)
Also notice that eventually you do not want to do this, but rather a join - what might be more efficient.
You can try something like:
Topic.find(:all, :conditions => ['forum_id not in (?)', #forums.map(&:id)])
You might need to do #forums.map(&:id).join(','). I can't remember if Rails will the argument into a CSV list if it is enumerable.
You could also do this:
# in topic.rb
named_scope :not_in_forums, lambda { |forums| { :conditions => ['forum_id not in (?)', forums.select(&:id).join(',')] }
# in your controller
Topic.not_in_forums(#forums)
To expand on #Trung Lê answer, in Rails 4 you can do the following:
Topic.where.not(forum_id:#forums.map(&:id))
And you could take it a step further.
If you need to first filter for only published Topics and then filter out the ids you don't want, you could do this:
Topic.where(published:true).where.not(forum_id:#forums.map(&:id))
Rails 4 makes it so much easier!
The accepted solution fails if #forums is empty. To workaround this I had to do
Topic.find(:all, :conditions => ['forum_id not in (?)', (#forums.empty? ? '' : #forums.map(&:id))])
Or, if using Rails 3+:
Topic.where( 'forum_id not in (?)', (#forums.empty? ? '' : #forums.map(&:id)) ).all
Most of the answers above should suffice you but if you are doing a lot more of such predicate and complex combinations check out Squeel. You will be able to doing something like:
Topic.where{{forum_id.not_in => #forums.map(&:id)}}
Topic.where{forum_id.not_in #forums.map(&:id)}
Topic.where{forum_id << #forums.map(&:id)}
You may want to have a look at the meta_where plugin by Ernie Miller. Your SQL statement:
SELECT * FROM topics WHERE forum_id NOT IN (<#forum ids>)
...could be expressed like this:
Topic.where(:forum_id.nin => #forum_ids)
Ryan Bates of Railscasts created a nice screencast explaining MetaWhere.
Not sure if this is what you're looking for but to my eyes it certainly looks better than an embedded SQL query.
The original post specifically mentions using numeric IDs, but I came here looking for the syntax for doing a NOT IN with an array of strings.
ActiveRecord will handle that nicely for you too:
Thing.where(['state NOT IN (?)', %w{state1 state2}])
Can these forum ids be worked out in a pragmatic way? e.g. can you find these forums somehow - if that is the case you should do something like
Topic.all(:joins => "left join forums on (forums.id = topics.forum_id and some_condition)", :conditions => "forums.id is null")
Which would be more efficient than doing an SQL not in
This way optimizes for readability, but it's not as efficient in terms of database queries:
# Retrieve all topics, then use array subtraction to
# find the ones not in our list
Topic.all - #forums.map(&:id)
You can use sql in your conditions:
Topic.find(:all, :conditions => [ "forum_id NOT IN (?)", #forums.map(&:id)])
Piggybacking off of jonnii:
Topic.find(:all, :conditions => ['forum_id not in (?)', #forums.pluck(:id)])
using pluck rather than mapping over the elements
found via railsconf 2012 10 things you did not know rails could do
When you query a blank array add "<< 0" to the array in the where block so it doesn't return "NULL" and break the query.
Topic.where('id not in (?)',actions << 0)
If actions could be an empty or blank array.
Here is a more complex "not in" query, using a subquery in rails 4 using squeel. Of course very slow compared to the equivalent sql, but hey, it works.
scope :translations_not_in_english, ->(calmapp_version_id, language_iso_code){
join_to_cavs_tls_arr(calmapp_version_id).
joins_to_tl_arr.
where{ tl1.iso_code == 'en' }.
where{ cavtl1.calmapp_version_id == my{calmapp_version_id}}.
where{ dot_key_code << (Translation.
join_to_cavs_tls_arr(calmapp_version_id).
joins_to_tl_arr.
where{ tl1.iso_code == my{language_iso_code} }.
select{ "dot_key_code" }.all)}
}
The first 2 methods in the scope are other scopes which declare the aliases cavtl1 and tl1. << is the not in operator in squeel.
Hope this helps someone.
If someone want to use two or more conditions, you can do that:
your_array = [1,2,3,4]
your_string = "SOMETHING"
YourModel.where('variable1 NOT IN (?) AND variable2=(?)',Array.wrap(your_array),your_string)

How to express a NOT IN query with ActiveRecord/Rails?

I'm hoping there is a easy solution that doesn't involve find_by_sql, if not then I guess that will have to work.
I found this article which references this:
Topic.find(:all, :conditions => { :forum_id => #forums.map(&:id) })
which is the same as
SELECT * FROM topics WHERE forum_id IN (<#forum ids>)
I am wondering if there is a way to do NOT IN with that, like:
SELECT * FROM topics WHERE forum_id NOT IN (<#forum ids>)
Rails 4+:
Article.where.not(title: ['Rails 3', 'Rails 5'])
Rails 3:
Topic.where('id NOT IN (?)', Array.wrap(actions))
Where actions is an array with: [1,2,3,4,5]
FYI, In Rails 4, you can use not syntax:
Article.where.not(title: ['Rails 3', 'Rails 5'])
Using Arel:
topics=Topic.arel_table
Topic.where(topics[:forum_id].not_in(#forum_ids))
or, if preferred:
topics=Topic.arel_table
Topic.where(topics[:forum_id].in(#forum_ids).not)
and since rails 4 on:
topics=Topic.arel_table
Topic.where.not(topics[:forum_id].in(#forum_ids))
Please notice that eventually you do not want the forum_ids to be the ids list, but rather a subquery, if so then you should do something like this before getting the topics:
#forum_ids = Forum.where(/*whatever conditions are desirable*/).select(:id)
in this way you get everything in a single query: something like:
select * from topic
where forum_id in (select id
from forum
where /*whatever conditions are desirable*/)
Also notice that eventually you do not want to do this, but rather a join - what might be more efficient.
You can try something like:
Topic.find(:all, :conditions => ['forum_id not in (?)', #forums.map(&:id)])
You might need to do #forums.map(&:id).join(','). I can't remember if Rails will the argument into a CSV list if it is enumerable.
You could also do this:
# in topic.rb
named_scope :not_in_forums, lambda { |forums| { :conditions => ['forum_id not in (?)', forums.select(&:id).join(',')] }
# in your controller
Topic.not_in_forums(#forums)
To expand on #Trung Lê answer, in Rails 4 you can do the following:
Topic.where.not(forum_id:#forums.map(&:id))
And you could take it a step further.
If you need to first filter for only published Topics and then filter out the ids you don't want, you could do this:
Topic.where(published:true).where.not(forum_id:#forums.map(&:id))
Rails 4 makes it so much easier!
The accepted solution fails if #forums is empty. To workaround this I had to do
Topic.find(:all, :conditions => ['forum_id not in (?)', (#forums.empty? ? '' : #forums.map(&:id))])
Or, if using Rails 3+:
Topic.where( 'forum_id not in (?)', (#forums.empty? ? '' : #forums.map(&:id)) ).all
Most of the answers above should suffice you but if you are doing a lot more of such predicate and complex combinations check out Squeel. You will be able to doing something like:
Topic.where{{forum_id.not_in => #forums.map(&:id)}}
Topic.where{forum_id.not_in #forums.map(&:id)}
Topic.where{forum_id << #forums.map(&:id)}
You may want to have a look at the meta_where plugin by Ernie Miller. Your SQL statement:
SELECT * FROM topics WHERE forum_id NOT IN (<#forum ids>)
...could be expressed like this:
Topic.where(:forum_id.nin => #forum_ids)
Ryan Bates of Railscasts created a nice screencast explaining MetaWhere.
Not sure if this is what you're looking for but to my eyes it certainly looks better than an embedded SQL query.
The original post specifically mentions using numeric IDs, but I came here looking for the syntax for doing a NOT IN with an array of strings.
ActiveRecord will handle that nicely for you too:
Thing.where(['state NOT IN (?)', %w{state1 state2}])
Can these forum ids be worked out in a pragmatic way? e.g. can you find these forums somehow - if that is the case you should do something like
Topic.all(:joins => "left join forums on (forums.id = topics.forum_id and some_condition)", :conditions => "forums.id is null")
Which would be more efficient than doing an SQL not in
This way optimizes for readability, but it's not as efficient in terms of database queries:
# Retrieve all topics, then use array subtraction to
# find the ones not in our list
Topic.all - #forums.map(&:id)
You can use sql in your conditions:
Topic.find(:all, :conditions => [ "forum_id NOT IN (?)", #forums.map(&:id)])
Piggybacking off of jonnii:
Topic.find(:all, :conditions => ['forum_id not in (?)', #forums.pluck(:id)])
using pluck rather than mapping over the elements
found via railsconf 2012 10 things you did not know rails could do
When you query a blank array add "<< 0" to the array in the where block so it doesn't return "NULL" and break the query.
Topic.where('id not in (?)',actions << 0)
If actions could be an empty or blank array.
Here is a more complex "not in" query, using a subquery in rails 4 using squeel. Of course very slow compared to the equivalent sql, but hey, it works.
scope :translations_not_in_english, ->(calmapp_version_id, language_iso_code){
join_to_cavs_tls_arr(calmapp_version_id).
joins_to_tl_arr.
where{ tl1.iso_code == 'en' }.
where{ cavtl1.calmapp_version_id == my{calmapp_version_id}}.
where{ dot_key_code << (Translation.
join_to_cavs_tls_arr(calmapp_version_id).
joins_to_tl_arr.
where{ tl1.iso_code == my{language_iso_code} }.
select{ "dot_key_code" }.all)}
}
The first 2 methods in the scope are other scopes which declare the aliases cavtl1 and tl1. << is the not in operator in squeel.
Hope this helps someone.
If someone want to use two or more conditions, you can do that:
your_array = [1,2,3,4]
your_string = "SOMETHING"
YourModel.where('variable1 NOT IN (?) AND variable2=(?)',Array.wrap(your_array),your_string)

Getting the id with a rails find with :select

I have this rails find that i need to get the id as well but if i put the id in the :select wont it effect the query and is there another way to get the id
#past_requests = Request.find_all_by_artist(name, :conditions => ["showdate < ?", Time.now], :select => "distinct venue, showdate")
#past_requests = Request.find_all_by_artist(name, :group => "venue, showdate")
code long for view. i'm remove your condition. sorry about this. Hope it'll helpful for you. :)
To be fair in cases where distinct returns a single row out of maybe 5 duplicates who's to say which id out of those 5 should be displayed in your result? I'm afraid what you are asking for is not practical. Maybe you misunderstand what distinct is used for ? Give us more info pls.

Association Problem

I am having a problem with an association:
Battalion
:has_many soldiers
Soldiers
:has_many primaries
I need to do this
#bseniorleads=(#user.battalion.soldiers.find(:all, :conditions => ["seniorleader = ?", "Yes"]))
then
#seniorspouse=(#bseniorleads.primaries.find(:all, :conditions => ["relationship = ?", "Spouse"]
This gives me an undefined method for primaries, I assume because the bseniorleads is an array?
Basically I don't know how to do this they right way but I need to be able to query a group from one model that meets a condition and then take that result and find the people from another model that belong to them. Any ideas?
Thanks in advance.
You should be able to do something like this (assuming you only needed the #bseniorleads instance variable in the second query):
#senior_spouse = #user.battalion.soldiers.find(
:all,
:select => 'primaries.*',
:joins => [:primaries],
:conditions => ["seniorleader = ? and primaries.relationship = ?", "Yes", "Spouse"]
)
I haven't checked that yet, but I think it should get you pretty close.
You might want to check out these two rails guides, which certainly helped me better understand ActiveRecord associations and querying:
ActiveRecord Querying
ActiveRecord Associations

Resources