Rails how to get leaf nodes of model linked list - ruby-on-rails-3

I want to create a linked list of models, sort of this structure
A <- B <- C // (C is the leaf)
D <- E <- F // (F is the leaf)
G <- H // (H is the leaf)
I // (I has no children, I itself is the leaf)
I have done it in a ruby model like this
class Node < ActiveRecord::Base
has_one :child, class_name: "Node", foreign_key: "parent_id", dependent: :destroy
belongs_to :parent, class_name: "Node"
end
I want to get a list of leaf nodes only i.e. C, F, H and I, I know how I'll do this in SQL, how do I do it in rails? I am on rails 3.1.

One pure ruby solution and not so performant:
Node.all.select{|n| n.child.blank?}
Basically writing a SQL solution in rails:
Node.joins("LEFT JOIN nodes AS children ON nodes.id = children.parent_id").where("children.id IS NULL")

Related

Rails admin gem, polymorphic association

How to get the queried data having polymorphic association
I have an 3 models
class Picture < ApplicationRecord
belongs_to :imageable, polymorphic: true
end
class Employee < ApplicationRecord
has_many :pictures, as: :imageable
end
class Product < ApplicationRecord
has_many :pictures, as: :imageable
end```
Employee and Product have column **is_active**.
In rails admin dropdown. I want to display the data where employee and product is **is_active = true.**
Have tried multiple ways to achieve this.
Please let me know if there is any solution?
You can write a custom scope in picture model as below
scope :list_active(imageable_type), -> {
where(imageable_type: imageable_type)
.joins("INNER JOIN #{imageable_type.pluralize} ON
{#imageable_type.pluralize}.id = imageable_id AND
imageable_type = '#{imageable_type}'")
.where('#{imageable_type.pluralize}.is_active = ?', true)
}
Then you can simply list and use the response.
E.g result = []
result << Image.list_active('Employee')
result << Image.list_active('Product')

SQL into ActiveRecord scope: Retrieve Records with given association, or whose nested objects have the given association

How do I translate this SQL into an ActiveRecord scope?
SELECT * FROM trxes
WHERE date="2022-05-01"
AND id IN (SELECT lines.trx_id FROM lines WHERE lines.category_id = 15)
OR id IN (SELECT id FROM trxes WHERE trxes.category_id = 15)
The scope I'm using gets close but returns Trxes outside of May 2022.
scope :category_id, -> (cat_id) { where(lines: Line.where(category_id: cat_id)).or(Trx.where(category_id: cat_id)) }
Trx.date("2022-May-01").category_id(15)
Trx Load (0.3ms) SELECT "trxes".* FROM "trxes" WHERE ("trxes"."date" = ? AND "trxes"."id" IN (SELECT "lines"."trx_id" FROM "lines" WHERE "lines"."category_id" = ?) OR "trxes"."category_id" = ?) [["date", "2022-05-01"], ["category_id", 15], ["category_id", 15]]
More information: A simple Trx has a category= Fuel, Groceries, Entertainment..., and 0 Line objects. A complex Trx has category=Split, and it has multiple Line objects which themselves have a category= Fuel, Groceries, Entertainment...
I want to query Trx model for category=Fuel, and retrieve all simple Trxes where category=Fuel, and complex Trxes containing a Line that has category=Fuel.
class Trx < ApplicationRecord
belongs_to :category, optional: true
has_many :lines, dependent: :destroy
...
class Line < ApplicationRecord
belongs_to :trx
belongs_to :category, optional: true
...
class Category < ApplicationRecord
has_many :lines
has_many :trxes
...
Additional effort I would like to combine the results of the following two ActiveRecord queries
Trx.where(date:"2022-May-01").where(category_id: 15)
Trx.where(date:"2022-May-01").where(lines: Line.where(category_id: 15))

How to write a Rails SQL query for finding an object where all children have an equal value

I've been reading this, but can't make sense of writing it into a Rails scope :
find all parent records where all child records have a given value (but not just some child records)
I have a Course, Section, and Quiz, object :
class Course < ActiveRecord::Base
has_many :course_members
has_many :members, through: :course_members
has_many :sections
has_many :quizzes, through: :sections
end
class Quiz < ActiveRecord::Base
belongs_to :member
belongs_to :section
end
class Section < ActiveRecord::Base
belongs_to :course
has_many :quizzes
end
I'd like to find all courses of a member, where all quizzes related to that course have the attribute completed = true.
So in my Member class, I'd ideally like to write something like :
has_many :completed_courses, -> {
joins(:courses, :quizzes, :sections)
# .select( 'CASE WHEN quizzes.completed = true then 1 end') ??? maybe ???
}, class_name: 'Course'
Haha! But barring that being too complicated. I've been trying to write this simply in the Course would also be fine.
class Member < ActiveRecord::Base
has_many :courses, through: :course_members
has_many :course_members
has_many :completed_courses,
-> { joins(:quizzes).where.not(quizzes: {completed: [false, nil]}) },
through: :course_members,
source: :course
end
If your completed boolean column is NOT NULL, then change [false, nil] above to just simply false
Usage Example
irb(main):002:0> Member.first.completed_courses
Member Load (0.2ms) SELECT "members".* FROM "members" ORDER BY "members"."id" ASC LIMIT 1
Course Load (0.1ms) SELECT "courses".* FROM "courses" INNER JOIN "sections" ON "sections"."course_id" = "courses"."id" INNER JOIN "quizzes" ON "quizzes"."section_id" = "sections"."id" INNER JOIN "course_members" ON "courses"."id" = "course_members"."course_id" WHERE (NOT (("quizzes"."completed" = 'f' OR "quizzes"."completed" IS NULL))) AND "course_members"."member_id" = ? [["member_id", 1]]

ActiveRecord query based on multiple objects via has_many relationship

I have a Product class that has_many Gender through Connection class instances. I want to query to find products that have both end_a and end_b present. The current class method works with 2 caveats:
Fails to return correctly if searching where end_a and end_b are the same. Instead should search if product has 2 instances, not just one of object.
Returns an Array when I want an ActiveRecord_Relation.
The class method .query is below, any feedback or ideas are appreciated.
class Product < ActiveRecord::Base
has_many :connections, dependent: :destroy, as: :connectionable
has_many :genders, through: :connections
def self.query(end_a, end_b)
search_base = active.joins(:connections)
end_a_search = search_base.where(connections: { gender_id: end_a } )
end_a_search & search_base.where(connections: { gender_id: end_b } )
end
end
ps: Once this is figured out will likely move this to a scope for Product
class Product < ActiveRecord::Base
has_many :connections, dependent: :destroy, as: :connectionable
has_many :genders, through: :connections
scope :with_genders, -> (end_a, end_b) {
relation = joins('INNER JOIN connections c1 ON c1.connectionable_id = products.id AND c1.connectionable_type = \'Product\'')
.joins('INNER JOIN connections c2 ON c1.connectionable_id = c2.connectionable_id AND c2.connectionable_type = \'Product\'')
.where(c1: {gender_id: end_a}, c2: {gender_id: end_b})
.group('products.id')
end_a == end_b ? relation.having('COUNT(products.id) > 1') : relation
}
end

Why are individual SELECT queries running when an all-encompassing SELECT already ran? (Rails/ActiveRecord)

I have the following code (note the includes and the .each):
subscribers = []
mailgroup.mailgroup_members.opted_to_receive_email.includes(:roster_contact, :roster_info).each { |m|
subscribers << { :EmailAddress => m.roster_contact.member_email,
:Name => m.roster_contact.member_name,
:CustomFields => [ { :Key => 'gender',
:Value => m.roster_info.gender.present? ? m.roster_info.gender : 'X'
} ]
} if m.roster_contact.member_email.present?
}
subscribers
Correspondingly, I see the following in my logs (i.e. select * from ROSTER_INFO ... IN (...)):
SELECT `ROSTER_INFO`.* FROM `ROSTER_INFO` WHERE `ROSTER_INFO`.`ID` IN ('1450', '1000', '1111')
Yet immediately after that there are select * from ROSTER_INFO for each ID already specified in the IN list of the previous query:
RosterInfo Load (84.8ms) SELECT `ROSTER_INFO`.* FROM `ROSTER_INFO` WHERE `ROSTER_INFO`.`ID` = '1450' LIMIT 1
RosterInfo Load (59.2ms) SELECT `ROSTER_INFO`.* FROM `ROSTER_INFO` WHERE `ROSTER_INFO`.`ID` = '1000' LIMIT 1
RosterInfo Load (56.8ms) SELECT `ROSTER_INFO`.* FROM `ROSTER_INFO` WHERE `ROSTER_INFO`.`ID` = '1111' LIMIT 1
If select * had already been done on ROSTER_INFO on all IDs of interest (IN (...)), why is another select * being done again for each of the same IDs? Doesn't ActiveRecord already know all the ROSTER_INFO columns for each ID?
(Meanwhile, there are no individual queries for ROSTER_CONTACT, yet if I remove :roster_contact from the includes method, then ROSTER_INFO is not queried again, but ROSTER_CONTACT is.)
RosterInfo model (abridged)
class RosterInfo < ActiveRecord::Base
self.primary_key = 'ID'
end
RosterContact model (abridged)
class RosterContact < ActiveRecord::Base
self.primary_key = 'ID'
has_many :mailgroup_members, foreign_key: 'rosterID'
has_many :mailgroups, through: :mailgroup_members
has_one :roster_info, foreign_key: 'ID' # can use this line
#belongs_to :roster_info, foreign_key: 'ID' # or this with no difference
def member_name # I added this method to this
roster_info.member_name # question only *after* having
end # figured out the problem.
end
RosterWeb model (abridged)
class RosterWeb < ActiveRecord::Base
self.primary_key = 'ID'
end
Mailgroup model (abridged)
class Mailgroup < ActiveRecord::Base
self.primary_key = 'ID'
has_many :mailgroup_members, foreign_key: 'mailCatID'
has_one :mailing_list, foreign_key: :legacy_id
end
MailgroupMember model (abridged)
class MailgroupMember < ActiveRecord::Base
self.primary_key = 'ID'
belongs_to :mailgroup, foreign_key: 'mailCatID'
belongs_to :roster_contact, foreign_key: 'rosterID'
belongs_to :roster_info, foreign_key: 'rosterID'
belongs_to :roster_web, foreign_key: 'rosterID'
scope :opted_to_receive_email, joins(:roster_web).where('ROSTER_WEB.receiveEmail=?', 1)
end
The issue turned out to be related to m.roster_contact.member_name -- unfortunately I made member_name a method of roster_contact that itself (indirectly) queried roster_info.member_name. I resolved this by changing the line
:Name => m.roster_contact.member_name,
to directly query roster_info as follows
:Name => m.roster_info.member_name,
I am sorry for the trouble!
I'm going to stick my neck out and say that this is probably an in-flight optimization by your query engine. The 'IN' is typically used to compare large sets of keys, the most efficient way of resolving three keys (assuming ID is the key) would be to retrieve each row by key, as has happened.
class RosterInfo < ActiveRecord::Base
has_one :roster_contact, foreign_key: 'ID'
end
class RosterContact < ActiveRecord::Base
has_one :roster_info, foreign_key: 'ID'
end
I don't know what is the premise for having bi-directional has_one, but I suspect it will turn out badly. Probably change one of them to belongs_to. Do the same for the other bi-directional has_one associations.
Another thing is that you are using 'ID' for the foreign_key column, where the usual practice is roster_contact_id or whichever class you are referencing.
Edit:
On closer examination, RosterInfo, RosterContact, RosterWeb look like separate tables for what should be a single record since they are all having the same set of mutual has_one associations. This is something that should be addressed on the schema level, but right now you should be able to drop the has_one associations from one of the three models to solve your immediate problem.