How to eager load an association in the subclass - ruby-on-rails

I have the following code :
class Parent < ActiveRecord::Base
# attributes name:string, root_id:integer , type:string , .....
has_many :childrens, class_name: 'Children, foreign_key: :root_id, primary_key: :id
end
and another class Children with
class Children < Parent
belongs_to :root, class_name: 'Parent', foreign_key: :root_id, primary_key: :id
end
Parent objects can be appear multiple times in the same table (for some reason...), but i wouldn't duplicate rows and copy all information each time, so i just make this Children subclass which inherit from Parent, and root_id is the reference to the parent, example :
object 1 : { id: 1 , name: "parent object", root_id: nil, type: nil, .... }
object 2 : {id: 2, name: "child", root_id: 1, type: 'Children', ....}
object 3 : {id: 3, name: "child", root_id: 1, type: 'Children', ... }
then i do something like this :
Parent.where("id IN (2, 3)")
here i fetch just 'Children' objects, so i want to eager load their parent, to have access to the name, and also other attributes ...
i tried with this
Parent.where("id IN (2, 3)").includes(:root)
but i get this error :
Association named 'root' was not found on Parent; perhaps you misspelled it?
it seem that :root association from the subclass is not accessible in the Parent class, there is a way to improve performance ?

Simple. The includes method takes on the associations that you have defined for a class. In your case, it takes only :childrens(it should actually be children without 's').
So to make available the :root to the includes method, cut and paste the belongs_to association from the Children(it should be actually be Child) class to the Parent class as below.
class Parent < ActiveRecord::Base
has_many :childrens, class_name: 'Children, foreign_key: :root_id, primary_key: :id
belongs_to :root, class_name: 'Parent', foreign_key: :root_id, primary_key: :id
end
Now, the following should work assuming, you dont have other problems.
Parent.where("id IN (2, 3)").includes(:root)
Tip:
1.9.3p385 :007 > "Child".pluralize
=> "Children"
1.9.3p385 :008 > "Children".singularize
=> "Child"

Related

How to create association between unsaved records

I created 3 models as below, and used cocoon nested form to create associations between them.
class Unit < ApplicationRecord
has_many :mapping_categories, -> { distinct }, dependent: :destroy, inverse_of: :unit
accepts_nested_attributes_for :mapping_categories,
allow_destroy: true,
reject_if: :all_blank
end
class MappingCategory < ApplicationRecord
belongs_to :unit
has_many :mapping_items, -> { distinct }, dependent: :destroy, inverse_of: :mapping_category
accepts_nested_attributes_for :mapping_items,
allow_destroy: true
end
class MappingItem < ApplicationRecord
belongs_to :mapping_category
has_many :mapping_item_links
has_many :linked_mapping_items, through: :mapping_item_links, dependent: :destroy
end
Each mapping_item can have many other mapping_items through a joint table. In every mapping_item section in Unit form, this association is displayed as a select input.
When creating or updating Unit, there are many mapping_categories tabs in the Unit form, and there are many mapping_items sections in each mapping_category section.
For example, I have Mapping Category A and Mapping Category B. I want to add Mapping Item 1 to Mapping Category A and Mapping Item 2 to Mapping Category B. The question is: How to create the association between Mapping Item 1 and Mapping Item 2, as these two items are not saved yet?
Thanks in advance.
YOU CAN DO IT
You have to write right code
user = User.new(name: 'Jons', email: 'jons#qq.ww')
bank_account = BankAccount.new(number: 'JJ123456', user: user)
bank_account.save
in this way will be saved both raws and user and bank_account
in your case:
unit = Unit.new(mapping_categories: [mapping_category])
mapping_category = MappingCategory.new(mapping_items: [mapping_item])
mapping_item = MappingItem.new
unit.save
and if you wanna use nested_attributes, you just have to build hash with attributes
params = { mapping_categories: [mapping_items: [{.....}]}] }
Unit.create(params)
but you have to figure out with right nesting
From my understanding of your question... You can't. These items don't yet have ids and there for can't be associated with another model.
> contact = Contact.new(full_name: "Steve", email:"example#asdf.com")
=> #<Contact id: nil, full_name: "Steve", email: "example#asdf.com", created_at: nil, updated_at: nil>
> invoice = Invoice.new(contact_id: contact.id, invoice_type: "Something")
=> #<Invoice id: nil, contact_id: nil, invoice_type: "Something" created_at: nil, updated_at: nil>
> invoice.save
=> false

Ruby: Parent-child association only returns "Object"

I'm trying the list my parent and children of my Model "Performance Indicators", in my model I have this for parent/children:
belongs_to :parent, class_name: "PerformanceIndicator", :foreign_key => 'parent_id2'
has_many :children, class_name: "PerformanceIndicator", :foreign_key => 'parent_id2'
and in my performance indicators table I have the column parent_id2: t.integer "parent_id2"
But if I try to call the parent like this for example: PerformanceIndicator.parent, its returning "Object", and if I try for the children it says #<NoMethodError: undefined method children' for
What am I doing wrong?
I want to list the performance indicators something like this:
Parent1
Children1
Children2
Parent2
Children1
EDIT:
this is my performance indicators model:
class PerformanceIndicator < ActiveRecord::Base
has_many :improvement_actions
has_ancestry
belongs_to :parent, class_name: "PerformanceIndicator"#, :foreign_key => :parent_id2
has_many :children, class_name: "PerformanceIndicator", :foreign_key => 'parent_id2'
scope :parents, -> { where('parent_id2=''') }
def self.search(search)
where("name iLIKE ? OR description iLIKE ?", "%#{search}%", "%#{search}%")
# where("description LIKE ?", "%#{search}%")
end
end
I think you should consider changing your terminology. A child-parent relationship generally infers the child class inheriting from the parent class.
But if I try to call the parent like this for example: PerformanceIndicator.parent, its returning "Object", and if I try for the children it says #
This is happening because you are actually calling the class method parent on your class PerformanceIndicator, which inherits from Object. thus, the parent class of PerformanceIndicator is Object. There is no class method children for your class because it doesn't exist. What you have defined in your class is an instance method.
You must have an instance of your class to call these methods. You can instantiate a new instance of this object like so:
pi = PerformanceIndicator.new
pi.parent
pi.children
The associations of a model are always per instance.
Therefore, you can't access PerformanceIndicator.parent as it is a call to the model itself, but not an instance.
to create proper associations, you have to create different instances, like:
indi1 = PerformanceIndicator.create
indi2 = PerformanceIndicator.create(parent: indi1)
Now you can access them like
indi1.children # => ActiveRecord::Relation
indi1.children.to_a # => [<PerformanceIndicator: id: 2, ....>]
indi2.parent # => <PerformanceIndicator: id: 1> alias indi1
If you already have a parent_id, then you should take away parent_id2 and change the relations as follows:
belongs_to :parent, class_name: "PerformanceIndicator"
has_many :children, class_name: "PerformanceIndicator", :foreign_key => 'parent_id'
Afterwards, you can create a parent scope:
scope :parents, -> { where(parent_id: nil) }
And you can iterate through them:
PerformanceIndicator.parents.each do |parent|
# do stuff with parent
parent.children.each do |child|
# do stuff with child
end
end

ActiveRecord Eager Load model that isn't belongs_to

I'm running into an issue with n+1 queries and I want to eager load a relationship, except I'm having trouble defining the relationship. It's complicated, haha, hear me out.
I have two models.
class Pokemon < ActiveRecord::Base
belongs_to :pokemon_detail, primary_key: "level", foreign_key: "level"
end
class PokemonDetail < ActiveRecord::Base
has_one :pokemons, primary_ley: "level", foreign_key: "level"
end
Let's say, I have a the following record:
<Pokemon id: 1, name: "squirtle", level: 1>
Which would obviously correspond with the following PokemonDetail
<PokemonDetail id: 1, name: "squirtle", level: 1, health: 150>
And that can be easily eager loaded like Pokemon.all.includes(:pokemon_detail), however, I want to eager load the information about one level higher.
<PokemonDetail id: 2, name: "squirtle", level: 2, health: 300>
I currently find the information about one level higher with the following method within the Pokemon model.
def next_level_info
PokemonDetail.where(level: self.level + 1)
end
But this isn't eager loaded. Any ideas?
Refactor schema first, to make it more sense:
pokemons { current_level_value, name }
pokemon_levels {value, pokemon_id (foreign key), health }
Redefine models like this:
class PokemonLevel < ActiveRecord::Base
belongs_to :pokemon
end
class Pokemon < ActiveRecord::Base
has_many :pokemon_levels
has_one :current_pokemon_level, -> { joins(:pokemon).where('pokemons.current_level_value = pokemon_levels.value') },
foreign_key: :pokemon_id, class_name: 'PokemonLevel'
has_one :next_pokemon_level, -> { joins(:pokemon).where('pokemons.current_level_value + 1 = pokemon_levels.value') },
foreign_key: :pokemon_id, class_name: 'PokemonLevel'
end
Simply use it eg:
Pokemon.includes(:current_pokemon_level, :next_pokemon_level).find(123)
I use PokemonLevel instead of PokemonDetail, because it is clearer to me

Creating a model that has a tree structure

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

Rails single table inheritance and subclasses name problem

I have 3 classes:
class User < ActiveRecord::Base
has_many :events, :foreign_key => 'owner_id'
end
class Event < ActiveRecord::Base
belongs_to :owner, :class_name => 'User', :foreign_key => 'owner_id'
end
class Event::User < Event
end
Class Event have 'type' field, and work as STI.
Now i create Event:
Event::User.create(:owner => User.first)
=> #<Event::User id: 8, owner_id: 1, title: nil, type: "Event::User", created_at: "2010-07-05 00:07:32", updated_at: "2010-07-05 00:07:32">
And now i want to receive the owner of creating event, but receive:
Event.last.owner
=> #<Event::User id: 1, owner_id: 1, title: nil, type: "Event::User", created_at: "2010-07-04 23:28:31", updated_at: "2010-07-04 23:28:31">
SQL log has given me the following:
Event::User Load (0.4ms) SELECT * FROM "events" WHERE ("events"."id" = 1) AND ( ("events"."type" = 'Event::User' ) )
Similar that rails search at first for an internal class with such name, and then already external. Certainly I can change a name of an internal class, but it no good for project, maybe have other way?
I don't quite get what you want to do here. If you want to use STI, then the subclasses of 'Event' model, should be event types, like "Meeting", "Party" or something like that.
class Event::User < Event defines Event::User as an event type. I don't think that is what you want to do.
If what you're trying to do is a one-to-many relation (user can create many events, event has one owner only) then a simpler way to do this would be to add a field in the event model "user_id", and then add the following to the event model:
class Event
belongs_to :owner, :foreign_key => 'user_id'
end
This allows you to do things like: User.first.events (array of events for the first user), and Event.last.owner (user object representing the owner of the last event)

Resources