Rails scope checking for no associations - ruby-on-rails

I have a Category model where a category may have some subcategories (category.categories). I want a scope that gives me all Categorys that have no subcategories.
In other words, I can write
without_subcategories = Category.select{|category| category.categories.none?}
but I would like to write this as a scope. How do I do this?
In case it's not clear, this is my model:
class Category < ActiveRecord::Base
belongs_to :parent, class_name: 'Category'
has_many :categories, foreign_key: :parent_id, class_name: 'Category'
scope :without_subcategories, lambda { WHAT GOES IN HERE? }
end

the best practice is minimize db queries by implementing a counter cache.
In rails this is super simple by adding an option :counter_cache => true to the belongs_to association. This assumes you create a 'categories_count' integer column in your categories db table. Given this, your scope is trivial.
class Category < ActiveRecord::Base
belongs_to :parent, class_name: 'Category', :counter_cache => true
has_many :categories, foreign_key: :parent_id, class_name: 'Category'
scope :without_subcategories, where(categories_count: 0)
end
hope this helped.

Related

self join how to reference the association from parent

I have Category class shown below
class Category < ActiveRecord::Base
has_many :subcategories, class_name: "Category", foreign_key: "parent_category_id"
belongs_to :parent_category, class_name: "Category"
belongs_to :main_category
end
and I wonder if I can define main_category association the rails way that I can
reference the #main_category on subcategories but leaving the main_category_id empty (as the reference on subcategory#main_category_id will duplicate the data which is in parent_category#main_category_id or it is just premature optimization?😅 ).
category = Category.new main_category: main_category
subcategory = Category.new parent_category: category
assert_equal category.main_category, subcategory.main_category
you can create a proxy that subcategories will delegate main_category to parent, the drawback that there're 3 queries to get main_category
class Category < ActiveRecord::Base
has_many :subcategories, class_name: "Category", foreign_key: "parent_category_id"
belongs_to :parent_category, class_name: "Category"
belongs_to :main_category, class_name: "Category"
# with subcategories, there're 3 queries:
# super return nil -> find parent_category -> find main_category of the parent
def main_category
super || parent_category&.main_category
end
end
You can use indirect assocations to setup "short-cuts" through the tree.
class Category < ActiveRecord::Base
# Going up...
has_many :subcategories,
class_name: "Category",
foreign_key: "parent_category_id"
has_many :grand_child_categories,
class_name: "Category",
through: :subcategories
has_many :great_grand_child_categories,
class_name: "Category",
through: :grand_child_categories
# Going down...
belongs_to :parent_category,
class_name: "Category"
has_one :grand_parent_category,
through: :parent_category,
class_name: "Category"
has_one :great_grand_parent_category,
through: :grand_parent_category,
class_name: "Category"
end
However if you have a heirarchy of unlimited depth ActiveRecord::Assocations can't really solve the problem of finding the node at the bottom of the tree. That requires more advanced SQL like a recursive common table expression (CTE).
While ActiveRecord does has the basic tools for creating self joining assocations most of the more andvaced stuff is out of scope and can be handled with gems like Ancestry.

Act as list with self-referential association

I have a self-referential association for categories table:
class Category < ApplicationRecord
has_many :subcategories,
class_name: 'Category',
foreign_key: 'parent_id',
dependent: :destroy,
inverse_of: :parent
belongs_to :parent,
class_name: 'Category',
optional: true,
inverse_of: :subcategories
end
I wanted to add act_as_list gem to manage position of elements for main categories and subcategories in scope of main category. As I read documentation it seems that there is no possibility for such scenario. Is there any workaround?

Has_many through with multiple relationships between the entities

I have these model definitions:
class Property < ActiveRecord::Base
belongs_to :user
has_many :users_favourites_properties, class_name: 'UsersFavouritesProperties', dependent: :destroy, :foreign_key => :property_id
has_many :favourites, :class_name => "User", through: :users_favourites_properties
end
class User < ActiveRecord::Base
has_many :properties
has_many :users_favourites_properties, class_name: 'UsersFavouritesProperties', dependent: :destroy, :foreign_key => :user_id
has_many :favourites, class_name: "Property", through: :users_favourites_properties
end
class UsersFavouritesProperties < ActiveRecord::Base
self.table_name = "favourites"
belongs_to :favourites, class_name: 'User', :foreign_key => :user_id, :primary_key => :id
belongs_to :favourites, class_name: 'Property', :foreign_key => :property_id, :primary_key => :id, counter_cache: :users_count
end
When I execute:
current_user.favourites << property
The UsersFavouritesProperties object created has the same user_id and property_id because the user_id is beign setted with the property_id.
Do you know what is happening?
You seem to misunderstand what the association name is for.
Each association method call (like has_many) adds certain methods to a model that allow for easy lookup for associated objects.
The name of association means (in scope of a model) "what is it for me". A symbol you specify first is an association name, and it is used (among other things) as a name of a method that you will use to access that association.
By defining an association named favourites once, you've defined the methods for UsersFavouritesProperties. The second time you define an association with the same name, some of the first one is redefined and crazy things can happen.
So, following that "what is it for me" principle, you can figure that a User associated with a certain UsersFavouritesProperty is UsersFavouritesProperty's user, and the resulting association, that you have:
belongs_to :favourites, class_name: 'User', :foreign_key => :user_id, :primary_key => :id
should be more like simple:
belongs_to :user
The rest is not even needed, as it follows the Rails' convention: class name and foreign key can be derived from association name in standard ways (capitalize for class name, add _id for foreign key), primary key is default.
I strongly recommend you read the Rails Style Guide and let ActiveRecord guess the most you can provide.
These lines
belongs_to :favourites, ...
belongs_to :favourites, ...
Should be
belongs_to :user, ...
belongs_to :property, ...
You can't use two keys for belongs_to

Get products of children categories

I have a model, called Groups, it's self-referential, there are groups in it - parents and children together, linked by parent_id. So I can get children of a group and products connected to one of groups, but how can I get all products of parent group through its children?
Here is my model:
class Group < ActiveRecord::Base
belongs_to :parent, class_name: 'Group', foreign_key: :parent_id
has_many :children, class_name: 'Group', foreign_key: :parent_id
has_many :products
validates :id, uniqueness: true
self.primary_key = :id
end
It would be great if, for example, I could call group.products and get all products, that are direct descendants of that group and all products of children groups.
It would be even better if I could paginate them with something like Kaminari(or another paginator).
P.S. I tried to insert someting like has_many :products, through: :children, but all I get is Stack level is too deep...
P.P.S. I can create method that will collect all products in Hash, but I won't be able to sort list of products by title, price etc.
Try this
class Group < ActiveRecord::Base
belongs_to :parent, class_name: 'Group', foreign_key: :parent_id
has_many :children, class_name: 'Group', foreign_key: :parent_id
has_many :products
has_many :child_products, :through => :children, :source => :products
validates :id, uniqueness: true
self.primary_key = :id
end

how can I do self-reference with ruby on rails?

I want to self-referentiate a model in a RoR app but, I don't know exactly how. I want to save a linked list where the next node has the id of the previous one. how can I do this rails way? It is a one-to-one relation.
The easiest way:
class MyModel < ActiveRecord::Base
belongs_to :parent, :class_name => 'MyModel'
has_many :children, :class_name => 'MyModel', :foreign_key => 'parent_id'
end
rails 5
add column xxx_id in users table:
in migration file:
add_reference :users, :xxx, index: true
and add code in User model
has_many :users, class_name: 'User', foreign_key: 'xxx_id'
belongs_to :manager, class_name: 'User', foreign_key: 'xxx_id'
If you don't have a manager for every user, you need to add optional: true.
'foreign_key' is not necessary. By default this is guessed to be the name of this class in lower-case and “_id” suffixed.
if foreign_key is user_id, user don't have manager necessary.
the result is:
has_many :users, class_name: 'User'
belongs_to :manager, class_name: 'User', optional: true
They are called self-joins
I've spent some time trying to make it work using Rails 3.2.14
The documentation's suggestion for self-joining associations hasn't worked for belongs_to associations. Adding a foreign key fixed the issue.
Class User < ActiveRecord::Base
has_many :invitees, class_name: 'User', foreign_key: :invited_by
belongs_to :host, class_name: 'User', foreign_key: :invited_by
end

Resources