i need to get a user id from a query result - ruby

am querying from my User table using the user name, how do i get the user id from the query object
User.where(user_name: "john")
my goal is to get the User id: 5, i thought it was as easy as (.id), but thats not working
=> #<ActiveRecord::Relation [#<User id: 5, user_name: "john", user_city: "India", created_at: "2019-01-19 18:02:32", updated_at: "2019-01-19 18:02:32">]>

Materialized where will return a collection, so you can get first record with .first
User.where(:user_name => "john").first&.id
Or use find_by which will return first record which satisfies condition.
User.find_by(:user_name => "john")&.id

If you're really only trying to get the id (i.e., not using any other attributes of the object) it's usually better to formulate this as a .pluck. This requires less time for ActiveRecord to instantiate, and makes the query job take better use of your database indices. As long as User#user_name is unique (and I'd hope so!) it will return an array of length 1.
User.where(user_name: "John").first.&id
# SELECT "users".* FROM "users" WHERE "users"."user_name" = "John"
# ORDER BY "users" ASC LIMIT 1
# => 1 or nil
User.where(user_name: "John").pluck(:id).first
# SELECT "users"."id" FROM "users" WHERE "users"."user_name" = "John"
# => 1 or nil
Unfortunately, find_by and its helpers don't work this way with pluck, and both of these statements instead result in errors
User.find_by(user_name: "John").pluck(:id)
User.find_by_user_name("John").pluck(:id)

Related

Laravel: Find specific pivot value from three-way pivot

I previously made this question: laravel: How to get column related to another column in pivot table (3 column pivot)
Which helped me loop through my three-way pivot and link each User, Account, and Role.
The implementation doesn't concern itself with a specific user or account.
On my app, a single Account can have many users. These users have specific roles on accounts like "owner", "admin", "manager", etc.
I'm creating a policy to determine if a user has permission based on their role on a specific account to perform an action.
I'm playing around with Tinker. So if I grab a user, and do $user->roles, it'll output
>>> $user->roles
=> Illuminate\Database\Eloquent\Collection {#3226
all: [
App\Role {#3250
id: 1,
name: "owner",
manage_billing: 1,
manage_users: 1,
close_account: 1,
created_at: "2020-05-02 12:34:39",
updated_at: "2020-05-02 12:34:39",
pivot: Illuminate\Database\Eloquent\Relations\Pivot {#3219
user_id: 1,
role_id: 1,
account_id: 1,
},
},
],
}
Given that I have an account_id, I'd like to check the role on that specific account.
I tried something like:
$user->roles->wherePivot('account_id', $account->id);
but the method wherePivot doesn't exist on collections.
(the $account variable is just $user->accounts()->first() in this example)
My Models and relationships are on the question I linked above.
This seems to be what I wanted:
$user->roles()->wherePivot('account_id', $account->id)->first();
My mistake was not doing roles()

rails 5 enum where "like"

I'm trying to query an activerecord model enum and use the like operator in the where method, and it just doesnt work. Is there some trick to allow me to query an enum this way? Works fine on regular columns. Here it is in the console.
Regular string column (title) works as shown below
irb(main):092:0> Proposal.select(:id,:department,:status).where('title like "test%"')
Proposal Load (0.3ms) SELECT "proposals"."id", "proposals"."department", "proposals"."status" FROM "proposals" WHERE (title like "test%") LIMIT ? [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Proposal id: 7, department: 1, status: "In Progress">, #<Proposal id: 61, department: 2, status: "Won">]>
However, trying it on an enum, gives no results.
irb(main):094:0> Proposal.select(:department,:status).where('status like "Wo%"')
Proposal Load (0.3ms) SELECT "proposals"."department", "proposals"."status" FROM "proposals" WHERE (status like "Wo%") LIMIT ? [["LIMIT", 11]]
=> #<ActiveRecord::Relation []>
Any idea why I can't use like operator on enum? I'm trying to use this to filter a view with datatables.net server side processing.
Enum stores data as integer like 0,1,2,3... Then rails map number to enum value defined in model. That is the reason why you doesn't get result

Sequel gem: Why does eager().all work but eager().first doesn't?

I'm storing a user's profile fields in a separate table, and want to look up a user by email address (for password reset). Trying to determine the best approach, and ran into this unexpected behaviour inconsistency.
Schema
create_table(:users) do
String :username, primary_key: true
...
end
create_table(:user_fields) do
primary_key :id
foreign_key :user_id, :users, type: String, null: false
String :label, null: false
String :value, null: false
end
Console Session
This version works (look up field, eager load it's associated user, call .all, take the first one):
irb(main):005:0> a = UserField.where(label: 'email', value: 'testuser#test.com').eager(:user).all[0]
I, [2015-09-29T17:54:06.273263 #147] INFO -- : (0.000176s) SELECT * FROM `user_fields` WHERE ((`label` = 'email') AND (`value` = 'testuser#test.com'))
I, [2015-09-29T17:54:06.273555 #147] INFO -- : (0.000109s) SELECT * FROM `users` WHERE (`users`.`username` IN ('testuser'))
=> #<UserField #values={:id=>2, :user_id=>"testuser", :label=>"email", :value=>"testuser#test.com"}>
irb(main):006:0> a.user
=> #<User #values={:username=>"testuser"}>
You can see both queries (field and user) are kicked off together, and when you try to access a.user, the data's already loaded.
But when I try calling .first in place of .all:
irb(main):007:0> b = UserField.where(label: 'email', value: 'testuser#test.com').eager(:user).first
I, [2015-09-29T17:54:25.832064 #147] INFO -- : (0.000197s) SELECT * FROM `user_fields` WHERE ((`label` = 'email') AND (`value` = 'testuser#test.com')) LIMIT 1
=> #<UserField #values={:id=>2, :user_id=>"testuser", :label=>"email", :value=>"testuser#test.com"}>
irb(main):008:0> b.user
I, [2015-09-29T17:54:27.887718 #147] INFO -- : (0.000172s) SELECT * FROM `users` WHERE (`username` = 'testuser') LIMIT 1
=> #<User #values={:username=>"testuser"}>
The eager load fails -- it doesn't kick off the second query for the user object until you try to reference it with b.user.
What am I failing to understand about the sequel gem API here? And what's the best way to load a model instance based on the attributes of it's associated models? (find user by email address)
Eager loading only makes sense when loading multiple objects. And in order to eager load, you need all of the current objects first, in order to get all associated objects in one query. With each, you don't have access to all current objects first, since you are iterating over them.
You can use the eager_each plugin if you want Sequel to handle things internally for you, though note that it makes dataset.first do something similar to dataset.all.first for eagerly loaded datasets. But it's better to not eager load if you only need one object, and to call all if you need to eagerly load multiple ones.

group method for activerecord. Docs are confusing?

I don't get what this means in the Rails tutorial:
group(*args) public
Allows to specify a group attribute:
User.group(:name)
=> SELECT "users".* FROM "users" GROUP BY name
Returns an array with distinct records based on the group attribute:
User.group(:name) [User id: 3, name: "Foo", ...>, #User id: 2, name: "Oscar", ...>]
I don't see the grouping with the example they gave...
Group is most useful (I think) if you are trying to count stuff in your database or if you join multiple tables. Let me give a few examples.
1.
If you want to know how many users there are in your data base with each name then you can do:
User.group(:name).count
this will return a hash looking something like this:
{ ann: 4, bert: 15, cecilia: 3 ... }
I do not know why there are so many Berts in your database but anyway...
2.
If your users have related records (for instance cars) the you can use this to get the first car included in your activerecord model (the reason it will be the first is because of how group works and is further explained in the link below)
User.joins(:cars).select('users.*, cars.model as car_model, cars.name as car_name').group('users.id')
Now all records in this result will have a method called car_model and one called car_name.
You can count how many cars each user has with one single query.
User.joins(:cars).select('users.*, count(cars.id) as car_count').group('users.id')
Now all records will have a car_count.
For further reading: Mysql group tutorial
Hope this shed enough light over groups for you to try them out a little bit. I do not think you can fully understand them until you worked with them a little bit.

Subqueries in activerecord

With SQL I can easily do sub-queries like this
User.where(:id => Account.where(..).select(:user_id))
This produces:
SELECT * FROM users WHERE id IN (SELECT user_id FROM accounts WHERE ..)
How can I do this using rails' 3 activerecord/ arel/ meta_where?
I do need/ want real subqueries, no ruby workarounds (using several queries).
Rails now does this by default :)
Message.where(user_id: Profile.select("user_id").where(gender: 'm'))
will produce the following SQL
SELECT "messages".* FROM "messages" WHERE "messages"."user_id" IN (SELECT user_id FROM "profiles" WHERE "profiles"."gender" = 'm')
(the version number that "now" refers to is most likely 3.2)
In ARel, the where() methods can take arrays as arguments that will generate a "WHERE id IN..." query. So what you have written is along the right lines.
For example, the following ARel code:
User.where(:id => Order.where(:user_id => 5)).to_sql
... which is equivalent to:
User.where(:id => [5, 1, 2, 3]).to_sql
... would output the following SQL on a PostgreSQL database:
SELECT "users".* FROM "users" WHERE "users"."id" IN (5, 1, 2, 3)"
Update: in response to comments
Okay, so I misunderstood the question. I believe that you want the sub-query to explicitly list the column names that are to be selected in order to not hit the database with two queries (which is what ActiveRecord does in the simplest case).
You can use project for the select in your sub-select:
accounts = Account.arel_table
User.where(:id => accounts.project(:user_id).where(accounts[:user_id].not_eq(6)))
... which would produce the following SQL:
SELECT "users".* FROM "users" WHERE "users"."id" IN (SELECT user_id FROM "accounts" WHERE "accounts"."user_id" != 6)
I sincerely hope that I have given you what you wanted this time!
I was looking for the answer to this question myself, and I came up with an alternative approach. I just thought I'd share it - hope it helps someone! :)
# 1. Build you subquery with AREL.
subquery = Account.where(...).select(:id)
# 2. Use the AREL object in your query by converting it into a SQL string
query = User.where("users.account_id IN (#{subquery.to_sql})")
Bingo! Bango!
Works with Rails 3.1
Another alternative:
Message.where(user: User.joins(:profile).where(profile: { gender: 'm' })
This is an example of a nested subquery using rails ActiveRecord and using JOINs, where you can add clauses on each query as well as the result :
You can add the nested inner_query and an outer_query scopes in your Model file and use ...
inner_query = Account.inner_query(params)
result = User.outer_query(params).joins("(#{inner_query.to_sql}) alias ON users.id=accounts.id")
.group("alias.grouping_var, alias.grouping_var2 ...")
.order("...")
An example of the scope:
scope :inner_query , -> (ids) {
select("...")
.joins("left join users on users.id = accounts.id")
.where("users.account_id IN (?)", ids)
.group("...")
}

Resources