Mongoid has_and_belongs_to_many inverse_of: :nil unexpected data - ruby-on-rails

I have a collection of Tasks that relate back to themselfs
class Task
include Mongoid::Document
has_and_belongs_to_many :related_tasks , class_name: 'Task', inverse_of: :nil
In the monogo data I am looking for
Parent task
{
"_id" : ObjectId(""),
"related_task_ids" : [
ObjectId(""),
ObjectId("")
],
}
And on the child task (nothing)
The parent tasks looks correct.
But on the child task I get
{
"_id" : ObjectId(""),
"nil_ids" : [
ObjectId("")
],
"related_task_ids" : [ ],
}
Where nil_ids is the parent id.
Why is it storing the nil_id's? and is there any way to stop this?
I want a 1..n relationship i.e a task has many children.
It's not a n..n relationship i.e. Children tasks don't have many parent tasks.

The reason you are seeing a nil_ids key on the child side of the association is that you have specified the :nil Ruby symbol rather than nil. So Mongoid is just interpreting this like any other symbol and creating a nils collection on the Task as the inverse of the related_tasks collection.
Try:
has_and_belongs_to_many :related_tasks , class_name: 'Task', inverse_of: nil
This should leave the related_task_ids in the parent task but not store the nil_ids on the children.

Related

Self join in rails is returning the parent record for each expected child row

I have an Audio model in my rails app, which is using an id and parent_audio_id field to build a hierarchy. A single Audio can be the child of another Audio, or have many children Audios (which I am calling derivatives).
I've referenced this edge guide on self joins to construct the relationships, and this SO post to help troubleshoot
Using the active record query below, I get a data set with the parent audio data duplicated N times, where N equals the number of child records for that parent.
How can I return the child records for a given parent audio record given a self-join relationship?
**Audio.rb**
class Audio < ApplicationRecord
has_many :derivatives, :class_name => "Audio", :foreign_key => "parent_audio_id"
belongs_to :parent_audio, :class_name => "Audio", optional: true
end
**Audio Migration**
class CreateAudios < ActiveRecord::Migration[6.0]
def change
create_table :audios do |t|
t.references :parent_audio, foreign_key: {to_table: :audios}
end
end
end
**Audios Controller**
def audio_details
data = []
derivatives = Audio.joins(:derivatives)
data.push(derivatives)
render json: data
end
I've also tried derivatives = Derivative.joins(:audios) but get the following error:
uninitialized constant Api::V1::AudiosController::Derivative
.joins simply adds a INNER JOIN to the query. It does not select any columns on the joined table and does not return the association. It returns a new scope based on whatever it was called on - so in this case the scope will select audios that have at least one match in the joined table (at least one descendant).
.joins is primarily used for filtering records based on the joined table:
Patient.joins(:appointments)
.where(scheduled_at: Time.current..)
Or selecting aggregates:
Hotel.joins(:reviews)
.select('hotels.*', 'AVG(reviews.rating) AS average_rating')
.group(:id)
If you want to return the descendants of a specific Audio you need to call the method generated by the association macro on an instance of the class:
#audio = Audio.find(params[:id])
#descendents = #audio.descendents
ActiveRecord will automatically create a separate query to load the decedents.
You can use .eager_load to force a single query instead of two:
#audio = Audio.eager_load(:descendents)
.find(params[:id])
#descendents = #audio.descendents
See Rails Guides: Active Record Query Interface.

Neo4j Cypher: How to fetch nodes with conditional query on reation

I have two Neo4j Nodes and one relation:
class StayPal
include Neo4j::ActiveNode
has_many :in, :places, origin: :owner
has_many :in, :shared_places, rel_class: 'HouseMate'
end
class Place
include Neo4j::ActiveNode
has_one :out, :owner, type: :owner_of, model_class: 'StayPal'
has_many :out, :house_mates, rel_class: 'HouseMate'
end
class HouseMate
include Neo4j::ActiveRel
include Enumable
creates_unique
from_class 'Place'
to_class 'StayPal'
type 'shared_with'
property :status, default: 0
enum_attr status: [:pending, :approved, :declined, :cancelled]
end
Objective: My objective is get places & shared_places of staypal together but the shared places included if they are status == approved
Query:
Neo4j::Session.current.query
.match(n: { StayPal: { user_id: 1 } })
.match('n<-[rel1:`owner_of`]-(result_places:`Place`)')
.match('n<-[rel2:`shared_with`]-(result_places1:`Place`)')
.pluck(:result_places1, :result_places)
With this I am getting the places and shared places of staypal
But I want shared places where status = 1
Modified Query
Neo4j::Session.current.query
.match(n: { StayPal: { user_id: 1 } })
.match('n<-[rel1:`owner_of`]-(result_places:`Place`)')
.match('n<-[rel2:`shared_with`]-result_places1:`Place`)')
.where('result_places1.status = 1')
.pluck(:result_places1, :result_places)
But with this I am getting no records
Some other helping queries
Neo4j::Session.current.query
.match(n: { StayPal: { user_id: 1 } })
.match('n<-[rel1:`owner_of`]-(result_places:`Place`)')
.match('n<-[rel2:`shared_with`]-result_places1:`Place`)')
.where('result_places1.status = 1')
.pluck(:result_places1)
Output:
[CypherRelationship 1239]
You are using the neo4j-core Query API, which you can do and which should allow you to get ActiveNode and ActiveRel objects, but there is a higher level API which is easier:
StayPal.where(user_id: 1).places(:place1).shared_places(:places2).pluck(:place1, place2)
To do this I assumed that you add this association to Place:
has_many :both, :shared_places, type: :shared_with
Note that I used the singular form for variables. It's important to remember that when you are matching that it does one match at a time. Singular variables help us to keep that in context.
Aside from that, though, I think you have a deeper issue that your HouseMate relationship is going from a Place to a StayPal. What is your model? If you want to record two people staying in the same house, you might want to have a new node with a label like HouseMateGroup and that node could point to the Place as well as two (or more) StayPals.
EDIT:
I think I'm understanding your use case more. I would probably make the model (:StayPal)-[:LIVES_IN]->(:Place)<-[:LIVES_IN]-(:StayPal)
Any given step in that doesn't map to the idea of a "house mate", but you can easily get the housemates by following relationships/associations. So if you wanted to get housemates you might do:
pal = StayPal.find_by(user_id: 1)
pal.places.people
That would get you all of the people that are in the places which user_id: 1 is in.
If you wanted to find all places which have associated people:
Place.as(:place1).people.places(:place2).pluck(:place1, :place2)
You could even count the number of people that exist in that relationship between places:
Place.as(:place1).people(:person).places(:place2).pluck(:place1, :place2, 'count(person)')

Get all tree from a model object

I have
#total = Purchase::Total.find(1);
Total model have:
has_many :items
belongs_to :member
belongs_to :company
..................
Also companies model has
has_many :addresses
has_one :subscription
..................
and a lot more
How can I get a tree from the #total object containing all the has_one, belongs_to dependencies?
I.E.
<Purchase::Total id: 3, member_id: 4, created_at: \"2015-11-25 14:47:46\", updated_at: \"2015-11-25 14:47:46\", affiliate_company_id: nil, is_paid: false, currency: 1, company_id: 37020, ser_id: 2>
<Company id: 37020, name: \"Andrew\", parent_id: 37019, member_company_id: 37019, payment_company_id: 37019, widget_id: 3003359>
And so ..... (I did the example with: #total.inspect and #total.company.inspect), and I need something like inspect to return automatically all the objects.
Using reflect_on_all_associations
Take a Queue and a Hash and add Total (model name) to it.
Pop a model name, get all associated models and add them queue. Also, using the tablize name of current model, create a new entry in hash and add the tablized names of associated models.
If queue is not empty, go to 2.
At the end, your hash should look like:
{ total: { company: [ :subscription, :addresses ] }, items: { associated: { another: :another_one } } }
Then you can use this in your query:
Total.where().join(hash[:total])
It will fetch all the associated data as well. Then you can simply loop through the attributes. If attribute type is ActiveRecord (or similar), then its an associated model data.

rails mongoid find parent with child

Fast Example,
class Band
include Mongoid::Document
embeds_many :albums
end
class Album
include Mongoid::Document
field :name, type: String
embedded_in :band
end
and the document will look like this,
{
"_id" : ObjectId("4d3ed089fb60ab534684b7e9"),
"albums" : [
{
"_id" : ObjectId("4d3ed089fb60ab534684b7e0"),
"name" : "Violator",
}
]
}
lets say, i want to make a method to find the Band with albums name
if this was ActiveRecord, it is simple
Album.find_by(name: "Violator").band
but what about like this situation?
Do i have to iterate the whole collection and find it like this?
Band.select {|band| band.albums.select{|album| album.name == "Violator"}}
Sounds crazy...
Or do i have to do the data modeling with Referenced relations not Embedded relations?
Embedded documents are best for items which don't need to query independently. If you need something to query independently, then consider using references. In your case, you can better find bands first by using specific album name and then process these bands
#bands = Band.where("albums.name" => "Violator")
#albums = #bands.collect{|band| band.albums.where(name: 'Violator') }.flatten
Here are more details on mongoid relations http://mongoid.org/en/mongoid/docs/relations.html

Adjacency data structure to nested hash

I have the following model in rails:
class Model < ActiveRecord::Base
# id — integer
# name — string
# model_id — integer
belongs_to :parent, class_name: 'Model', foreign_key: 'model_id'
has_many :children, class_name: 'Model', foreign_key: 'model_id'
end
I am using adjacency structure, which can have infinite depth. I am on a Postgres database using recursive selects.
What will be the most sane way to get a nested hash of objects? I tried to select instances of Model and sort them, yet could not bring this to any usable result.
Lets say I have four Model instances saved in my database: Model_1, Model_2, Model_3 and Model_4. Model_3 is a child of Model_2 and Model_4 is a child of Model_3.
Here is an output I am trying to achieve (a nested hash of Model instances):
{
#<Model_1...> => {},
#<Model_2...> => {
#<Model_3...> => {
#<Model_4...> => {}
}
}
}
Any ideas?
Update: Tree is already recovered — either as a CollectionProxy, Relation or any other array-ish data structure. I wan't to sort that tree into the hash of nested hashes.
I would name it parent_id field.
belongs_to :parent, class_name: "Model"
has_many :children, class_name: "Model", foreign_key: "parent_id"
When you have the hash, you would use sort or sort_by:
http://www.ruby-doc.org/core-2.1.0/Enumerable.html#method-i-sort_by
def sort(hash)
hash.sort { |m1, m2| m1.id <=> m2.id }
sort(hash.children)
end
First, define the ff method inside your Model class:
def to_hash
if children.empty?
{self => {}}
else
{self => children.inject({}) {|hash, model| hash.merge(model.to_hash) } }
end
end
Then do the ff to get the output you want:
top_level_models = #code that queries top-level models while eager-loading all nested children
hash_of_nested_models = top_level_models.inject({}) {|hash, ancestor| hash.merge(ancestor.to_hash) }
The hash argument that you pass to includes should cover the depth of your nesting. The argument passed to includes above will be the nesting for children with a depth of 3 descendants. For as long as you includes all the nested children in your where query, generating the hash will not do any more db queries.
Hope that helps!
Getting AR to do this without N+1 queries would be difficult. Will have to write something that works with the data in memory.
You would have to write a custom function which looks something like:
def to_hash
root_hash = {}
# Load all the model into memory and into a hash to allow faster access when we have the id
models = Hash[Model.all.collect {|m| [m.id, m]}]
# The resultant hash for each child
models_with_hash = Hash[map.values.collect {|m| [m.id, {}]} ]
# Stitch them together
models.each do |id, m|
if m.model_id.nil?
root_hash.merge! m => models_with_hash[m.id]
else
# should name model_id to parent_id
models_with_hash[m.model_id].merge! m => models_with_hash[m.id]
end
end
root_hash
end

Resources