ActiveRecord: Scope for where attribute is present - ruby-on-rails

I want to grab those Blogs where its title.present? == true. (Blogs have a string attribute of title).
class Blog < ActiveRecord::Base
scope :with_present_titles, -> {where("#{title.present?} == ?", true)} #doesn't work
end
#blogs = Blog.with_present_titles #doesn't work
Not sure what I'm doing wrong here.

To return all records with some values in title column, you would update your scope with the following query:
scope :with_present_titles, -> { where("title is not null and title != ''") }
This returns all records with some value in title column leaving any null and empty titles.
.present? being a method provided by rails, you cannot simply use these methods in your DB queries.

Depending on your rails version you can also use where.not (introduced in rails 4) E.g.
scope :with_present_titles, ->{where.not(title: [nil,'']) }
#Blog.with_present_titles.to_sql
#=> "SELECT [blogs].*
# FROM [blogs]
# WHERE (NOT (([blogs].[title] = '' OR [blogs].[title] IS NULL)))"

Simply write the following scope.
scope :with_present_titles, -> { where('title IS NOT ? AND TITLE != ?', nil, '') }

Related

Get all records greater than created at rails

I want to get all records of matching wiht query, I get multiple params and save them in query like that
query = Hash.new
query[:user_id] = params["user_id"] if query["user_id"]
query[:vehicle] = params["vehicle_id"] if query["vehicle_id"]
trips = Trip.where(query)
I also want to add params['created_at] if these params are present but no idea how to do it
You could add a scope to your Trip model.
scope :created_after, ->(time) { where("created_at > ?", time) if time }
Then chain it like so:
Trip.created_after(params["created_at"]).where(query)
Not the most elegant, but it will work. If "created_at" is not in the params, it will be nil and will just return all trips.
Just for fun and riffing off the answer provided by Paul Byrne and this Q&A, perhaps something like:
params.slice(:user_id, :vehicle_id).compact.
each_with_object(Trip.created_after(params[:created_at])) do |(key, value), scope|
scope.merge(Trip.send(key, value))
end

Dynamic search form for multiple columns of the same model in Rails 4

I am trying to create a search form with multiple columns(all from the same model)
Now I want to set the value of all other columns as nil if the category column is set to a particular value. Something like this-
#app/models/question.rb
if (cateogry.matches => 'Exam Questions')
query = query.where("name like ? AND course like ? AND year like ?", "%#{query}%", "%#{query}%", "%#{query}%")
else
query = query.where("name like ?","%#{query}%")
end
The search form is a basic form using get method.
You could use scopes for that.
class Question < ActiveRecord::Base
...
# Scope for searching by exam
scope :by_exam, -> (name, course, year) {
match(:name, name).
match(:course).
match(:year, year)
}
# Scope forsearching by nma
scope :by_name, ->(name) { match(:name, name) }
# Helper scope for matching
scope :match, ->(key, value) { where(arel_table[key].matches("%#{value}%"} }
end
So in your controller
if (cateogry.matches => 'Exam Questions')
Question.by_exam(params[:name], params[:course], params[:year])
else
Question.by_name(params[:name])
end

can't query by association field value

I need to query only Realties where user id confirmed.
I am using "devise" gem for authentication, and my query looks like this:
#search = Realty.includes(:user).where("users.confirmed_at != ?", nil)
As result I get => [], but there are many Realties records where user.confirmed? => true. I double checked it from the console.
The association structure looks like this:
class Realty
belongs_to :user
.....
class User
has_many :realties
.....
Please help me or tell where I make an mistake ??
Thank you.
#search = Realty.includes(:user).where("users.confirmed_at IS NOT NULL")
You cannot use != in a SQL statement.
If you want to check if the value is not empty as well, then you can do
#search = Realty.includes(:user).where("users.confirmed_at <> ''")
it will return the results where users.confirmed_at is neither null nor empty.

Find records where an attribute is present

I have a User model with the attributes username, email and name.
username and email are required upon signup, but not name.
What would be the query to find all users that have filled out name (i.e. it is no nil)?
The query should be at least Rails 3.2 and 4.0 compatible.
I'm thinking something in the lines of:
User.where(name: present?)
[UPDATED 13/5/2022]
To get all records where an attribute is not present in SQL, we would write
WHERE attr IS NULL or attr = ''
an easy mistake to make is then to negate this and write
WHERE attr is not null and attr != ''
However in SQL this equates to writing
WHERE attr != ''
since the NULL value is always ignored when using the equality operator.
So this translates to rails as follows:
User.where.not(name: '')
[MY OLD ANSWER]
An empty value in a database gets a special value NULL. Testing whether is set uses the special comparator IS NULL or IS NOT NULL.
Then there still remains the possibility that an empty string was filled in, so a complete test would be
#users = User.where("name is NOT NULL and name != ''")
[UPDATED for rails 4+]
Since rails 4 we can write:
User.where.not(name: [nil, ""])
which will generate the same query. Awesome :)
present?
present? is essentially not nil and not empty?:
class Object
def present?
!blank?
end
def blank?
respond_to?(:empty?) ? !!empty? : !self
end
end
ActiveRecord condition
In Rails 4, not conditions can be done without raw sql code.
# Both lines lead to the same result:
User.where.not(name: [nil, ""])
User.where.not(name: nil).where.not(name: "")
Since there is no raw sql code, you don't have to worry about if they work with every database adapter. In fact, this works fine for both, mysql and postgres.
to_sql
You can see how they translate to sql queries if you append .to_sql, for example in the rails console.
# rails console
User.where.not(name: [nil, ""]).to_sql
# => "SELECT \"users\".* FROM \"users\" WHERE (NOT ((\"users\".\"name\" = '' OR \"users\".\"name\" IS NULL)))"
User.where.not(name: nil).where.not(name: "").to_sql
# => "SELECT \"users\".* FROM \"users\" WHERE (\"users\".\"name\" IS NOT NULL) AND (\"users\".\"name\" != '')"
Further Reading
[1] Rails 4 guide "ActiveRecord Query Interface"
[2] Definition of present? on github
NOT SQL queries can be built by where.not
#users = User.where.not(name: nil)
Try this:
User.where("name IS NOT NULL AND name != ?", "")
I edited my answer as per #nathavanda comments, which his answer in my opinion should be the accepted one.
You can simply do this:
User.where.not(name: '')
Because of the nature of not, it won't include records where name is nil nor empty string. See this article for more details about not and nil

Rails ActiveRecord: Find All Users Except Current User

I feel this should be very simple but my brain is short-circuiting on it. If I have an object representing the current user, and want to query for all users except the current user, how can I do this, taking into account that the current user can sometimes be nil?
This is what I am doing right now:
def index
#users = User.all
#users.delete current_user
end
What I don't like is that I am doing post-processing on the query result. Besides feeling a little wrong, I don't think this will work nicely if I convert the query over to be run with will_paginate. Any suggestions for how to do this with a query? Thanks.
It is possible to do the following in Rails 4 and up:
User.where.not(id: id)
You can wrap it in a nice scope.
scope :all_except, ->(user) { where.not(id: user) }
#users = User.all_except(current_user)
Or use a class method if you prefer:
def self.all_except(user)
where.not(id: user)
end
Both methods will return an AR relation object. This means you can chain method calls:
#users = User.all_except(current_user).paginate
You can exclude any number of users because where() also accepts an array.
#users = User.all_except([1,2,3])
For example:
#users = User.all_except(User.unverified)
And even through other associations:
class Post < ActiveRecord::Base
has_many :comments
has_many :commenters, -> { uniq }, through: :comments
end
#commenters = #post.commenters.all_except(#post.author)
See where.not() in the API Docs.
#users = (current_user.blank? ? User.all : User.find(:all, :conditions => ["id != ?", current_user.id]))
You can also create named_scope, e.g. in your model:
named_scope :without_user, lambda{|user| user ? {:conditions => ["id != ?", user.id]} : {} }
and in controller:
def index
#users = User.without_user(current_user).paginate
end
This scope will return all users when called with nil and all users except given in param in other case. The advantage of this solution is that you are free to chain this call with other named scopes or will_paginate paginate method.
Here is a shorter version:
User.all :conditions => (current_user ? ["id != ?", current_user.id] : [])
One note on GhandaL's answer - at least in Rails 3, it's worth modifying to
scope :without_user, lambda{|user| user ? {:conditions => ["users.id != ?", user.id]} : {} }
(the primary change here is from 'id != ...' to 'users.id !=...'; also scope instead of named_scope for Rails 3)
The original version works fine when simply scoping the Users table. When applying the scope to an association (e.g. team.members.without_user(current_user).... ), this change was required to clarify which table we're using for the id comparison. I saw a SQL error (using SQLite) without it.
Apologies for the separate answer...i don't yet have the reputation to comment directly on GhandaL's answer.
Very easy solution I used
#users = User.all.where("id != ?", current_user.id)
User.all.where("id NOT IN(?)", current_user.id) will through exception
undefined method where for #<Array:0x0000000aef08f8>
User.where("id NOT IN (?)", current_user.id)
Another easy way you could do it:
#users = User.all.where("id NOT IN(?)", current_user.id)
an array would be more helpful
arrayID[0]=1
arrayID[1]=3
User.where.not(id: arrayID)
User.where(:id.ne=> current_user.id)
ActiveRecord::QueryMethods#excluding (Rails 7+)
Starting from Rails 7, there is a new method ActiveRecord::QueryMethods#excluding.
A quote right from the official Rails docs:
excluding(*records)
Excludes the specified record (or collection of records) from the resulting relation. For example:
Post.excluding(post)
# SELECT "posts".* FROM "posts" WHERE "posts"."id" != 1
Post.excluding(post_one, post_two)
# SELECT "posts".* FROM "posts" WHERE "posts"."id" NOT IN (1, 2)
This can also be called on associations. As with the above example, either a single record of collection thereof may be specified:
post = Post.find(1)
comment = Comment.find(2)
post.comments.excluding(comment)
# SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 AND "comments"."id" != 2
This is short-hand for .where.not(id: post.id) and .where.not(id: [post_one.id, post_two.id]).
An ArgumentError will be raised if either no records are specified, or if any of the records in the collection (if a collection is passed in) are not instances of the same model that the relation is scoping.
Also aliased as: without
Sources:
Official docs - ActiveRecord::QueryMethods#excluding
PR - Add #excluding to ActiveRecord::Relation to exclude a record (or collection of records) from the resulting relation.
What's Cooking in Rails 7?
What you are doing is deleting the current_user from the #users Array. This won't work since there isn't a delete method for arrays. What you probably want to do is this
def index
#users = User.all
#users - [current_user]
end
This will return a copy of the #users array, but with the current_user object removed (it it was contained in the array in the first place.
Note: This may not work if array subtraction is based on exact matches of objects and not the content. But it worked with strings when I tried it. Remember to enclose current_user in [] to force it into an Array.

Resources