Let's assume I have a grandparent document with many parents, and each parent has many children.
What is the best way, in Rails with Mongoid, to get all of the children for a specific grandparent without looping?
For example, if I were to use loops, it would look something like this (rough code):
def children
children = []
parents.each do |p|
p.children.each do |c|
children << c
end
end
children.uniq
end
class Grandparent
include Mongoid::Document
has_many :parents
end
class Parent
include Mongoid::Document
belongs_to :grandparent
has_many :children
end
class Child
include Mongoid::Document
belongs_to :parent
end
A method like this, it would load the children as an attribute once called.
def children(reload=false)
#children = nil if reload
#children ||= Child.where(:parent_id.in => parents.map(&:id))
end
See this SO answer as well
Related
I have a blog with subcategories/main categories and on the main category, I want it to list the posts from all of its child categories. I got it working with using the method .first but I just don't know how to handle this in the way I need it to.
BlogCategory Model:
class BlogCategory < ApplicationRecord
extend FriendlyId
friendly_id :name, use: :slugged
has_many :posts
# This is called a self referential relation. This is where records in a table may point to other records in the same table.
has_many :sub_categories, class_name: "BlogCategory", foreign_key: :parent_id
belongs_to :parent, class_name: 'BlogCategory', foreign_key: :parent_id
# This is a scope to load the top level categories and eager-load their posts, subcategories, and the subcategories' posts too.
scope :top_level, -> { where(parent_id: nil).includes :posts, sub_categories: :posts }
def should_generate_new_friendly_id?
slug.nil? || name_changed?
end
end
blog_categories Controller:
def show
#cat = BlogCategory.friendly.find(params[:id])
#category = #cat.parent
#posts = #cat.posts
#sub_category = #cat.sub_categories.first
unless #sub_category.nil?
#relatives = #sub_category.posts
end
end
private
def cat_params
params.require(:blog_category).permit(:name, :parent_id, :sub_category)
end
def main_cat
#cat = BlogCategory.parent_id.nil?
end
Post Model: belongs_to :blog_category
I have tried a few configurations of .all .each and seen if .collection worked, but these didn't seem to fix my problem.
Thank you I do appreciate it.
You can add a has many association in your Category model like this
has_many :sub_category_posts, through: :sub_categories, source: :posts
In your controller
#relatives = #cat.sub_category_posts
I guess you want all the posts. If a post belongs to a category, that category will be a child of another category, and eventually, you'll have the main category, so, you could do something like:
#posts = Post.where.not(blog_category: nil)
If you have many main categories, one per blog, you need to implement a recursive method.
You could also use https://github.com/collectiveidea/awesome_nested_set and do something like:
#cat.descendants # array of all children, children's children, etc., excluding self
https://github.com/collectiveidea/awesome_nested_set/wiki/Awesome-nested-set-cheat-sheet
My problem:
I have Three models: Company, Parent, and Child.
Child belongs_to Parent which belongs_to Company
I need to get all children where a certain attribute is set to false, within one company. (The Parent model has a company_id while the Child model does not.)
What I'm trying:
I have the following join:
#objects = Parent.joins(:childs).where('parents.company_id' => current_user.company_id, 'childs.foo' => false)
In my view:
<!-- This should be a list of child objects -->
<% #objects.each do |obj| %>
<%= obj.foo %>
<% end %>
(foo being the attribute of the child object)
Models:
class Company < ActiveRecord::Base
has_many :parents, :dependent => :destroy
...
end
class Parent < ActiveRecord::Base
has_many :childs, :dependent => :destroy
belongs_to :company
...
end
class Child < ActiveRecord::Base
belongs_to :parent
...
end
However, writing the Parent.joins(:childs)... returns an ActiveRecord relation of Parent objects. (Throwing an error when I try to access the child attributes) I need the end list to be of child objects. But I am finding it difficult to do so.
A good answer to this question would be one that:
Solved this problem in another way that made more sense while not being too computationally intensive.
Or something that shows how to get a list/relation of child objects instead of the parent objects.
Simple, start with the Child class:
Child.joins(:parent).where(parents: {company_id: current_user.company_id}, foo: false)
I would probably advise using scopes/class methods to accomplish this in a cleaner fashion:
class Parent < ActiveRecord::Base
def self.for_user(user)
where(company_id: user.company_id)
end
end
class Child < ActiveRecord::Base
scope :fooless, ->{where foo: false}
end
Now you can do this:
Child.joins(:parent).merge(Parent.for_user(current_user)).fooless
Is there a way to easily and efficiently fetch the corresponding (child) models of a parent model and then render it in a template? I would like to know how to do it with and without joins
For Example, Consider these 3 tables:
# ProductGroup is the highest parent
class ProductGroup < ActiveRecord::Base
attr_accessible :name, :merchant_id
has_many :product_items
has_many :product_group_selections
end
# ProductItem is a child of ProductGroup
class ProductItem < ActiveRecord::Base
attr_accessible :base_price, :name, :product_group_id
belongs_to :product_group
end
# ProductGroupSelection is a child of ProductGroup
class ProductGroupSelection < ActiveRecord::Base
attr_accessible :name, :price_extra, :product_attr_group_id, :product_item_id
belongs_to :product_group
has_many :product_group_selection_attrs
end
# ProductGroupSelectionAttr is a child of ProductGroupSelection
class ProductGroupSelectionAttr < ActiveRecord::Base
attr_accessible :name, :product_group_id
belongs_to :product_group_selection
end
What I want is a data-structure that looks like this (when searching product_groups for merchant_id = 1)
merchant_id 1 => {
ProductGroup.name, ProductGroup.merchant_id,
ProductItems => [...],
ProductGroupSelections => {ProductGroupSelections.name, ProductGroupSelectionAttrs => [...]}
}
This way I can loop through, in-turn, all groups and their sub models to generate a form using ERB.
Thank you
When iterating over a collection of records that in turn have collections you'll run into the infamous N+1 query. Essentially for every ProductGroup you would run a query to pull back all of it's ProductItem records. And worse if your working with 2 levels of relations.
To make this work more efficiently you want to make use of includes which ActiveRecord defines as a means of eager loading associations in as few queries as possible.
ProductGroup.includes(:product_items).includes(:product_group_selections => :product_group_selection_attrs)
From there you simply add on any conditions you need and whatever gets loaded for ProductGroup will ensure that all of the associated models also get loaded.
Now you just iterate normally over your associations. Assuming #product_groups is has a collection of ProductGroup
#product_groups.each do |product_group|
# do stuff with product_group
product_group.product_items.each do |product_item|
# do stuff with product_item
end
product_group.product_group_selections do |product_group_selection|
# do stuff with product_group_selection
product_group_selection.product_group_selection_attrs do |product_group_selection_attr|
# do stuff with product_group_selection_attr
end
end
end
The default way that rails sets up associations should fulfill the data structure you asked for, just with actual records instead of a hash of hashes, which you would need to load anyway to create the hash of hashes.
Maybe something like this:
class ProductGroup < ActiveRecord::Base
# I have no idea what to call this method
def merchant_data
{:name => self.name, :merchant_id => self.merchant_id, :items => self.product_items, :selections => self.product_group_selections}
end
end
Inside of your controller you would have something like:
def merchant_search
#product_group = ProductGroup.find_by_merchant_id(params[:merchant_id})
#merchant_data = #product_group.merchant_data
##merchant_data => {:name=>"...", :merchant_id=> 1, :items=>[....], :selections=>[..]}
end
Simply make use of the hash inside your view similar to how you would work with any other instance variable, only this time with a Hash. For instance if you wanted to loop through all the items inside of the returned data structure simply:
#merchant_data[:items].each {|item| ... }
I have categories that are in a tree structure. I am trying to link them together by defining a parent for each one. (I couldn't figure out how to call the property parent so it's just category for now, but it means the parent).
class Category < ActiveRecord::Base
has_one :category # the parent category
end
But the relationship ends up the wrong way around.
The getter function is on the child category (correctly) but the category_id is stored on the parent:
parent = Category.create(:name => "parent")
child = Category.create(:name => "child", :category => parent)
parent.id # 1
child.id # 2
child.category_id # nil
parent.category_id # 2
child.category.name # "parent" (!!)
The parent needs to be able to have multiple children so this isn't going to work.
What you're looking for is self joins. Check this section of the Rails guide out: http://guides.rubyonrails.org/association_basics.html#self-joins
class Category < ActiveRecord::Base
has_many :children, class_name: "Category", foreign_key: "parent_id"
belongs_to :parent, class_name: "Category"
end
Every Category will belong_to a parent, even your parent categories. You can create a single category parent that your highest level categories all belong to, then you can disregard that information in your application.
You can use acts_as_tree gem to achieve this, find below example and link.
https://github.com/amerine/acts_as_tree/tree/master
class Category < ActiveRecord::Base
include ActsAsTree
acts_as_tree order: "name"
end
root = Category.create("name" => "root")
child1 = root.children.create("name" => "child1")
subchild1 = child1.children.create("name" => "subchild1")
root.parent # => nil
child1.parent # => root
root.children # => [child1]
root.children.first.children.first # => subchild1
You should take a look at the ancestry gem: https://github.com/stefankroes/ancestry
It provides all the functionality you need and is able to get all descendants, siblings, parents, etc with a single SQL query by using a variant of materialized paths so it'll have better performance than the self-joins and acts_as_tree answers above.
Category should have many categories, and the foreign key of each category should be the parent_id. So, when you do parent.children it lists all the categories which have parent_id=parent.id.
Have you read on Single Table Inheritance?
Full Article - https://blog.francium.tech/best-practices-for-handling-hierarchical-data-structure-in-ruby-on-rails-b5830c5ea64d
A Simple table
Table Emp
id: Integer
name: String
parent_id: Integer
Associations
app/models/emp.rb
class Emp < ApplicationRecord
has_many :subs, class_name: 'Emp', foreign_key: :parent_id
belongs_to :superior, class_name: 'Emp', foreign_key: :parent_id
end
Scope Definition
class Emp < ApplicationRecord
----
----
scope :roots, -> { where(parent_id: nil) }
end
Fetching data
def tree_data
output = []
Emp.roots.each do |emp|
output << data(emp)
end
output.to_json
end
def data(employee)
subordinates = []
unless employee.subs.blank?
employee.subs.each do |emp|
subordinates << data(emp)
end
end
{name: employee.name, subordinates: subordinates}
end
Eager Loading
def tree_data
output = []
Emp.roots.includes(subs: {subs: {subs: subs}}}.each do |emp|
output << data(emp)
end
output.to_json
end
I'm trying to access my parent model in my child model when validating. I found something about an inverse property on the has_one, but my Rails 2.3.5 doesn't recognize it, so it must have never made it into the release. I'm not sure if it's exactly what I need though.
I want to validate the child conditionally based on parent attributes. My Parent model has already been created. If the child hasn't been created when I update_attributes on the parent, then it doesn't have access to the parent. I'm wondering how I can access this parent. It should be easy, something like parent.build_child sets the parent_id of the child model, why is it not doing it when building the child for accepts_nested_attributes_for?
For Example:
class Parent < AR
has_one :child
accepts_nested_attributes_for :child
end
class Child < AR
belongs_to :parent
validates_presence_of :name, :if => :some_method
def some_method
return self.parent.some_condition # => undefined method `some_condition' for nil:NilClass
end
end
My form is standard:
<% form_for #parent do |f| %>
<% f.fields_for :child do |c| %>
<%= c.name %>
<% end %>
<% end %>
With an update method
def update
#parent = Parent.find(params[:id])
#parent.update_attributes(params[:parent]) # => this is where my child validations take place
end
I had basically the same problem with Rails 3.2. As suggested in the question, adding the inverse_of option to the parent's association fixed it for me.
Applied to your example:
class Parent < AR
has_one :child, inverse_of: :parent
accepts_nested_attributes_for :child
end
class Child < AR
belongs_to :parent, inverse_of: :child
validates_presence_of :name, :if => :some_method
def some_method
return self.parent.some_condition # => undefined method `some_condition' for nil:NilClass
end
end
I had a similar problem: Ruby on Rails - nested attributes: How do I access the parent model from child model
This is how I solved it eventually; by setting parent on callback
class Parent < AR
has_one :child, :before_add => :set_nest
accepts_nested_attributes_for :child
private
def set_nest(child)
child.parent ||= self
end
end
You cannot do this because in-memory child doesn't know the parent its assigned to. It only knows after save. For example.
child = parent.build_child
parent.child # => child
child.parent # => nil
# BUT
child.parent = parent
child.parent # => parent
parent.child # => child
So you can kind of force this behavior by doing reverse association manually. For example
def child_with_inverse_assignment=(child)
child.parent = self
self.child_without_inverse_assignment = child
end
def build_child_with_inverse_assignment(*args)
build_child_without_inverse_assignment(*args)
child.parent = self
child
end
def create_child_with_inverse_assignment(*args)
create_child_without_inverse_assignment(*args)
child.parent = self
child
end
alias_method_chain :"child=", :inverse_assignment
alias_method_chain :build_child, :inverse_assignment
alias_method_chain :create_child, :inverse_assignment
If you really find it necessary.
P.S. The reason it's not doing it now is because it's not too easy. It needs to be explicitly told how to access parent/child in each particular case. A comprehensive approach with identity map would've solved it, but for newer version there's :inverse_of workaround. Some discussions like this one took place on newsgroups.
check these sites, maybe they'll help you...
Rails Nested Attributes Association Validation Failing
accepts_nested_attributes_for child association validation failing
http://ryandaigle.com/articles/2009/2/1/what-s-new-in-edge-rails-nested-attributes
it seems, rails will assign parent_id after child validation succeeds.
(as parent has an id after it's saved)
maybe worth trying this:
child.parent.some_condition
instead of self.parent.some_condition ... who knows...