Get specific attributes from an ActiveRecord model
This will works as well
User.select(:id, :first_name).find(5)
What Choco said will work if you want an array not an active record instance. If you want an active record instance, do:
User.where(id: 5).select(:id, :first_name).take
Here's some more info. I'm not sure how much you do/don't know, so I'll assume you know less rather than more.
I assume you realise what you're doing above is method chaining - ie, you're calling a method, then calling another method on the result of the first method. For example:
User.find_by(id: 5).attributes.slice('id', 'first_name')
You're calling the find_by_id
method on the User
class. That method will return an instance of the User
class (ie, an active record instance). That instance has a method called attributes
, which you call. The attributes
method returns an instance of the Hash
class, representing the fields in the previous active record instance. Hash
has a slice
method, which you call in turn. The slice
method returns another instance of Hash
, containing the subset of fields you specified.
So, the concept to be clear on is that when you're chaining methods, you're not calling each subsequent method on the same thing - you're calling it on the return value of the previous method in the chain.
OK, so with that out of the way:
All the find
methods are intended to query the database and return a single instance of an Active Record model, or a plain ruby array containing multiple instances of Active Record models.
A lot of other methods - select
, where
, etc, do NOT hit the database right away, and are NOT intended to return an active record instance. They all return an instance of ActiveRecord::Relation
. An ActiveRecord::Relation
is kinda like a potential group of records matching certain conditions - and you can add extra conditions to it. It's like a 'database query waiting to happen', or a 'database query that may or may not have happened yet'.
When you call a method like where
, order
, or select
on ActiveRecord::Relation
, it will return an instance of ActiveRecord::Relation
, meaning you've got an instance of the same class, and can chain methods from that class nicely eg: User.where(name: 'Fred').select(:id, :name).order('name ASC')
Your instance of ActiveRecord::Relation
will be very smart, and won't bother hitting the database until you call a method that clearly indicates you want the records now - such as each
, all
, take
, etc.
So, I've gone on way longer than planned here, so I'll wrap up. Here's the code I first mentioned, with a broken down, heavily commented explanation below:
User.where(id: 5).select(:id, :first_name).take
breaking it down:
my_relation = User.where(id: 5)
# The database will not have been hit yet - my_relation
# is a query waiting to happen
my_second_relation = my_relation.select(:id, :first_name)
# The database has STILL not been hit.
# the relation now contains both the conditions
# from the previous my_relation, and the new 'select'
# criteria we specified
my_user = my_second_relation.take
# OK, 'take' means we want the first record
# matching all our criteria,
# so my_second_relation will hit the database
# to get it, and then return an Active Record
# instance (ie, an instance of the User class)
Wow... I went on longer than expected. Hope some of it was useful!
Had a requirement like this but wanted something more generic that would just gather a list of method names and values from a model (including attributes). So, I added this method to my model:
def attributes_from_keys(*keys)
keys.inject({}) do |hash_to_return, key|
hash_to_return.merge(key => send(key))
end
end
Then, call this method with:
MyModel.find(1).attributes_from_keys(:id, :name, :age)
returns
{id: 1, name: 'Lawrence', age: 23}
Try this:
User.where(id: 5).pluck(:id, :first_name)
it returns array containing id and first_name values