activerecord not like query - ruby-on-rails

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

Related

Is there something like an `ILIKE` method in Rails 4?

I used to do this with an array condition inside the where method:
Article.where('title ILIKE ?','%today%')
This worked in Postgres but ILIKE is not present in MySQL and other DBMS.
What I need is to be able to perform case insensitive queries using a code like
Article.ilike(title:'%today%',author:'%john%')
Even if there's not builtin method to perform case insensitive queries, you can use the Arel library and the matches method, like in:
Article.where(Article.arel_table[:title].matches('%today%'))
This is DB agnostic and SQL Injection proof.
I've written an ilike method in my common scope file, that allows you to call it with a list of attributes and values, that's it:
module CommonScopes
extend ActiveSupport::Concern
module ClassMethods
def ilike( options={} )
raise ArgumentError unless options.is_a? Hash or options.empty?
if options.one?
where(arel_table[options.keys.first].matches(options.values.first))
else
key, value = options.shift
ilike( {key=>value} ).merge( ilike( options ) )
end
end
end
end
You can place this inside app/models/concerns/common_scopes.rb and include where you need it.
No, there isn't. You need to write driver-specific SQL to achieve this.
ActiveRecord's goal is to make database access fast and easy for 90% of usecases, not to make your models completely database-agnostic. Switching your entire database backend from one system to another is not something they optimize for.
You might consider looking at another gem like DataMapper which provides a Ruby-syntax for wrapping things like like (but which may or may not provide an equivalent to ilike):
# If the value of a pair is an Array, we do an IN-clause for you.
Person.all(:name.like => 'S%', :id => [ 1, 2, 3, 4, 5 ])
Rails don't have the direct case sensitive search. It's dependent on the DB level. For MySQL you can use LOWER method.
YourModel.where('lower(column_name) = ?', str.downcase)

how to use LIKE in included relations

I want to use search condition with included relations, just like below
Post.includes(:tags).where( tags: { title: '%token%' }).all
The posts and tags table has been associated with a 3rd table named post_tag_relations.
The schema is like below:
posts
id: pk
title: string
content: text
tags
id: pk
title: string
post_tag_relations
id: pk
tag_id: integer
post_id: integer
The syntax only works with equal condition, I really dont know how to use LIKE search condition.
When using Post.joins(:tags) and Tag.area_table[:title].matches('%token%') it will works fine, but some post that has no tags will not be fetch out.
Could anyone help me? Thanks a lot.
UPDATE:
The Rails version is 4.1.
I want to search the post like posts.title LIKE '%token%' OR tags.title LIKE '%token%', so if use Post.joins(:tags) will not be functional if some posts have no tags. So I need use Post.includes(:tags) instead.
UPDATED AGAIN:
looks cannot use one-query to fetch, so I had already try another database schema...
Why not do this:
Post.includes(:tags).where(Tag.arel_table[:title].matches('%token%').or(Tag.arel_table[:post_id].eq(nil)))
Since ruby-on-rails-2 the joins operation is used in all cases before the includes operation during performance, but since includes uses LEFT OUTER JOIN operator, you should use exactly it. May be you need also to use not LEFT, but FULL join. So try this with arel gem:
class Post
scope :with_token(token), -> do |token|
re = Regexp.union(token).to_s
cond = Arel.sql("title REGEXP ? OR content REGEXP ?", re, re)
includes(:tags).where(Tag.arel_table[:title].eq(token).or(cond))
end
end
Of course original condition could be replaced to use LIKE operator:
class Post
scope :with_token(token), -> do |token|
includes(:tags).where(arel_table[:title].matches("%#{token}%")
.or(arel_table[:content].matches("%#{token}%")
.or(Tag.arel_table[:title].eq(token))))
end
end
NOTE: If there are some errors, provide please result SQL.
Something like this:
Post.includes(:tags).where( "tags.title LIKE ?", "%#{token}%" )
could work.
(The syntax might be a little wrong, sorry, but you get the idea)

Add conditions do activerecord includes

First I have this:
has_one :guess
scope :with_guesses, ->{ includes(:guess) }
Which loads all guesses (if they exists) for a 'X' model (run two queries). That's ok. works perfectly.
But I need to add one more condition to It.
If I do (my first thought):
scope :with_guesses, ->(user) { includes(:guess).where("guesses.user_id = ?", user.id) }
It will also run ok, BUT in one query (join) which will exclude results that doesn't have a 'guess'.
Any tips on how to use include with conditions but KEEPING the results that don't have a 'guess' ?
UPDATE
I ended up solving this by using a decorator, which I can pass the user as a context in the controller call, keeping the views clean.
I've used the Draper gem (https://github.com/drapergem/draper) to do this. You don't really need a gem to work with decorators in rails, but it can be helpful.
I didn't test it but you can use something like
User.eager_load(:guesses).where("guesses.user_id = ?", user.id)
when you using includes and where, the includes left join will be inner join.
so if you want to using a left join with where, you have to use string sql fragment:
scope :with_guesses, ->(user) { joins('left outer join guesses on guesses.user_id = ?',
user.id)}
I didn't test this code above, you have to test it yourself, this is just a way to think about
this problem.
here is reference:
http://guides.rubyonrails.org/active_record_querying.html#specifying-conditions-on-eager-loaded-associations

How to compare date part of the datetime field in rails 3.1?

Our app has a lease_booking model with a field of lease_date which is a datatime (we use sqlite for development and mysql for production). The app needs to find all the lease_booking on a given date. The code is like:
LeaseBooking.where("lease_date == ?", '2012-1-5')
The problem is that we can not find any lease_booking on 2012/1/5 because the lease_date is a date+time format. How can the app compare the date part only for a datatime field in rails 3.1?
thanks.
I'm afraid you'd have to do :
LeaseBooking.where("date(lease_date) = ?", '2012-1-5')
(with only one = sign)
Maybe there is a better answer, because call the "date" function is not really pretty for ActiveRecord! Like Jason Lewis says in the comments, it is DB-specific.
Alternately, try this:
scope :booking_date, lamda {|d| where('lease_date > ?', Date.parse(d).to_time).where('lease_date < ?', (Date.parse(d) +1).to_time)}
And just call
LeaseBooking.booking_date('2012-01-05')
The where-chaining is ugly, but whether or not you can use multiple ? placeholders, or named variables, to do it in one statement, is highly DB-dependent. ARel is pretty good at turning even ugly chained queries into efficient SQL, though.

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