OR operator inside a Where Active Record Query - ruby-on-rails

So inside a Where Active Record(AR) query you can do:
game_stickers.where('stickers.name != ?', 'Ban')
But how can you test matches against multiple strings with an OR operator without doing something like:
game_stickers.where('stickers.name != ? OR stickers.name != ?', 'Ban', 'Closed')
or without reverting to something like [see note below]:
game_stickers.where.not(stickers.name: ['Ban','Closed','Block'])
NOTE:
The reason I do not want to go with the last alternative is because I'm using some joins and references in my queries that (as far as I can see) do not play nicely with this option. The context code goes something like:
game_stickers_and_stickers = game_stickers.includes(:sticker)
game_stickers_and_stickers.where('stickers.name = ?', 'Ban').references(:stickers).where(placement_side: side)
Maybe a you can advise on the optimal way to do this query.

Note: it seems to me you want an AND between those conditions, not an OR. Think about it. Anyway, try this one
game_stickers_and_stickers = game_stickers.includes(:sticker)
game_stickers_and_stickers.where.not(stickers: {name: ['Ban','Closed','Block']}).where(placement_side: side)
that condition fragment should be converted to the SQL
WHERE stickers.name NOT IN ('Ban', 'Closed', 'Block')

Related

How can I get ActiveRecord to bind in raw statements?

So I'm basically doing this:
::OuterModel.where(%{
EXISTS(SELECT * FROM inner_model
WHERE outer_model.id = inner_model.outer_model_id)
AND inner_model.parameter = ?)
}, 1)
Now the issue becomes that this does text replacement in ActiveRecord, it doesn't bind ? to 1, which in turn is rendering ActiveRecords prepared statements pretty meaningless since every query has a different value of 1.
How can I get bind on my EXIST statements?
This is also of course true when doing something simple like:
::OuterModel.where('state = ?', 'active')
The alternative here isn't to do .join or generate IN, the performance of that is much worse, or wouldn't work in my actual use-case.
If you sure that this param is safe and you did not receive it from user side - then you just can use string interpolation #{1}. If not - you can use interpolation with sanitize that value #{ActiveRecord::Base.connection.quote(1)}
In my particular case I'm able to achieve this using arel.exists, rails will then prepare and bind with the OuterModel query.
inner_models = ::InnerModel.where("outer_model.id = inner_model.outer_model_id")
.where(paramter: 1)
::OuterModel.where(inner_models.arel.exists)
Although it doesn't really answer the in-general question on binding against raw SQL.

Result check with activerecord

When we are checking one active record result is empty or not, what will be
way which have more performance.
pc_count = Property.select("id").where('property_category_id = ?', 5).limit(1)
if pc_count.blank?
#
end
if pc_count[0]
#
end
In two ways i have tried this pc_count.blank? or pc_count[0], because i heard that blank will take extra query, but when i tried that in console i couldn't see that any extra call
Try exists
which will be like.
Person.exists?(5)
Person.exists?('5')
Person.exists?(:name => "David")
Person.exists?(['name LIKE ?', "%#{query}%"])
Person.exists?
refer: http://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-exists-3F
No, both your versions do not produce another query.
It depends what you are doing with pc_count:
1.) You are using that id somewhere later, then use either of your methods (i prefer .blank?)
2.) You only need this for the this check. Then I would do
Property.select("id").where('property_category_id = ?', 5).limit(1).count
because there will be no model created and you can just test count == 0

Connecting subqueries with 'OR' in active Record / Ruby on Rails framework

Hello I struggle creating a specific query.
I have a searchfield with input parameter "{search}". I want to use this search field to find all tables which have a plate with veggies of a specific flavor or description.
I already got a tricky query which does exactly what it is supposed to do - so this works perfectly fine!
Tables.where(id: Plate.select("plate_id").where(Veggie.select("id").where('description LIKE ? OR flavor LIKE ?', "%#{search}%","%#{search}%")))
Aside from Veggies, suppose there is also a SQL-Table calles Fruits. Now i want to search for all Tables which have Plates which have Veggies OR(!!!) Fruits fitting to the description or flavor of the searchfield input.
I experimented a lot but can not find the right way.
Here is one of my experiments which does not work though:
Tables.where(id: Plate.select("plate_id").where((Veggie.select("id").where('description LIKE ? OR flavor LIKE ?', "%#{search}%","%#{search}%")).or(Fruit.select("id").where('color LIKE ? OR flavor LIKE ?', "%#{search}%","%#{search}%")))
The code above does not work! The problem is, I can not figure out how to combine those 2 subqueries. The or statement does not seem to work. Any help appreciated.
EDIT:
As has been pointed out, i now tried doing it with the UNION command. Unfortunately the .union() option with the Gem active_record_union seems to work only for rails 4+.
So i simulated a union with the following code.
def self.search2(search2)
if search2
if search2.length > 0
#Plate1 = Plate.where(veggie_id: Veggie.select("id").where('description LIKE ? OR color LIKE ?', "%#{search2}%","%#{search2}%"))
#Plate2 = Plate.where(fruit_id: Fruit.select("id").where('description LIKE ? OR color LIKE ? OR flavor LIKE ?', "%#{search2}%","%#{search2}%", "%#{search2}%" ))
#Plate12 = #Plate1+#Plate2
#table_ids = Array.new
#Plate12.each do |plate|
#table_ids.push(plate.table_id)
end
where(id: #table_ids)
else
scoped
end
else
scoped
end
end
Actually, i do believe this is very messy! It DOES work as intended, but i am a fraid it is a bit slow and unperformant. If anyone has a better suggestion i am glad about any help.

activerecord not like query

I could not find an activerecord equivalent of "Not Like". I was able to find a where.not, but that will check if a string does not match a value, as so:
User.where.not(name: 'Gabe')
is the same as:
User.where('name != ?', 'Gabe')
I was looking for a NOT LIKE, where the value is not contained in the string. The equivalent sql query would look like as follows:
SELECT * FROM users WHERE name NOT LIKE '%Gabe%'
In ActiveRecord I can currently get away with the following:
User.where("name NOT LIKE ?", "%Gabe%")
But that leaves a lot to be desired. Any new additions to Rails 4 to facilitate this?
Well, you can do something like:
User.where.not("name LIKE ?", "%Gabe%")
Note: This is only available in Rails 4.
As others have pointed out ActiveRecord does not have a nice syntax for building like statements. I would suggest using Arel as it makes the query less database platform specific (will use ilike for sqlite & like for other platforms).
User.where(User.arel_table[:name].does_not_match('%Gabe%'))
You could also implement this as a scope to contain the implementation to the model:
class User < ActiveRecord::Base
scope :not_matching,
-> (str) { where(arel_table[:name].does_not_match("%#{str}%")) }
end
Unfortunately ActiveRecord does not have a like query builder. I agree that the raw 'NOT LIKE' leaves a lot to be desired; you could make it a scope (scope :not_like, (column, val) -> { ... }), but AR itself does not do this.
Just addition to the answer of "where.not" of active record. "where.not" will exclude null values also. i.e. Query User.where.not(name: 'Gabe') will leave record with name 'Gabe' but it also exclude name column with NULL values. So in this scenario the solution would be
User.where.not(name: 'Gabe')
.or(User.where(name: nil))

Remove a 'where' clause from an ActiveRecord::Relation

I have a class method on User, that returns applies a complicated select / join / order / limit to User, and returns the relation. It also applies a where(:admin => true) clause. Is it possible to remove this one particular where statement, if I have that relation object with me?
Something like
User.complex_stuff.without_where(:admin => true)
I know this is an old question, but since rails 4 now you can do this
User.complex_stuff.unscope(where: :admin)
This will remove the where admin part of the query, if you want to unscope the whole where part unconditinoally
User.complex_stuff.unscope(:where)
ps: thanks to #Samuel for pointing out my mistake
I haven't found a way to do this. The best solution is probably to restructure your existing complex_stuff method.
First, create a new method complex_stuff_without_admin that does everything complex_stuff does except for adding the where(:admin => true). Then rewrite the complex_stuff method to call User.complex_stuff_without_admin.where(:admin => true).
Basically, just approach it from the opposite side. Add where needed, rather than taking away where not needed.
This is an old question and this doesn't answer the question per say but rewhere is a thing that exists.
From the documentation:
Allows you to change a previously set where condition for a given attribute, instead of appending to that condition.
So something like:
Person.where(name: "John Smith", status: "live").rewhere(name: "DickieBoy")
Will output:
SELECT `people`.* FROM `people` WHERE `people`.`name` = 'DickieBoy' AND `people`.`status` = 'live';
The key point being that the name column has been overwritten, but the status column has stayed.
You could do something like this (where_values holds each where query; you'd have to tweak the SQL to match the exact output of :admin => true on your system). Keep in mind this will only work if you haven't actually executed the query yet (i.e. you haven't called .all on it, or used its results in a view):
#users = User.complex_stuff
#users.where_values.delete_if { |query| query.to_sql == "\"users\".\"admin\" = 't'" }
However, I'd strongly recommend using Emily's answer of restructuring the complex_stuff method instead.
I needed to do this (Remove a 'where' clause from an ActiveRecord::Relation which was being created by a scope) while joining two scopes, and did it like this: self.scope(from,to).values[:joins].
I wanted to join values from the two scopes that made up the 'joined_scope' without the 'where' clauses, so that I could add altered 'where' clauses separately (altered to use 'OR' instead of 'AND').
For me, this went in the joined scope, like so:
scope :joined_scope, -> (from, to) {
joins(self.first_scope(from,to).values[:joins])
.joins(self.other_scope(from,to).values[:joins])
.where(first_scope(from,to).ast.cores.last.wheres.inject{|ws, w| (ws &&= ws.and(w)) || w}
.or(other_scope(from,to).ast.cores.last.wheres.last))
}
Hope that helps someone

Resources