How to find all document by association Mongoid 4 - mongoid

I have a model Tag which potentially belongs to several other models, but at the moment only one model Todo which in turn belongs to User like so:
class User
include Mongoid::Document
field: name, type: String
has_many :todos
end
class Todo
include Mongoid::Document
field: name, type: String
belongs_to :user
end
class Tag
include Mongoid::Document
field: name, type: String
belongs_to :todos
end
How can I query all Tags that belongs to a particular user? I've written the following:
todo_ids = Todo.where(user_id: '86876876787')
and then:
tags = Tag.where('todo_id.in': todo_ids)
But those didn't work. What am I missing?

You're missing two things:
Mongoid isn't ActiveRecord so it won't know what to do with todo_ids in the Tag query.
'todo_id.in' is a field path that is trying to look at the in field inside a todo_id hash, this isn't a use of MongoDB's $in operator.
You can only work with one collection at a time so to fix the first one, you need to pull an array of IDs out of MongoDB:
todo_ids = Todo.where(user_id: '86876876787').pluck(:id)
# -------------------------------------------^^^^^^^^^^^
To fix the second one, use the $in operator:
tags = Tag.where(todo_id: { '$in': todo_ids })
tags = Tag.where(:todo_id.in => todo_ids)
tags = Tag.in(todo_id: todo_ids)
#...

Related

Can't get my strong parameters nested correct in Ruby on Rails

I'm trying to add an array of objects (tasks in this case) as a return type in my controller for the root object (project). One project has many tasks and I'd like to save it all at once, but I keep getting the following error indicating that the return type isn't correct.
ActiveRecord::AssociationTypeMismatch (Task(#47457277775360) expected, got {"name"=>"Some task", "start"=>"2019-12-05T03:38:48.555Z", "end"=>"2019-12-14T03:38:48.555Z"} which is an instance of ActiveSupport::HashWithIndifferentAccess(#47457266882220))
The whitelisted parameters I have are like this
# whitelist params
params.permit(:name, :description, tasks: [:name, :start, :end])
The data being returned for the above example:
{"name"=>"asdf", "description"=>"zxvccxvzzxcvxcvcxvzxcvz", "tasks"=>[{"start"=>"2019-12-05T03:38:48.555Z", "end"=>"2019-12-14T03:38:48.555Z", "name"=>"Some task"}]}
[Edit] - Here are the models we're working with
# app/models/task.rb
class Task < ApplicationRecord
# model association
belongs_to :project
# validation
validates_presence_of :name
end
# app/models/project.rb
class Project < ApplicationRecord
# model association
has_many :tasks, dependent: :destroy
accepts_nested_attributes_for :tasks
# validations
validates_presence_of :name
end
As per rails documentation Nested attributes allow you to save attributes on associated records through the parent. So in your strong params you need to pass attributes like this.
params.permit(:name, :description, task_attributes: [:name, :start, :end])
I suggest you to bind all params under one attribute like this
params.require(:project).permit(:name, :description, task_attributes: [:name, :start, :end])
so you must send params from frontend like
{"project": {"name"=>"asdf", "description"=>"zxvccxvzzxcvxcvcxvzxcvz", "task_attributes"=>[{"start"=>"2019-12-05T03:38:48.555Z", "end"=>"2019-12-14T03:38:48.555Z", "name"=>"Some task"}]}}
You can read the documentation from https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html

Mongoid 3.1 eager loading, json, and field names

Recently updated to Mongoid 3.1 from 3.0.3 and this resulted in some broken code and confusion on my side.
Say you have a pair of classes with a belongs_to/has_many relationship, like so:
class Band
include Mongoid::Document
field :name, type: String
has_many :members, :autosave => true
end
class Member
include Mongoid::Document
field :name, type: String
belongs_to :band
end
Saving all this to the database like so:
b = Band.new
b.name = "Sonny and Cher"
b.members << Member.new(name: "Sonny")
b.members << Member.new(name: "Cher")
b.save
I would in my API, be able to return a 'member' object like so:
m = Member.where(name: "Sonny").first
m.to_json
which yields the following, as expected:
{"_id":"<removed>","band_id":"5151d89f5dd99dd9ec000002","name":"Sonny"}
My client can request the full band object with a subsequent call if it wants to. However, in some cases I DO want to include the referenced item directly. With 3.0.3, I would just do the following:
m = Member.where(name: "Sonny").first
m[:band] = m.band
m.to_json
and this would add a new field with the full band information to it. With 3.1, however (it may have started in earlier versions, but I didn't test), I now get this:
{"_id":"<removed>","band_id":{"_id":"5151dc025dd99d579e000002","name":"Sonny and Cher"},"name":"Sonny"}
So, it looks like the band info has been eager-loaded into the field? Why is it stored under the key ':band_id' and not ':band'? I guess ':band' is protected, but I still don't think the data should be stored under the ':band_id' key. I suspect I am missing something here. Any ideas?
You can specify an :include option for to_json like so:
m.to_json(include: :band)
The JSON will then have a key band with the Band object converted to JSON and band_id will still be present.

Mongoid composite query

I'm kind of a newbie to mongoid, I have a problem querying the db for inner relations:
this is the first model of whom I wish to create a query
class User
include Mongoid::Document
include Mongoid::MultiParameterAttributes
include Geocoder::Model::Mongoid
has_many :hosted_meals, class_name:'Meal', inverse_of: :host
has_and_belongs_to_many :followers, class_name: 'User', inverse_of: :following
.
.
.
end
Second model
class Meal
include Mongoid::Document
include Mongoid::Timestamps
include Geocoder::Model::Mongoid
field :privacy, type: String, default: 'p'
belongs_to :host, class_name: 'User', inverse_of: :hosted_meals
Im trying to create a mongo query that does the following:
return all meals of type 'p'
return all meals are of type 'f' and their host is in the current user's followers, something like this:
Meal.where(:privacy => 'f', :host_id.in => )

nested form, embedded docs, mongoid 2.2.0 problem

I cant seem to find answer for this here or with the google, any help would be awesome.
The Building saves correctly, but the embedded doc PriorityArea doesnt get updated...
I want to eventually have it ajax a new form for new priority areas evenutally, but need it to update first.
Mongoid::Errors::InvalidFind in BuildingsController#update
Calling Document#find with nil is invalid
class Building
include Mongoid::Document
embeds_many :priority_areas
accepts_nested_attributes_for :priority_areas, :allow_destroy => true, :autosave => true
end
class PriorityArea
include Mongoid::Document
embedded_in :building, :inverse_of => :priority_areas
end
#view
= form_for [#customer, #building] do |f|
...
...
= f.fields_for :priority_areas do |pa|
= f.name
...
...
#controller
#building.update_attributes(params[:building])
It correctly yeilds the correct data from the db, but fails to error above on building#update. Any help is greatly appreciated.
update
in the building#update im
puts params[:building][:priority_areas_attributes].to_yaml
which yeilds
--- !map:ActiveSupport::HashWithIndifferentAccess
"0": !map:ActiveSupport::HashWithIndifferentAccess
name: area 51
location: near front door
notes: ""
priority: "1"
id: ""
"1": !map:ActiveSupport::HashWithIndifferentAccess
name: area 52
location: near rear door
notes: ""
priority: "2"
id: ""
im guessing the problem is the null id:""
The problem was the null id
it needed to have an ObjectId to work correctly. stupid error on my part.
I encountered the exact problem. simple_form was automatically passing an id parameter to my controller, but it was blank.
Why was the id for my embedded document blank? I'm guessing it's because I imported the parent document via mongoimport. If I manually generate a parent document via web forms, then the embedded documents have IDs as expected.
Here was my workaround:
class Foo
include Mongoid::Document
embeds_many :bars
accepts_nested_attributes_for :bars
####
# simple_form_for / embedded document workaround
#
# Because simple_form wants to provide the ID for an existing object,
# it will output a blank ID because imported embedded documents
# have an ID of nil.
#
# Intercept it to avoid
# Mongoid::Errors::InvalidFind in FoosController#update
def bars_attributes=(attribs)
attribs.each do |key, value|
index = key.to_i
fixed_attrib = value.delete_if { |k,v| k=="id" and v=="" }
self.bars[index].update_attributes(fixed_attrib)
end
end
end
class Bar
include Mongoid::Document
embedded_in :foo
end

Mongoid criteria for referenced relations

I have these 2 models:
class Track
include Mongoid::Document
field :artist, type: String
field :title, type: String
field :isrc, type: String
has_many :subtitles
end
class Subtitle
include Mongoid::Document
field :lines, type: Array
belongs_to :track
end
How can I check if a track exists that has a certain 'isrc' and has subtitles (no matter how many)?
I've been trying this but it seems to ignore the subtitles criteria:
Track.exists?(conditions: {isrc: my_isrc, :subtitles.exists => true})
It returns true even if the track with that 'isrc' has no subtitles. what to do?
You simply cannot do this way in mongo, since Track & subtitle stored in different documents. Exists command in mongodb only can verify the fields in its own documents, here the relationship is maintained in Subtitle document as track_id not in Track document. So Track doesn't have the track of subtitles.
One easy way to achieve this is to change your relation from belongs_to to embedded. So Track can easily verify the subtitles using $exists.
Another way is
Track.where(:isrc => my_isrc).select {|track| track.subtitles.count > 0}
But disadvantage in this query is multiple round trips made to mongo to verify each tracks subtitle count.
For anyone else struggling with this, I found that mapping the ids to a list, and then using the any_in or all_in functions does the trick.
I needed to get all the messages sent to a user, but messages aren't related directly to my users. Instead, users belong to lists, and lists have many messages. To get messages that "belong to" a user, I did this:
Here's my controller:
#lists = #group.lists.where(deleted: false).order_by([:created_at, :desc])
#messages = Messages.any_in(list: #lists.map(&:id))
#messages = #messages.order_by([:created_at, :desc]).paginate(:page => params[:page], :per_page => 3)
The Messages.any_in(list: #lists.map(&:id)) was the key insight.

Resources