rails mongoid find parent with child - ruby-on-rails

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

Related

Mongoid Query Embedded Document Return Wrong Record

I have two models Core::User and Core::UserMeta :
class Core::User
include Mongoid::Document
embeds_many :core_user_meta, class_name: 'Core::UserMeta'
end
class Core::UserMeta
include Mongoid::Document
field :meta_key, type: String
field :meta_value
embedded_in :core_user, class_name: 'Core::User'
end
I'm using 2 criterias for the query Core::UserMeta on Core::User looks like :
Core::User.where('core_user_meta.meta_key': 'roles', 'core_user_meta.meta_value': 'member').first
I want to get a record has criterias "roles" in meta_key and ["member"] in meta_value but the result always, looks like I'm using or clause instead of and clause
#<Core::User _id: BSON::ObjectId('5b961b07eea19009d397cfaf'), core_user_meta: [{"_id"=>BSON::ObjectId('5b961b07eea19009d397cfb0'), "meta_key"=>"nickname", "meta_value"=>"member"}, {"_id"=>BSON::ObjectId('5b961b07eea19009d397cfbb'), "meta_key"=>"roles", "meta_value"=>["subscriber"]}]>
Your query, as written, is:
Give me a user which has a meta with meta_key of 'roles' and which has a meta with meta_value of 'member'.
This query is satisfied with a user object that has two metas, each of the metas satisfying one of the conditions.
To impose both conditions on the same meta, use $elemMatch:
Core::User.where('core_user_meta'=>{'$elemMatch'=>{'meta_key': 'roles', 'meta_value': 'member'}}).first

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)')

How to create model with an array field which contains another documents as an embedded documents in Mongodb (Mongoid)

I am using Rails 4 with Mongoid for an event based application.
I am trying to create a model where I want to add an array field with embedded documents in that array. This embedded documents will contain user's geo coordinate and timestamp. After every 5 minutes I will be pushing user's latest coordinates to user's (location) array. can someone please help me, How can i create that.
My sample model and desired documents are as below.
class User
include Mongoid::Document
field :name, type: String
field :locations, type: Array
end
Here I want to push
Here is sample document that I am looking for as a result:
{ _id : ObjectId(...),
name : "User_name",
locations : [ {
_id : ObjectID(...),
time : "...." ,
loc : [ 55.5, 42.3 ]
} ,
{
_id : ObjectID(...),
time : "...",
loc : [ -74 , 44.74 ]
}
]
}
I was able to add the value in location array without embedded document through IRB, but as I will be using MongoDB's Geospatial queries later on, so I want to use 2D indexes and rest of the stuff Mongo Documentation mentioned.
Hence I believe it needs to have array of documents which contain the latitude & longitude. which will also save my time to code.
Also can I make the time of the location as documents '_id' ? (It can help me to reduce the query overhead)
I would really appriciate if someone can help me with the structure of model i should write or guide me to the references.
P.S: Let me know if you suggest some extra references/help about storing geospatial data in mongoDB which can be helpful for me.
Hope this will help somebody.
If you want to embed documents you can use embedded_many feature of mongoid, which handles such relations. It allows you to define index on embedded documents as well
http://mongoid.org/en/mongoid/docs/relations.html#embeds_many
Mongoid points out, that 2D indexes should be applied to arrays:
http://mongoid.org/en/mongoid/docs/indexing.html
In your case models may look like this:
class User
include Mongoid::Document
field :name, type: String
embeds_many :locations
index({ "locations.loc" => "2d" })
accepts_nested_attributes_for :locations # see http://mongoid.org/en/mongoid/docs/nested_attributes.html#common
end
class Location
include Mongoid::Document
field :time, type: DateTime # see http://mongoid.org/en/mongoid/docs/documents.html#fields
field :loc, type: Array
embedded_in :user
end
But beware of using update and nested attributes - it allows you only update attributes, but not delete or reject them. It's preferrable to use (association)_attributes= methods instead:
#user = User.new({ name: 'John Doe' })
#user.locations_attributes = {
"0" => {
_id : ObjectID(...),
time : "...." ,
loc : [ 55.5, 42.3 ]
} ,
"1" => {
_id : ObjectID(...),
time : "...",
loc : [ -74 , 44.74 ]
}
}
#user.save!

ActiveRecord group by on a join

Really been struggling trying to get a group by to work when I have to join to another table. I can get the group by to work when I don't join, but when I want to group by a column on the other table I start having problems.
Tables:
Book
id, category_id
Category
id, name
ActiveRecord schema:
class Category < ActiveRecord::Base
has_many :books
end
class Book < ActiveRecord::Base
belongs_to :category
end
I am trying to get a group by on a count of categories. I.E. I want to know how many books are in each category.
I have tried numerous things, here is the latest,
books = Book.joins(:category).where(:select => 'count(books.id), Category.name', :group => 'Category.name')
I am looking to get something back like
[{:name => fiction, :count => 12}, {:name => non-fiction, :count => 4}]
Any ideas?
Thanks in advance!
How about this:
Category.joins(:books).group("categories.id").count
It should return an array of key/value pairs, where the key represents the category id, and the value represents the count of books associated with that category.
If you're just after the count of books in each category, the association methods you get from the has_many association may be enough (check out the Association Basics guide). You can get the number of books that belong to a particular category using
#category.books.size
If you wanted to build the array you described, you could build it yourself with something like:
array = Categories.all.map { |cat| { name: cat.name, count: cat.books.size } }
As an extra point, if you're likely to be looking up the number of books in a category frequently, you may also want to consider using a counter cache so getting the count of books in a category doesn't require an additional trip to the database. To do that, you'd need to make the following change in your books model:
# books.rb
belongs_to :category, counter_cache: true
And create a migration to add and initialize the column to be used by the counter cache:
class AddBooksCountToCategories < ActiveRecord::Migration
def change
add_column :categories, :books_count, :integer, default: 0, null: false
Category.all.each do |cat|
Category.reset_counters(cat.id, :books)
end
end
end
EDIT: After some experimentation, the following should give you close to what you want:
counts = Category.joins(:books).count(group: 'categories.name')
That will return a hash with the category name as keys and the counts as values. You could use .map { |k, v| { name: k, count: v } } to then get it to exactly the format you specified in your question.
I would keep an eye on something like that though -- once you have a large enough number of books, the join could slow things down somewhat. Using counter_cache will always be the most performant, and for a large enough number of books eager loading with two separate queries may also give you better performance (which was the reason eager loading using includes changed from using a joins to multiple queries in Rails 2.1).

Mongoid and querying for embedded locations?

I have a model along the lines of:
class City
include Mongoid::Document
field :name
embeds_many :stores
index [["stores.location", Mongoid::GEO2D]]
end
class Store
include Mongoid::Document
field :name
field :location, :type => Array
embedded_in :cities, :inverse_of => :stores
end
Then I tried calling something like City.stores.near(#location).
I want to query the City collection to return all cities that have at least 1 Store in a nearby location. How should I set up the index? What would be the fastest call?
I read the Mongoid documentation with using index [[:location, Mongo::GEO2D]] but I am not sure how this applies to an embedded document, or how to only fetch the City and not all the Stop documents.
Mike,
The feature you are requesting is called multi-location documents. It is not supported in the current stable release 1.8.2. This is available only from version 1.9.1.
And Querying is straightforward when use mongoid, its like this
City.near("stores.location" => #location)
And be careful when using near queries in multi-location documents, because the same document may be returned multiple times, since $near queries return ordered results by distance. You can read more about this here.
Use $within query instead to get the correct results
Same query written using $within and $centerSphere
EARTH_RADIUS = 6371
distance = 5
City.where("stores.location" => {"$within" => {"$centerSphere" => [#location, (distance.fdiv EARTH_RADIUS)]}})

Resources