Get specific attributes from an ActiveRecord model

authorization, filter, ruby-on-rails, select

Solution

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!

Problem

Let's say that I have a `User` model with attributes `:id, :first_name, :last_name, and :email`. In my application, guest users shouldn't see `User's email and last_name`. I know how to select specific values if I want a list of users but I don't know how I can get specific attributes of a specific user like ``` User.find_by(id: 5).select(:id, :first_name). ``` One solution to that is to user ``` User.find_by(id: 5).attributes.slice('id', 'first_name') ``` but then I get a hash instead of an AR record. I could do ``` User.select(:id, :first_name).find_by(id: 1) ``` but the thing that I don't know which filters I should filter since I need to know the user first. Could you help me?

Original source