I am new to rails. What I see that there are a lot of ways to find a record:
find_by_<columnname>(<columnvalue>)
find(:first, :conditions => { <columnname> => <columnvalue> }
where(<columnname> => <columnvalue>).first
And it looks like all of them end up generating exactly the same SQL. Also, I believe the same is true for finding multiple records:
find_all_by_<columnname>(<columnvalue>)
find(:all, :conditions => { <columnname> => <columnvalue> }
where(<columnname> => <columnvalue>)
Is there a rule of thumb or recommendation on which one to use?
where returns ActiveRecord::Relation
Now take a look at find_by implementation:
def find_by
where(*args).take
end
As you can see find_by is the same as where but it returns only one record. This method should be used for getting 1 record and where should be used for getting all records with some conditions.
Edit:
This answer is very old and other, better answers have come up since this post was made. I'd advise looking at the one posted below by #Hossam Khamis for more details.
Use whichever one you feel suits your needs best.
The find method is usually used to retrieve a row by ID:
Model.find(1)
It's worth noting that find will throw an exception if the item is not found by the attribute that you supply. Use where (as described below, which will return an empty array if the attribute is not found) to avoid an exception being thrown.
Other uses of find are usually replaced with things like this:
Model.all
Model.first
find_by is used as a helper when you're searching for information within a column, and it maps to such with naming conventions. For instance, if you have a column named name in your database, you'd use the following syntax:
Model.find_by(name: "Bob")
.where is more of a catch all that lets you use a bit more complex logic for when the conventional helpers won't do, and it returns an array of items that match your conditions (or an empty array otherwise).
Model.find
1- Parameter: ID of the object to find.
2- If found: It returns the object (One object only).
3- If not found: raises an ActiveRecord::RecordNotFound exception.
Model.find_by
1- Parameter: key/value
Example:
User.find_by name: 'John', email: 'john#doe.com'
2- If found: It returns the object.
3- If not found: returns nil.
Note: If you want it to raise ActiveRecord::RecordNotFound use find_by!
Model.where
1- Parameter: same as find_by
2- If found: It returns ActiveRecord::Relation containing one or more records matching the parameters.
3- If not found: It return an Empty ActiveRecord::Relation.
There is a difference between find and find_by in that find will return an error if not found, whereas find_by will return null.
Sometimes it is easier to read if you have a method like find_by email: "haha", as opposed to .where(email: some_params).first.
Since Rails 4 you can do:
User.find_by(name: 'Bob')
which is the equivalent find_by_name in Rails 3.
Use #where when #find and #find_by are not enough.
The accepted answer generally covers it all, but I'd like to add something,
just incase you are planning to work with the model in a way like updating, and you are retrieving a single record(whose id you do not know), Then find_by is the way to go, because it retrieves the record and does not put it in an array
irb(main):037:0> #kit = Kit.find_by(number: "3456")
Kit Load (0.9ms) SELECT "kits".* FROM "kits" WHERE "kits"."number" =
'3456' LIMIT 1
=> #<Kit id: 1, number: "3456", created_at: "2015-05-12 06:10:56",
updated_at: "2015-05-12 06:10:56", job_id: nil>
irb(main):038:0> #kit.update(job_id: 2)
(0.2ms) BEGIN Kit Exists (0.4ms) SELECT 1 AS one FROM "kits" WHERE
("kits"."number" = '3456' AND "kits"."id" != 1) LIMIT 1 SQL (0.5ms)
UPDATE "kits" SET "job_id" = $1, "updated_at" = $2 WHERE "kits"."id" =
1 [["job_id", 2], ["updated_at", Tue, 12 May 2015 07:16:58 UTC +00:00]]
(0.6ms) COMMIT => true
but if you use where then you can not update it directly
irb(main):039:0> #kit = Kit.where(number: "3456")
Kit Load (1.2ms) SELECT "kits".* FROM "kits" WHERE "kits"."number" =
'3456' => #<ActiveRecord::Relation [#<Kit id: 1, number: "3456",
created_at: "2015-05-12 06:10:56", updated_at: "2015-05-12 07:16:58",
job_id: 2>]>
irb(main):040:0> #kit.update(job_id: 3)
ArgumentError: wrong number of arguments (1 for 2)
in such a case you would have to specify it like this
irb(main):043:0> #kit[0].update(job_id: 3)
(0.2ms) BEGIN Kit Exists (0.6ms) SELECT 1 AS one FROM "kits" WHERE
("kits"."number" = '3456' AND "kits"."id" != 1) LIMIT 1 SQL (0.6ms)
UPDATE "kits" SET "job_id" = $1, "updated_at" = $2 WHERE "kits"."id" = 1
[["job_id", 3], ["updated_at", Tue, 12 May 2015 07:28:04 UTC +00:00]]
(0.5ms) COMMIT => true
Apart from accepted answer, following is also valid
Model.find() can accept array of ids, and will return all records which matches.
Model.find_by_id(123) also accept array but will only process first id value present in array
Model.find([1,2,3])
Model.find_by_id([1,2,3])
The answers given so far are all OK.
However, one interesting difference is that Model.find searches by id; if found, it returns a Model object (just a single record) but throws an ActiveRecord::RecordNotFound otherwise.
Model.find_by is very similar to Model.find and lets you search any column or group of columns in your database but it returns nil if no record matches the search.
Model.where on the other hand returns a Model::ActiveRecord_Relation object which is just like an array containing all the records that match the search. If no record was found, it returns an empty Model::ActiveRecord_Relation object.
I hope these would help you in deciding which to use at any point in time.
Suppose I have a model User
User.find(id)
Returns a row where primary key = id. The return type will be User object.
User.find_by(email:"abc#xyz.com")
Returns first row with matching attribute or email in this case. Return type will be User object again.
Note :- User.find_by(email: "abc#xyz.com") is similar to User.find_by_email("abc#xyz.com")
User.where(project_id:1)
Returns all users in users table where attribute matches.
Here return type will be ActiveRecord::Relation object. ActiveRecord::Relation class includes Ruby's Enumerable module so you can use it's object like an array and traverse on it.
Both #2s in your lists are being deprecated. You can still use find(params[:id]) though.
Generally, where() works in most situations.
Here's a great post: https://web.archive.org/web/20150206131559/http://m.onkey.org/active-record-query-interface
The best part of working with any open source technology is that you can inspect length and breadth of it.
Checkout this link
find_by ~> Finds the first record matching the specified conditions. There is no implied ordering so if order matters, you should specify it yourself. If no record is found, returns nil.
find ~> Finds the first record matching the specified conditions , but if no record is found, it raises an exception but that is done deliberately.
Do checkout the above link, it has all the explanation and use cases for the following two functions.
I will personally recommend using
where(< columnname> => < columnvalue>)
Related
In Ruby on Rails, there appear to be two methods to check whether a collection has any elements in it.
Namely, they are ActiveRecord::FinderMethods’ exists? and ActiveRecord::Relation’s any?. Running these in a generic query (Foo.first.bars.exists? and Foo.first.bars.any?) generated equivalent SQL. Is there any reason to use one over the other?
#any and #exists? are very different beasts but query similarly.
Mainly, #any? accepts a block — and with this block it retrieves the records in the relation, calls #to_a, calls the block, and then hits it with Enumerable#any?. Without a block, it's the equivalent to !empty? and counts the records of the relation.
#exists? always queries the database and never relies on preloaded records, and sets a LIMIT of 1. It's much more performant vs #any?. #exists? also accepts an options param as conditions to apply as you can see in the docs.
The use of ActiveRecord#any? is reduced over ActiveRecord#exists?. With any? you can check, in the case of passing a block, if certain elements in that array matches the criteria. Similar to the Enumerable#any? but don't confuse them.
The ActiveRecord#any? implements the Enumerable#any? inside the logic of its definition, by converting the Relation accessed to an array in case a block has been passed to it and yields and access the block parameters to implement in a "hand-made" way a "Ruby" any? method.
The handy else added is intended to return the negation of empty? applied to the Relation. That's why you can check in both ways if a model has or no records in it, like:
User.count # 0
User.any? # false
# SELECT 1 AS one FROM "users" LIMIT ? [["LIMIT", 1]]
User.exists? # false
# SELECT 1 AS one FROM "users" LIMIT ? [["LIMIT", 1]]
You could also check in the "any?" way, if some record attribute has a specific value:
Foo.any? { |foo| foo.title == 'foo' } # SELECT "posts".* FROM "posts"
Or to save "efficiency" by using exists? and improve your query and lines of code:
Foo.exists?(title: 'foo') # SELECT 1 AS one FROM "posts" WHERE "posts"."title" = ? LIMIT ? [["title", "foo"], ["LIMIT", 1]]
ActiveRecord#exists? offers many implementations and is intended to work in a SQL level, rather than any?, that anyways will convert the Relation what you're working with in an array if you don't pass a block.
The answers here are all based on very outdated versions. This commit from 2016 / ActiveRecord 5.1 changes empty?, which is called by any? when no block is passed, to call exists? when not preloaded. So in vaguely-modern Rails, the only difference when no block is passed is a few extra method calls and negations, and ignoring preloaded results.
I ran into a practical issue: exists? forces a DB query while any? doesn't.
user = User.new
user.skills = [Skill.new]
user.skills.any?
# => true
user.skills.exists?
# => false
Consider having factories and a before_create hook:
class User < ActiveRecord::Base
has_many :skills
before_create :ensure_skills
def ensure_skills
# Don't want users without skills
errors.add(:skills, :invalid) if !skills.exists?
end
end
FactoryBot.define do
factory :user do
skills { [association(:skill)] }
end
end
create(:user) will fail, because at the time of before_create skills are not yet persisted. Using .any? will solve this.
If I do this:
a = User.all
a.length # 15
It works, I get 15. But, if for some reason my query returns only one value ...
a = User.find(1)
a.length # NoMethodError: undefined method `length' for #<User:0x007f00815f9e38>
Which is logical, there's only one result. Is there a method that can check if the result is 1?
EDIT:
I'll explain what I'm trying to do. I want to know if a certain query returns 1 or more records. I'd try this with count, length or size, but all of those methods aren't available when the query returns only a single record. I need to know when I get one record and when there's more, but I can't figure out how to measure this without getting 'nomethod' errors on both scenarios.
# There're 15 users active right now.
a=User.find_by(:active => true)
a.length # 15
# Only 1 user is active.
a=User.find_by(:active => true)
a.length # Nomethod Error
SOLUTION:
I was using the find_by method, which doesn't seem to include the size, count, length methods in the result. As suggested below, I used where instead, and the result now included the size methods.
When you call
User.all
the result is an array, and thus has a length method.
In contrast, when you call either of
User.find(1)
or
User.find_by(active: true)
the result is either a single User instance or nil, neither of which have a length method. Since find does a lookup against the backing table's unique id, you'll never get back more than one result.
As a further contrast, if your User model defined a boolean called active you could write:
User.where(active: true)
... and that would return an array that you can use with count or length.
In general, when you're looking for the number of rows that correspond to some condition, I would use
Model.where(conditions).count
... as this is reliable and generates a single query (SELECT COUNT(*) ...) without instantiating any models.
Try using count. The difference between them is described here.
Doing Foo.find(:id) you're basically telling ActiveRecord you just want to know about that 1 foo, you're not bothered about it's size or count because you already know it.
This is my irb:
irb(main):001:0> u = User.all
irb(main):002:0> u.count
(0.5ms) SELECT COUNT(*) FROM `users`
=> 18
irb(main):003:0> u.length
=> 18
irb(main):004:0> u.size
=> 18
All great, I do have 18 users.
This is my console when I try to do a count from a find
irb(main):005:0> u1 = User.find(1)
irb(main):006:0> u1.length
NoMethodError: undefined method `length' for #<User:0x00000002dbc9a8>
Smashing, we're on the same page now.
Now, this is what happens when I have 1 user from a collection/array.
irb(main):031:0> u.count
(0.4ms) SELECT COUNT(*) FROM `users`
=> 1
irb(main):032:0> u.size
=> 1
irb(main):033:0> u.length
=> 1
irb(main):034:0>
Becaue I'm querying a collection/array of records instead of a single one, I don't get the error.
So, Foo.all will return an array and give you access to count, size and length while Foo.find returns a single object.
Edit:
If find_by returns nil or undefined method you could write your query like so: Foo.where(active: true) ..
I'm new to rails. Just wondering which is the better approach that will return nil if the subject_id can't be found:
#subject = Subject.find_by_id(params[:subject_id])
or
#subject = Subject.where(:id => params[:subject_id]).first
Thanks.
I prefer find_by as the name is descriptive and you get the object with out having to call a second function (i.e. first)
User.find(9) # returns User object. Throws exception when not found.
User.find_by(id: 9) # returns User object. Returns nil when not found.
User.where(id: 9).first # returns User object. Returns nil when not found.
They both generate the same SQL statement:
1.9.3p194 :003 > Example.find_by_id(9)
Example Load (0.3ms) SELECT "examples".* FROM "examples" WHERE "examples"."id" = 9 LIMIT 1
nil
1.9.3p194 :004 > Example.where(:id => 9).first
Example Load (0.3ms) SELECT "examples".* FROM "examples" WHERE "examples"."id" = 9 LIMIT 1
nil
So they'll have the same performance characteristics at the database. There may be a slight difference in the Rails code for find_by_*_ vs. where, but I'd imagine that will be negligible compared to query time.
Edit: In light of Ryan Bigg's comment below, I'd have to suggest the second form for forward compatibility.
How do I define a model attribute as an expression of another attribute?
Example:
Class Home < ActiveRecord::Base
attr_accessible :address, :phone_number
Now I want to be able to return an attribute like :area_code, which would be an sql expression like "substr(phone_number, 1,3)".
I also want to be able to use the expression / attribute in a group by query for a report.
This seems to perform the query, but does not return an object with named attributes, so how do I use it in a view?
Rails Console:
#ac = Home.group("substr(phone_number, 1,3)").count
=> #<OrderedHash {"307"=>3, "515"=>1}>
I also expected this to work, but not sure what kind of object it is returning:
#test = Home.select("substr(phone_number, 1,3) as area_code, count(*) as c").group("substr(phone_number, 1,3)")
=> [#<Home>, #<Home>]
To expand on the last example. Here it is with Active Record logging turned on:
>Home.select("substr(phone_number, 1,3) as area_code, count(*) as c").group("substr(phone_number, 1,3)")
Output:
Home Load (0.3ms) SELECT substr(phone_number, 1,3) as area_code, count(*) as c FROM "homes" GROUP BY substr(phone_number, 1,3)
=> [#<Home>, #<Home>]
So it is executing the query I want, but giving me an unexpected data object. Shouldn't I get something like this?
[ #<area_code: "307", c: 3>, #<area_code: "515", c: 1> ]
you cannot access to substr(...) because it is not an attribute of the initialized record object.
See : http://guides.rubyonrails.org/active_record_querying.html "selecting specific fields"
you can workaround this this way :
#test = Home.select("substr(phone_number, 1,3) as phone_number").group(:phone_number)
... but some might find it a bit hackish. Moreover, when you use select, the records will be read-only, so be careful.
if you need the count, just add .count at the end of the chain, but you will get a hash as you already had. But isn't that all you need ? what is your purpose ?
You can also use an area_code column that will be filled using callbacks on create and update, so you can index this column ; your query will run fast on read, though it will be slower on insertion.
I have photos that belong to collections and users. Photos always belong to a user, but may not be assigned to a collection.
In my controller, this works perfectly:
#collection_photos = Photo.where( :collection => #collection, :user => current_user )
However, this fails...
#other_photos = Photo.where( :collection => nil, :user => current_user )
...but this works:
#other_photos = Photo.where( :collection_id => nil, :user => current_user )
When collection is set to nil I get this error message: No attribute named 'collection' exists for table photos.
If I pass an object, it knows to search for collection_id from the symbol :collection, but if I don't pass an object it doesn't seem to be aware of the association.
Am I understanding this correctly? Could anyone explain a little better why :collection=>nil doesn't work?
when you use pass in the conditions into ActiveRecord, it actually tries to analyze the objects that you passed in, is it a string? an array? a hash? and what's in the string, array or hash?
and in your case, a hash, so it's trying to analyze what's in the hash, in the first statement (which works), you passed in a model instance as the value, so it tries to find if there are any associations that mapped to the key your specified and voila, it found it and everything works as planned
in the second case, you passed in nil as the value, now, ActiveRecord sees that it's a nil object, so it decided that it's not an association. note that it doesn't look at the key, but it only looked at the value, thus it tries to find if there's any column that mapped to the key, but it couldn't find, returning an error
in the last case, you passed in nil as the value, same thing, it tried to find a column which mapped to :collection_id, thus it passed in nil as the value in the SQL statement, and it returned successfully
so it's just an unfortunate considerations taken by ActiveRecord that makes the second case not working =)
hope this clarifies! =D
My guess is that it's like the famous rails .find vs .find_by_id.
.find is designed to throw an exception if it cannot find any association.
where as .find_by_id will just return nil if doesn't find any association.
so in your .where statement, when you search for the collection it's probably treating that like a .find and when you search by collection_id it will return nil just like .find_by_id does if it can't find any associated collection.
I'm not sure how these two methods differ in Activerecord's inner workings, but they are designed to react differently to nil results.
I think your answer is in ActiveRecord::PredicateBuilder.build_from_hash. There is a case statement in there that checks the class of each value in the hash, and it specifically looks for ActiveRecord::Relation
This seems to no longer be an issue in Rails 4. For instance the following code
#other_photos = Photo.where( :collection => nil, :user => User.first )
would run
User Load (Xms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1
Photo Load (Xms) SELECT "photos".* FROM "photos" WHERE "photos"."collection_id" IS NULL AND "photos"."user_id" = 1
*Tested in Rails 4.1.1