Rails - Simplify WHERE condition IS NOT NULL - sql

Working on a webpage I used the next line:
Model.select(:column).where("column IS NOT NULL")
I was wondering if there was a more Rails-ish way to do this, like using a hash for example
Model.select(:column).where(column: !nil)

The Squeel gem will allow you to use != nil type syntax, but natively rails will not.
Example: Model.where{column != nil}

I would prefer to use a scope as its more readable as well as its more manageable later (like merging with other scopes)
class Model < ActiveRecord::Base
scope :not_null, lambda { |column|
{:select => column,
:conditions => "#{column} NOT NULL"
}
}
end
then use
Model.not_null("column_name")

In the Rails 4 you can do this:
Model.select(:column).where.not(column: nil)

Related

Rails ActiveRecord where clause

I want to select Cars from database with where clause looking for best DRY approach for my issue.
for example I have this two parameters
params[:car_model_id] (int)
params[:transmission_id] (int)
params[:from_date]
params[:to_date]
but I dont know which one will be null
if params[:car_model_id].nil? && !params[:transmission_id].nil?
if params[:from_date].nil? && params[:from_date].nil?
return Car.where(:transmission_id => params[:transmission_id])
else
return Car.where(:transmission_id => params[:transmission_id], :date => params[:from_date]..params[:to_date])
end
elseif !params[:car_model_id].nil? && params[:transmission_id].nil?
if params[:from_date].nil? && params[:from_date].nil?
return Car.where(:car_model_id=> params[:car_model_id])
else
return Car.where(:car_model_id=> params[:car_model_id], :date => params[:from_date]..params[:to_date])
end
else
return Car.where(:car_model_id=> params[:car_model_id], :transmission_id => params[:transmission_id], :date => params[:from_date]..params[:to_date])
end
what is best approach to avoid such bad code and check if parameter is nil inline(in where)
You can do:
car_params = params.slice(:car_model_id, :transmission_id).reject{|k, v| v.nil? }
and then:
Car.where(car_params)
Explanation: Since, you're checking if the particular key i.e.: :car_model_id and transmission_id exists in params. The above code would be something like this when you have just :transimission_id in params:
Car.where(:transmission_id => '1')
or this when you have :car_model_id in params:
Car.where(:car_model_id => '3')
or this when you'll have both:
Car.where(:transmission_id => '1', :car_model_id => '3')
NOTE: This will work only when you have params keys as the column names for which you're trying to run queries for. If you intend to have a different key in params which doesn't match with the column name then I'd suggest you change it's key to the column name in controller itself before slice.
UPDATE: Since, OP has edited his question and introduced more if.. else conditions now. One way to go about solving that and to always keep one thing in mind is to have your user_params correct values for which you want to run your queries on the model class, here it's Car. So, in this case:
car_params = params.slice(:car_model_id, :transmission_id).reject{|k, v| v.nil? }
if params[:from_date].present? && params[:from_date].present?
car_params.merge!(date: params[:from_date]..params[:to_date])
end
and then:
Car.where(car_params)
what is best approach to avoid such bad code and check if parameter is
nil inline(in where)
Good Question !
I will make implementation with two extra boolean variables (transmission_id_is_valid and
car_model_id_is_valid)
transmission_id_is_valid = params[:car_model_id].nil? && !params[:transmission_id].nil?
car_model_id_is_valid = !params[:car_model_id].nil? && params[:transmission_id].nil?
if transmission_id_is_valid
return Car.where(:transmission_id => params[:transmission_id])
elseif car_model_id_is_valid
return Car.where(:car_model_id=> params[:car_model_id])
....
end
I think now is more human readable.
First, I would change this code to Car model, and I think there is no need to check if params doesn't exists.
# using Rails 4 methods
class Car < ActiveRecord::Base
def self.find_by_transmission_id_or_model_id(trasmission_id, model_id)
if transmission_id
find_by trasmission_id: trasmission_id
elsif model_id
find_by model_id: model_id
end
end
end
In controller:
def action
car = Car.find_by_transmission_id_or_model_id params[:trasmission_id], params[:car_model_id]
end
edit:
This code is fine while you have only two parameters. For many conditional parameters, look at ransack gem.

Nested select in rails (SQL to Rails conversion)

I have this rails logic that uses partial SQL query code. I was wondering if there was a way a better way or a cleaner way to do the same thing (i.e. use rails's methods to replace the SQL code)?
#servers = Server
.select("*", "(SELECT AVG('reviews'.'average') FROM 'reviews' WHERE 'reviews'.'server_id' = 'servers'.'id') AS s_avg")
.order("s_avg DESC")
.paginate(:page => params[:page], :per_page => 25)
First good thing is to move that code from view or controller to model and wrap it in scope. Moreover, scopes can be chained.
class Server < ActiveRecord::Base
scope :averaged, -> { where(SQL CODE HERE) }
scope :expensive, -> { where('price > ?', price) }
scope :latest, -> { where('created_at > ?', Date.today - 3.days.ago) }
scope :active, -> { where(active: true) }
end
Only then you can pass and chain it in controller:
#servers = Server.latest.averaged
So, simply try to brake your SQL on several parts, move these parts to model and wrap them with scopes.
You can find a lot of useful examples of query methods without pure SQL here:
http://guides.rubyonrails.org/active_record_querying.html

Is it possible to "add" SQL clauses related to a `scope` method in a `where` method / clause?

I am using Ruby on Rails 3.2.2 and I am experimenting the Squeel gem. I would like to know if (in some way, by using the Squeel gem or not) it is possible to "add" SQL clauses related to a scope method "directly" in a where clause. That is, I have:
class Article < ActiveRecord::Base
# Note: This is a scope method.
def self.created_by(user)
where(:user_id => user.id)
end
# I would like to use a scope method like the following.
#
# Note: Code in the following method doesn't work, but it should help
# understanding what I mean.
def self.scope_method_name(user)
where{ created_by(user) | ... & ... }
end
end
So, when I run Article.scope_method_name(#current_user).to_sql then it should return something like the following:
SELECT articles.* FROM articles WHERE articles.user_id = 1 OR ... AND ...
I tryed sifters but those (at least for me) are intended to be used exclusively in other Squeel statements. That is, if I state a sifter then I cannot use that to scope ActiveRecords because that sifter returns a Squeel::Nodes::Predicate object instead of an ActiveRecord::Relation.
You have to drop down into more raw AREL for OR operations
def self.scope_method_name(user)
t = arel_table
where(
(t[:user_id].eq(user.id).or(
t[:blah].eq('otherthing')
).and([:bleh].eq('thirdthing'))
)
end
Or something along those lines.
You can chain scopes like Article.by_author(user).by_editor() but this joins all the conditions with ANDs. So, to get around this, you can write individual scopes (not chaining them) using Squeel like:
class Article < ActiveRecord::Base
scope :by_author, ->(user) { where{author_id == user.id} }
scope :by_editor, ->(user) { where{editor_id == user.id} }
scope :by_title, ->(token) { where{title =~ "%#{token}%"} }
scope :by_author_or_editor, ->(user) { where{(author_id == user.id)|(editor_id == user.id)} }
scope :by_author_or_editor_and_title, ->(user, token) { where{((author_id == user.id)|(editor_id == user.id))&(title =~ "%#{token}%")} }
end
Or you can use sifters:
class Article < ActiveRecord::Base
sifter :sift_author do |user|
author_id == user.id
end
sifter :sift_editor do |user|
editor_id == user.id
end
sift :sift_title do |token|
title =~ "%#{token}%"
end
scope :by_author, ->(user) { where{sift :sift_author, user} }
scope :by_editor, ->(user) { where{sift :sift_editor, user} }
scope :by_title, ->(token) { where{sift :sift_title, token} }
scope :by_author_or_editor, -> (user) { where{(sift :sift_author, user)|(sift :sift_editor, user)} }
scope :by_author_or_editor_and_title, ->(user, token) { where{((sift :sift_author, user)|(sift :sift_editor, user))&(sift :sift_title, token)} }
end
This gives you your scopes that return an ActiveRecord::Relation, so you can in theory further chain them.

SQL OR in scopes in Rails 3 / Arel

I'm trying to create a Rails 3 model scope, which is, essentially:
scope :published, where(:published => true)
scope :viewable_by, lambda { |user| where(:user_id => user.id).or(published) }
The trouble is, I can't find a way to make this work.
I had hoped arel_table would work, but it complains it "Cannot visit ActiveRecord::Relation":
where(arel_table[:user_id].eq(user.id)).or(published)
This could be done by replicating the "published" scope as Arel but then I would be repeating code (and in my project the "published" scope is quite a bit more in depth).
The best I can come up with is this:
scope :viewable_by, lambda { |user| where("(user_id = ?) OR (#{published.where_values.collect(&:to_sql).join(" AND ")})", user.id) }
This is the shortest I could come up with after a bit of digging around:
scope :published, where(:published => true)
scope :viewable_by, lambda { |user| where("user_id = ? OR #{published.where_clauses.first}", user.id) }
(Of course it only works with a single where clause; use a join if there's more than one.)
I would recommend using a gem like MetaWhere that more clearly exposes some of the Arel underpinnings. Here's an example from the main page linked above that creates an OR query:
Article.where(:title.matches % 'Hello%' | :title.matches % 'Goodbye%').to_sql
=> SELECT "articles".* FROM "articles" WHERE (("articles"."title" LIKE 'Hello%'
OR "articles"."title" LIKE 'Goodbye%'))

Using dynamic finders to specify NOT NULL

I very often want to use dynamic finders to specify NOT NULL. So…
this works:
Widget.find_all_by_color('blue')
this works:
Widget.find_all_by_color(nil)
But how can I do
SELECT * FROM `widgets` WHERE `color` IS NOT NULL;
?
Two ways depending on how specific you want to be:
# in your model
class Widget < ActiveRecord::Base
# first way
named_scope :coloured, {:conditions => ["color IS NOT NULL"]}
# second way
named_scope :not_null, lambda{|*args| (field=args.first ? {:conditions => ["#{field} is not null",field]} : {}) } }
end
# in your controller
#coloured_widgets = Widget.coloured.all # using first way
#coloured_widgets = Widget.not_null(:colour).all # using second way
I hope it helps.
Cheers
Widget.find(:all, :conditions => "color IS NOT NULL")
Try this:
Widget.all(:conditions => "color IS NOT NULL")
Not quite as elegant, but this should work:
Widget.find(:all, :conditions => "'color' IS NOT NULL")