Need array of self, children and children childrens in rails - mysql

I have a table like following
well,here in this table every user has a parent user,then if we select a user then its id ,children ids and children childrens ids should return as array.I need a query to get this values in rails with out using any gem.Thanx for your help:->

class User << ActiveRecord::Base
def self.build_tree(reverse=false)
g = Array.new
self.find(:all).each do |p|
if reverse
g << [p.id,p.parent]
else
g << [p.parent,p.id]
end
end
g
end
def self.subordinates(man_id)
g = self.build_tree false
g.map{|i| i[1] if i[0]==man_id.to_i}.compact
end
def self.superiors(user_id)
g = self.build_tree true
g.map{|i| i[1] if i[0]==user_id.to_i}.compact
end
end
When call either Superiors(parents) or Subordinates(childrends) it will gives required result
Ex:- [2,4,6,8]
If you want to get either children->childrends or parent->parents just do iterate call function either superior or subordinates until get the nil or [] array .

You are stranding into SQL anti-pattern. Performing operations on trees constructed like that is very inefficient. I don't say, that you should use a gem for that, but consider using some smarter method of keeping this data (searching for sql tree structure should yield some meaningful results).
Query you are looking for needs two self joins:
SELECT t1.id user_ids, t2.id children_ids, t3.id children_children_ids FROM users t1
LEFT JOIN users t2 ON t2.parent = t1.id
LEFT JOIN users t3 ON t3.parent = t2.id
On the other hand, if your rails models have defined self-relation, you could easily write:
user.children #=> (array of children)
user.children.flat_map(&:children) #=> (array of grandchildren)
Definition of this relation should look like:
class User << ActiveRecord::Base
has_many :children, class_name: User, foreign_key: 'parent'
end

Related

Use distinct method with order on a many to many relation with ActiveRecord

I have a classic many to many relationship defined like this in ActiveRecord:
class Developer < ApplicationRecord
has_many :developers_code_reviews
has_many :code_reviews, through: :developers_code_reviews
end
class DevelopersCodeReview < ApplicationRecord
belongs_to :code_review
belongs_to :developer
end
class CodeReview < ApplicationRecord
has_many :developers_code_reviews
has_many :developers, through: :developers_code_reviews
end
and I basically want to have a Developer array sorted by code_review.created_at without doubles.
My first attempt was the basic one: Developer.order('code_reviews.created_at': :asc) which triggers this error: ActiveRecord::StatementInvalid: Mysql2::Error: Unknown column 'code_reviews.created' in 'order clause.
After a few googling I understood the join was not automatically performed by ActiveRecord so I added it: Developer.joins(:code_reviews).order('code_reviews.created_at': :asc). This one works but has doubles inside. I need to have a developer to appear only once in this array.
If I try to create a distinct on that query, ActiveRecord/MySQL complains that the ORDER BY is not performed on a column that is in the SELECT. How can I solve this problem?
I googled it quite a lot I can't find anything.
NOTE
The MYSQL query which works but with doubles looks like this:
SELECT `developers`.*
FROM `developers`
INNER JOIN `developers_code_reviews` ON `developers_code_reviews`.`developer_id` = `developers`.`id`
INNER JOIN `code_reviews` ON `code_reviews`.`id` = `developers_code_reviews`.`code_review_id`
ORDER BY `code_reviews`.`created_at` ASC
and I'd like to have a distinct on the developers.
I found the answer thanks to this clear example on MySQL sub queries: http://www.mysqltutorial.org/mysql-subquery/
With this example, I understood that I needed a sub query looking like this:
SELECT *
FROM developers
LEFT OUTER JOIN (
SELECT developer_id, max(updated_at) max_updated_at
FROM developers_code_reviews
GROUP BY developer_id
) dcr
ON developers.id = dcr.developer_id
ORDER BY maxupdt
Translated to ruby in my case:
class DevelopersCodeReview < ApplicationRecord
belongs_to :code_review
belongs_to :developer
class << self
def developer_queue
select('developer_id, max(updated_at) max_updated_at').
group(:developer_id)
end
end
end
class Developer < ApplicationRecord
belongs_to :slack_workspace
belongs_to :project, optional: true
class << self
def queue
developer_queue = DevelopersCodeReview.developer_queue.to_sql
joins("LEFT OUTER JOIN (#{developer_queue}) dcr ON id = dcr.developer_id").
order(max_updated_at: :asc)
end
end
end

Rails 5 left outer join using includes() and where()

I'm having a heck of a time getting the intended behavior using includes() and where().
Result I want:
- All students (even if they have zero check-ins)
- All check-ins in the Library
Result I'm getting:
- Only students with check-ins in the library
- All check-ins in the library, for those students
Currently my code is based off of this:
http://edgeguides.rubyonrails.org/active_record_querying.html#specifying-conditions-on-eager-loaded-associations
Which describes the behavior I want:
Article.includes(:comments).where(comments: { visible: true })
If, in the case of this includes query, there were no comments for any
articles, all the articles would still be loaded.
My code:
#students = Student.includes(:check_ins)
.where(check_ins: {location: "Library"})
.references(:check_ins)
.
class CheckIn < ApplicationRecord
belongs_to :student
end
.
class Student < ApplicationRecord
has_many :check_ins, dependent: :destroy
end
The generated SQL query:
SELECT "students"."id" AS t0_r0,"check_ins"."id" AS t1_r0, "check_ins"."location" AS t1_r1, "check_ins"."student_id" AS t1_r6 FROM "students" LEFT OUTER JOIN "check_ins" ON "check_ins"."student_id" = "students"."id" WHERE "check_ins"."location" IN ('Library')
This SQL query gives the join behavior I want:
SELECT first_name, C.id FROM students S LEFT OUTER JOIN check_ins C ON C.student_id = S.id AND location IN ('Library');
Tried a new approach using Scopes with relations, expecting to preload everything and filter it out, but was pleasantly surprised that Scopes actually give me the exact behavior I want (right down to the eager loading).
Here's the result:
This ActiveRecord Call pulls in the full list of students and eager loads the check-ins:
#students = Student.all.includes(:check_ins)
The scope of check_ins can be limited right in the has_many declaration:
Class Student < ApplicationRecord
has_many :check_ins, -> {where('location = 'Library'}, dependent: :destroy
end
Resulting in two clean, efficient queries:
Student Load (0.7ms) SELECT "students".* FROM "students"
CheckIn Load (1.2ms) SELECT "check_ins".* FROM "check_ins" WHERE location = 'Library') AND "check_ins"."student_id" IN (6, 7, 5, 3, 1, 8, 9, 4, 2)
Bingo!
p.s. you can read more about using scopes with assocations here:
http://ducktypelabs.com/using-scope-with-associations/
What you want in terms of pure SQL is:
LEFT OUTER JOIN "check_ins" ON "check_ins"."student_id" = "students"."id"
AND location IN ('Library')
However it is not possible (afaik) to get ActiveRecord to mark the association as loaded without trickery*.
class Student < ApplicationRecord
has_many :check_ins
def self.joins_check_ins
joins( <<~SQL
LEFT OUTER JOIN "check_ins" ON "check_ins"."student_id" = "students"."id"
AND location IN ('Library')
SQL
)
end
end
So if we iterate though the result it will cause a N+1 query issue:
irb(main):041:0> Student.joins_check_ins.map {|s| s.check_ins.loaded? }
Student Load (1.0ms) SELECT "students".* FROM "students" LEFT OUTER JOIN "check_ins" ON "check_ins"."student_id" = "students"."id"
AND location IN ('Library')
=> [false, false, false]
irb(main):042:0> Student.joins_check_ins.map {|s| s.check_ins.size }
Student Load (2.3ms) SELECT "students".* FROM "students" LEFT OUTER JOIN "check_ins" ON "check_ins"."student_id" = "students"."id"
AND location IN ('Library')
(1.2ms) SELECT COUNT(*) FROM "check_ins" WHERE "check_ins"."student_id" = $1 [["student_id", 1]]
(0.7ms) SELECT COUNT(*) FROM "check_ins" WHERE "check_ins"."student_id" = $1 [["student_id", 2]]
(0.6ms) SELECT COUNT(*) FROM "check_ins" WHERE "check_ins"."student_id" = $1 [["student_id", 3]]
To be honest, I never like preloading only a subset of association
because some parts of your application probably assume that it is
fully loaded. It might only make sense if you are getting the data to
display it.
- Robert Pankowecki, 3 ways to do eager loading (preloading) in Rails 3 & 4
So in this case you should consider preloading all the data and using something like a subquery to select the count of check_ins.
I would also advise you to create a separate table for locations.
I think this is the only way to create the query you want.
Student.joins("LEFT OUTER JOIN check_ins ON check_ins.student_id = students.id AND check_ins.location = 'Library'")
Reference : http://apidock.com/rails/ActiveRecord/QueryMethods/joins

Group by one column and sum by another

Trying to write rake task that contains a query that will group by one value on a join table and then sum another column. I'd like to do it using the query interface. Purpose of this task is to find the videos that have been the most popular over the last 5 days.
In pertinent part:
course_ids = Course.where(school_id: priority_schools).pluck(:id)
sections = Section.where(course_id: course_ids)
sections.each do |section|
users = section.users.select {|user| user.time_watched > 0}
user_ids = []
users.each { |user| user_ids << user.id }
user_videos = UserVideo.group(:video_id).
select(:id, :video_id, :time_watched).
where("created_at > ?", Date.today - 5.days).
where(user_id: user_ids).sum(:time_watched)
p "user_videos: #{user_videos.inspect}"
end
Any suggestions for the how / the best way to write this query?

Converting SQL to ActiveRecord Query

I'm having troubles converting the following SQL query to an ActiveRecord query.
This would be the query in raw SQL:
SELECT customers.name, customers.email, customers.address, (sales.quantity * sales.price AS total_spent)
FROM customers JOIN sales ON customers.id = sales.customer_id
GROUP BY customers.id
ORDER BY total_spent DESC
I've done this to start working on that for ordering the top customers but it doesn't work:
Customer.joins(:buys).group("customers.id").select("id", "name", "price"*"stock" as "total_buys")
Any help would be really appreciated.
Thanks.
First set up your model associations and add some convenience methods
class Customer < ActiveRecord::Base
has_many :sales
# if you add a subtotal field that is autocalculated you could also do this
def total_spent
self.sales.all.map(|sale| sale.total ).sum
end
end
class Sale < ActiveRecord::Base
belongs_to :customer
def total
self.price * self.amount
end
end
Then in your app or the console you can type:
Customer.first.sales.map(&:total).sum
Or all Customers:
Customer.all.each{ |c| puts c.sales.map(&:total).sum }

Fetch all table name and row count for specific table with Rails?

How can i fetch all the table name and row count for the specific table from the specific database ?
Result
Table Name , Row Count , Table Size(MB)
---------------------------------------
table_1 , 10 , 2.45
table_2 , 20 , 4.00
ActiveRecord::Base.connection.tables.each do |table|
h = ActiveRecord::Base.connection.execute("SHOW TABLE STATUS LIKE '#{table}'").fetch_hash
puts "#{h['Name']} has #{h['Rows']} rows with size: #{h['Data_length']}"
end
The question is tagged mysql but you can do it in a DB-agnostic manner via ORM.
class DatabaseReport
def entry_counts
table_model_names.map do |model_name|
entity = model_name.constantize rescue nil
next if entity.nil?
{ entity.to_s => entity.count }
end.compact
end
private
def table_model_names
ActiveRecord::Base.connection.tables.map(&:singularize).map(&:camelize)
end
end
Note that this will skip tables for which you don't have an object mapping such as meta tables like ar_internal_metadata or schema_migrations. It also cannot infer scoped models (but could be extended to do so). E.g. with Delayed::Job I do this:
def table_model_names
ActiveRecord::Base.connection.tables.map(&:singularize).map(&:camelize) + ["Delayed::Job"]
end
I came up with my own version which is also db agnostic.
As it uses the descendants directly it also handles any tables where the table_name is different to the model name.
The rescue nil exists for cases when you have the class that inherits from ActiveRecord but for some reason don't have a table associated with it. It does give data for STI classes and the parent class.
my_models = ActiveRecord::Base.descendants
results = my_models.inject({}) do |result, model|
result[model.name] = model.count rescue nil
result
end
#temp_table = []
ActiveRecord::Base.connection.tables.each do |table|
count = ActiveRecord::Base.connection.execute("SELECT COUNT(*) as count FROM #{table}").fetch_hash['count']
size = ActiveRecord::Base.connection.execute("SHOW TABLE STATUS LIKE '#{table}'").fetch_hash
#temp_table << {:table_name => table,
:records => count.to_i,
:size_of_table => ((BigDecimal(size['Data_length']) + BigDecimal(size['Index_length']))/1024/1024).round(2)
}
end
end