I need to get some data from ActiveRecord, I have following two tables Department and users I have issue that I am getting one hash in which user is giving me user_ids and emails, now I want to create hash container users, departments and emails in specific format. I have tried a lot map/select but still could not figure out any simple way.
class User < ApplicationRecord
belongs_to :department
end
And Department
class Department < ApplicationRecord
has_many :users
end
I am getting following values from user
sample_params = [{ user_id: 1, email: 'example1#example.com' },
{ user_id: 5, email: 'example5#example.com' },
{ user_id: 13, email: 'example13#example.com'}]
Now I have retrieve departments from database and other data and join in one huge hash so I can add it to my class. I can get all my users by following command
users = User.where(id: sample_params.map{ |m| m[:user_id] })
I will get whole ActiveRecord objects with all users if I run following command I am getting all user_id and project_id
users.map { |u| {user_id: u.id, department_id: u.department_id } }
I will get this
[{:user_id=>1, :department_id=>3},
{:user_id=>5, :department_id=>3},
{:user_id=>13, :department_id=>2}]
but I want to create following hash, Is there any simple way to do it directly using query or in other few lines, I can try using Iteration but that would be very long and complicated. As I also need to merge emails in it and add one project instead of same projects multiple ids.
[
{department_id: 1, users: [{user_id: 1, email: 'example1#example.com'},
{user_id: 5, email: 'example5#example.com'}]},
{department_id: 2, users: [{ user_id: 13, email: 'example13#example.com']
I am using here sample data the real data is very very large include hundreds of users and many departments.
I don't think you can do it in one go! let us run throgh and try how can we solve it. First instead of using map in your params let us try another alternate for it. Remove following line
users = User.where(id: sample_params.map{ |m| m[:user_id] })
user following line
User.where(id: sample_params.pluck(:user_id)).pluck(:department_id, :id).grou
p_by(&:first)
This will bring you result in each users id with department grouping in one query
{3=>[[3, 1], [3, 5]], 2=>[[2, 13]]}
Now we are getting department_id and user_id so we will run map on them to get first array with department_id and user_id in a group with following command
data =
User
.where(id: sample_params.pluck(:user_id))
.pluck(:department_id, :id)
.group_by(&:first).map do |department_id, users|
{ department_id: department_id,
users: users.map { |_, id| id } }
end
This will give you hash with department and users
[{:department_id=>3, :users=>[1, 5]}, {:department_id=>2, :users=>[13]}]
Now you have hash of department and users. so let us work on this this is second phase where I will use select to get email from your params and merge it to your data.
result = []
data.each do |department|
department_users = []
department[:users].each do |user|
emails = sample_params.select { |user| user[:user_id] == 1 }[0][:email];
department_users << { id: user, emails: emails }
end; result << {department_id: department[:department_id], users: department_users}
end
Now if you do puts result[0] you will get following hash as you wanted.
{:department_id=>3, :users=>[{:id=>1, :emails=>"example1#example.com"}, {:id=>5, :emails=>"example1#example.com"}]}, {:department_id=>2, :users=>[{:id=>13, :emails=>"example1#example.com"}]}
This will solve issue, I understand there are two operation but there is no double sql queries in single it is working and you are also replacing your emails. I hope it solve you issue, any one can make it simpler would be appreciated.
Related
I have this field called data that was setup with the jsonb_accessor ruby gem like this:
jsonb_accessor :data,
line_items: [:jsonb, array: true, default: []]
This is on my Contract model. And when a Contract is created, its an empty array like this:
data: {"line_items"=>[]},
I want to add multiple hashes to this empty array. For example this:
{user: "Joe"}
Then later I may add the hash:
{user: "Jack"}
I've tried many things, with NO luck including:
new = [{user: "Joe"}].to_json
Contract.last.update_all(["line_items = line_items || ?::jsonb", new])
##
Contract.last.update(["line_items = line_items || ?::jsonb", new])
Then I tried push
Contract.last.line_items.push("line_items" => {user: "Todd"})
Then I tried merge
Contract.last.line_items = Contract.last.line_items.merge(new)
Nothing has worked.
One option would just be to set the attribute with the data you want, then persist it with #save (or #save!)
contract = Contract.create!
=> #<Contract id: 1, data: {"line_items"=>[]}, line_items: []>
contract.line_items = [{foo: "bar"}]
contract.save!
contract
=> #<Contract id: 1, data: {"line_items"=>[{"foo"=>"bar"}]}, line_items: [{"foo"=>"bar"}]>
In my testing, I was able to use the #update method by just passing the object itself (rather than converting it to JSON first):
Contract.update(1, :line_items => [{foo: "bar"}])
=> #<Contract id: 1, data: {"line_items"=>[{"foo"=>"bar"}]}, line_items: [{"foo"=>"bar"}]>
Edit:
It looks like you're trying to append to the array with a single query, which appears to be possible with
Contract.where(id: 1).update_all("data=jsonb_set(data,'{line_items}', data->'line_items' || '{\"foo\": \"bar\"}')")
Though I'm sure there is a better way to achieve the same functionality...
jsonb_accessor means you don't have to manually mess around with JSON for simple accessor operations. If you want to assign to line_items use update! like normal.
contract.update!(line_items: [{user: "Joe"}])
If you want to append to a single Contract's line_items, add to the array in Ruby and update.
contract = Contract.last
line_items = contract.line_items + [{user: "Joe"}]
contract.update!(line_items: line_items)
If you want to do it all in SQL jsonb_accessor offers you no help. You have to write the SQL yourself. To add to an array, use jsonb_insert.
Contract
.where(...)
.update_all(
q[jsonb_insert(data, '{"line_items", -1}', '{user: "Joe"}', true)]
)
'{"line_items", -1}' says to insert at the last element of the key "line_items" and true has to insert after that point.
I'm looping through companies and collecting all of the employee names for each company:
companies = Company.all
companies.each do |company|
employee_names = company.employees.pluck(:name)
# do work, I still need access to the company object
end
That will do a separate query to get the employee names for each company. The Bullet gem suggested to add includes(:employees). So I attempted:
companies = Company.includes(:employees)
companies.each do |company|
employee_names = company.employees.collect(&:name)
# do work
end
This successfully minimizes the amount of queries, but the request actually takes longer! I'm assuming that is because the includes loads the entire set of employees into memory, but I only need the name.
Is there a better way to do an includes but only on a specific column (or columns)?
The better way is DB specific. For PostgreSQL:
SELECT_CLAUSE = <<~SQL
companies.*,
array(
SELECT name FROM employees WHERE company_id = companies.id
) as employee_names
SQL
# ...
companies = Company.select(SELECT_CLAUSE)
companies.each do |company|
# company.employee_names # => Array
end
Or don't instantiate model instances at all:
PLUCK_COLUMN = 'array(SELECT name FROM employees WHERE company_id = companies.id)'
# ...
companies = Company.pluck( :id, :title, PLUCK_COLUMN )
# [
# [ 1, 'Apple', [ 'John', 'Vasya', ... ] ],
# ...
Don't forget about indexes and pagination if you have a lot of data.
Do you have Company indexed on employee_id. It seems to me that the issue is that you're now doing work in the query that you used to do in the application memory. If you want to query company information based on certain employees, which seems to be the case, you will want to have the Company table to have an index on employee_id.
I do not know of any built-in Rails solution for this special use case.
However, you might be able to solve it by emulating sort of what includes does, with the difference that you call pluck in the end:
companies = Company.all.to_a
#=> [#<Company:...>, #<Company:...>, ...]
company_ids = companies.map(&:id)
#=> [1, 2, 3, 5, ...]
company_employee_map = Employee.
where(company_id: company_ids).
pluck(:company_id, :name).
inject({}) { |hash, (company_id, name)|
hash[company_id] ||= []
hash[company_id] << name
hash
}
companies.each do |company|
employee_names = company_employee_map[company.id]
end
The code is untested, so it might need some adjustments.
I'm building an e-commerce app with rails and I have the following models:
Order
id:
user_id:
order_status:
subtotal:
OrderItem
id:
order_id:
product_id:
unit_price:
quantity:
total_price:
Product
id:
base_price:
discount:
price:(base_price less discount)
Each user can have one pending_order (order_status:1) and this order is then considered the "cart"
Then let's say a product get's its price updated, or a new discount amount.
I want to identify all the order_items that belong to a pending order and belong to the product being changed.
This is so that I can update the prices on these pending order items.
How can I write this query so that I can call it in the Product model before_save method?
I want something like (I know this is wrong but just to get an idea):
#items = OrderItem.where(order.order_status:1).where(product_id:self.id)
I think I need to use .joins() but I'm at a lose for how to get this working.
Can anyone help?
Something like this:
#items = OrderItem.joins(:order).where(product_id: 1, orders: { order_status: 1})
?
Currently I have a table of 10,000 users who I want to search. I also have another table of 3 users that I have approved. I want to make it so that the search result only returns 9997 of the users. In other words, if I have approved a user, I do not want them to appear in the search result. Here is what I have so far for the code:
approved_users = Approval.where(user_id: logged_in_user.id)
approved_user_ids = approved_users.map { |user| user.approved_user_id}
pre_search_results = User.find_by_sql("SELECT *
FROM users LEFT JOIN approvals
ON users.id = approvals.approved_user_id
WHERE name LIKE ? OR operator_id LIKE ?"
)
The first statement approved_users returns this when I manually added 3 users:
id: 10, user_id: 39, approved_user_id: 37
id: 11, user_id: 39, approved_user_id: 35>,
id: 12, user_id: 39, approved_user_id: 41>
I then made approved_user_ids just give me the id portion of the above statement, so it returns this:
[37, 35, 41]
Now, I want pre_search_results to return me the 9997 other users and I have the above statement. It is currently returning nothing. Any ideas what can be done?
To get all users whose ids are not present in approved_user_ids, you can use: pre_search_results = User.where("id NOT IN (?)", approved_user_ids)
My thought is to split your two different conditions into two different scopes, which can then be chained together...
I'm also assuming a relationship in your User model.
user.rb
class User
has_many :approvals
scope :has_been_approved, -> { joins(:approvals) }
scope :has_not_been_approved, -> ( includes(:approval).where(approvals: { user_id: nil })
scope :search_by_name, ->(term) { where("name LIKE :t", t: term) }
...which lets you do something in your controller (or wherever) like...
User.has_not_been_approved.search_by_name("Rodolfo")
The scope for has_not_been_approved is a little awkward. Here's where I found the example: Rails find record with zero has many records associated
I have a user, membership and group model. A user has many memberships, and groups through memberships. A group has many memberships, and users through memberships. Memberships belongs to both.
When a user accesses the users index view I want to show him/her all users that belong to the group he/she belongs to: I created the following code, which does not work. I cannot seem to get the query to work. Pease help!
The code in the Users controller
class UsersController < ApplicationController
def index
#users = current_user.relevant_members
end
end
The code in the User model
def relevant_members
group_ids = self.group_ids
user_ids = Membership.select("user_id").where("group_id IN (#{group_ids.join(", ")})")
User.find(user_ids)
end
I don't understand why, when tested in the console, user_ids is not an array of ids, but the following array:
[#<Membership user_id: 2>, #<Membership user_id: 2>, #<Membership user_id: 3>, #<Membership user_id: 3>, #<Membership user_id: 3>, #<Membership user_id: 2>]
What would be a better way of returning an array of user ids to find all relevant group members?
Membership.select("user_id").where("group_id IN (#{group_ids.join(", ")})")
returns always an array of Membership objects, select("user_id") changes only the fields that are loaded from DB to the object.
It's enough to map it to id
user_ids = Membership.select("user_id").where("group_id IN (#{group_ids.join(", ")})").map(&:user_id)
Membership.select('user_id') will return an array of Membership objects with just the user_id attribute, that's correct so far. It will work if you extract the ids from the result:
membershipts = Membership.select("user_id").where("group_id IN (#{group_ids.join(", ")})")
user_ids = memberships.map(&:user_id)
Best regards
Tobias
Did you try the pluck() function ?
https://apidock.com/rails/ActiveRecord/Calculations/pluck
user_ids = Membership.pluck("user_id").where("group_id IN (#{group_ids.join(", ")})")
This is because your user_ids is an object of the class Membership and hence you can't get an array of the 'user_ids' directly
You have to do something like following
user_ids = Membership.select("user_id").where("group_id IN (#{group_ids.join(", ")})")
user_ids_array = user_ids.collect{|u| u.user_id}
User.find(user_ids_array)
Also you can modify your where clause as follow, which is more ruby way.
user_ids = Membership.select("user_id").where(["group_id IN (?)", group_ids])
Membership.where("group_id in (#{groups.collect(&:id).join(',')})")
.all.collect(&:user_id)