Rails Active Record complex query - ruby-on-rails

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})
?

Related

Retrieving data in a hash

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.

Aggregate values on an attribute returned by a has_many through

Data structure is as follows:
class Job
has_many job_sections
has_many job_products, through job_sections
end
class JobSection
belongs_to job
has_many job_products
end
class JobProduct
belongs_to product
belongs_to job_section
end
When I call job.job_products i could end up with something like this:
#<JobProduct:0x007ff4128b0ca0
id: 18133,
product_id: 250,
quantity: 3,
frozen_cache: {},
discount: 0.0,
#<JobProduct:0x007ff4128b00c0
id: 18134,
product_id: 250,
quantity: 1,
frozen_cache: {},
discount: 0.0]
As you can see the product_id is identical in both instances.
How do I merge the contents of the arrays by product id so I retrieve and act on them as aggregated values?
In a way, I need to be able to act on job products by their product_id rather than their id.
Effectively the result being something like this...
[#<SomeFancySeerviceObjectMaybe?
product_id: 250,
quantity: 4,
frozen_cache: {},
discount: 0.0]
Do I opt for a little Plain Old Ruby Object to handle them all, or do I have to rethink the architecture of this, or is there (hopefully!) a bit of Rails secret sauce that than can help me out?
*FYI Job Section is a recent addition to the architecture, and I don't think its has been particularly well thought through. However, I can't spend too much time reversing what's already in place.
This set up isn't ideal, I'm probably the sixth dev in as many years to start picking this apart.
Your suggestions are most welcome. Thank you
In SQL this would be something like this:
SELECT SUM(quantity)
FROM job_products
WHERE product_id = 250
GROUP BY product_id
You can do that in ActiveRecord too. If you just want an integer, you can use pluck:
total_quantity = job.job_products.
group(:product_id).
pluck("SUM(job_products.quantity)").
first
You can also pluck several columns if you want (in Rails 4+), which is why it returns an array. So if you want average discount at the same time, it's easy.
If you would prefer a JobProduct instance, you can get that too, but in your case a lot of the attributes will be nil because of the grouping. But you can say:
summary = job.job_products.
group(:product_id).
select("SUM(job_products.quantity) AS total_quantity").
first
And you'll get a read-only JobProduct with an extra attribute named total_quantity. So you can do summary.total_quantity. But because of the grouping, summary will have a nil id, discount, etc. Basically it will only have attributes matching the things you select. This is a little weird, but sometimes it lets you write methods that work both on "real" JobProducts and for these summary versions.

Getting the correct users with activerecord

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

Array results using Active Record Query Model 'select' and 'where'

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)

How can I write this query efficiently in Ruby on Rails?

I have three tables: Accounts, Investments, and Games. An Investment has an account_id, game_id, some statistic counters, and is created the first time an Account participates in a Game.
I want to provide a JSON list of the latest Games along with the user's Investment in that Game, like this:
[{id: 666, name: "Foobar", ..., investment: {tokens: 58, credits: 42, ...}},...]
If they have not yet participated in the game, I still want to include an Investment object with default values, so I overrode the serializable_hash function in my Game model:
# game.rb
has_many :investments
def serializable_hash(options=nil)
options ||= {}
i = investments.find_or_initialize_by_account_id options[:uid]
{:id => id, ..., :investment => i.serializable_hash}
end
However, when I run something like Game.find(list_of_ids).to_json(:uid => current_user.id), Rails does a separate query on the Investments table for each Game. I tried Game.includes(:investments).find(list_of_ids).to_json(:uid => current_user.id) but not only does that load the investments for all users, it still does a separate query for each game to find or initialize the investment object.
In short, given a list of game IDs and an account id, what's a clean way to load the associated Investment objects that exist in one query, and initialize the rest?
You want to give the list of ids to the server in one go. I use the IN operator for this:
Game.includes(:investments).where("games.id IN (?)", list_of_ids)

Resources