Mongoid: Is it possible to treat a reference as if it was embedded? - mongoid

class Part
include Mongoid::Document
belongs_to :type
# data
end
class Type
include Mongoid::Document
has_many :parts
field :name
# data
end
Let's say I have a design like the above. I need to have it in such a way that Type exists independently of any Part, both because this is the logical structure and because Types can change and I want to be able to do that centrally. But when I find a Part, I'd like to have it's associated Type embedded in the document returned. I want to do this because in almost any case I need to use the data in a Part, I need the data from its associated Type. When I get the JSON on my client side, it only containes type_id but no Type data. Is there any automatic what to include the data from Type in Part's JSON while retaining the benefits of references?

In the comments, mu answered that one can just override the as_json method of the model.
My implementation is as follows:
class Part
include Mongoid::Document
belongs_to :type
def as_json options = {}
if (options.has_key? :include and not options[:include].has_key? :type) or
not options.has_key? :include
options[:include] = :type
end
super.as_json(options)
end
end

Related

How to ignore an unknown subclass with single collection inheritance in Mongoid?

The default inheritance method for Mongoid is creating a single collection for all subclasses. For instance:
class User
include Mongoid::Document
end
class Admin < User
end
class Guest < User
end
Internally, Mongoid adds a _type field to each document with the class name, which is used to automatically map each instance to the right class.
The problem is, if I have a document in this collection with an unknown _type value, I get an exception:
NameError: uninitialized constant UnknownClass
This can happen if you create a new subclass of User, in the example above, and a migration that creates a new instance of this new subclass. Until you restart your servers, every query to this collection (like User.all.to_a). Is there a safe way to avoid this error?
The only solution I came up is rescuing NameError exception and querying by all known subclasses:
class User
def self.some_query(params)
self.where(params).to_a
rescue NameError => e
Rails.logger.error "Unknown subclass: #{e.message}"
subtypes = self.descendants.map(&:to_s)
self.where(params.merge(:_type.in => subtypes)).to_a
end
end

Only persist document if it has embedded documents with Mongoid?

I have a 2 level nested form (much like this) with the following classes. The problem I have is that when I don't add any intervals (the deepest embedded document) I don't want the second deepest document to be persisted either. In the owner I added a reject statement to check if there's any intervals being passed down, this works.
However, when the schedule originally had intervals but they where destroyed in the form (by passing _destroy: true) the schedule also needs to be destroyed. What would be the best way to do this? I would like to avoid a callback on the schedule that destroys the document after it is persisted.
class Owner
include Mongoid::Document
embeds_many :schedules
attr_accessible :schedules_attributes
accepts_nested_attributes_for :schedules, allow_destroy: true, reject_if: :no_intervals?
def no_intervals?(attributes)
attributes['intervals_attributes'].nil?
end
end
class Schedule
include Mongoid::Document
embeds_many :intervals
embedded_in :owner
attr_accessible :days, :intervals_attributes
accepts_nested_attributes_for :intervals,
allow_destroy: true,
reject_if: :all_blank
end
class Interval
include Mongoid::Document
embedded_in :schedule
end
Update: Maybe this is best done in the form itself? If all intervals is marked with _destroy: true, also mark the schedule with _destroy: true. But Ideally the solution would be client agnostic.
How about adding this to the Owner class:
before_update do
schedules.each |schedule|
schedule.destroy if schedule.intervals.empty?
end
end

How do I limit the number of referenced documents retrieved by Mongoid find()?

I have a REST API route that should be limiting how many referenced "Thing" documents are included when showing a "Model" object.
Main model:
class Model
include Mongoid::Document
has_many :things
end
Referenced Model:
class Thing
include Mongoid::Document
belongs_to :model
end
Model Controller:
def show
# this should retrieve the first 30 "Things" that belong to the model we found
#model = Model.find(params[:id])
# I attempted this with no luck:
# #model = Model.find(params[:id]).includes(:things).limit(30)
end
How can I retrieve the first N referenced records that are part of a model?
here is the solution
#things = Thing.where(:model_id => #model.id).limit(30)

Mongoid - One way references

Is it possible to do a one way reference in mongoid?
I would like to do something like:
class User
include Mongoid::Document
include Mongoid::Timestamps
has_many :blogs, :class_name => "Blog", :inverse_of => :editor
has_one :active_blog, :class_name => "Blog", :inverse_of => :active_users
end
and the blog model:
class Blog
include Mongoid::Document
include Mongoid::Timestamps
belongs_to :editor, :class_name => "User", :inverse_of => :blogs
end
So, basically, I would like the User to store an object id referencing the blog that it is currently editing/posting to. I do not need the blog to know about the active users, only the other way around.
It seems like the canonical way to do this would be to use 'belongs_to' on the User, and 'has_many' on the Blog. This does work, but it isn't ideal because it doesn't really semantically express the relationship between the two models.
I am new to Mongoid, and have not been able to find a better answer. Is there a better way to set up this type of releationship?
Thanks a ton!
If you do not even want to create even the accessor active_user on blog side, you can have:
class User
belongs_to :active_blog, :class_name => "Blog", :inverse_of => nil
end
On the other hand has_many/has_one and belongs_to seems perfectly fine to me. It would not store user_ids in blog and blog doesn't need to know about active user unless you decide it should and start using the accessor from blog side.

Mongoid relational Polymorphic Association

Does anyone know how to do a polymorphic association in Mongoid that is of the relational favor but not the embedding one.
For instance this is my Assignment model:
class Assignment
include Mongoid::Document
include Mongoid::Timestamps
field :user
field :due_at, :type => Time
referenced_in :assignable, :inverse_of => :assignment
end
that can have a polymorphic relationship with multiple models:
class Project
include Mongoid::Document
include Mongoid::Timestamps
field :name, :type => String
references_many :assignments
end
This throws an error saying unknown constant Assignable. When I change the reference to embed, this all works as documented in Mongoid's documentation, but I need it to be reference.
Thanks!
Answering to an ancient post, but someone may find it useful.
Now there's also a polymorphic belongs_to:
class Action
include Mongoid::Document
include Mongoid::Timestamps::Created
field :action, type: Symbol
belongs_to :subject, :polymorphic => true
end
class User
include Mongoid::Document
include Mongoid::Timestamps
field :username, type: String
has_many :actions, :as => :subject
end
class Company
include Mongoid::Document
include Mongoid::Timestamps
field :name, type: String
has_many :actions, :as => :subject
end
From Mongoid Google Group it looks like this is not supported. Here's the newest relevant post I found.
Anyway, this is not to hard to implement manually. Here's my polymorphic link called Subject.
Implementing inverse part of relation might be somewhat more complicated, especially because you will need same code across multiple classes.
class Notification
include Mongoid::Document
include Mongoid::Timestamps
field :type, :type => String
field :subject_type, :type => String
field :subject_id, :type => BSON::ObjectId
referenced_in :sender, :class_name => "User", :inverse_of => :sent_notifications
referenced_in :recipient, :class_name => "User", :inverse_of => :received_notifications
def subject
#subject ||= if subject_type && subject_id
subject_type.constantize.find(subject_id)
end
end
def subject=(subject)
self.subject_type = subject.class.name
self.subject_id = subject.id
end
end
Rails 4+
Here's how you would implement Polymorphic Associations in Mongoid for a Comment model that can belong to both a Post and Event model.
The Comment Model:
class Comment
include Mongoid::Document
belongs_to :commentable, polymorphic: true
# ...
end
Post / Event Models:
class Post
include Mongoid::Document
has_many :comments, as: :commentable
# ...
end
Using Concerns:
In Rails 4+, you can use the Concern pattern and create a new module called commentable in app/models/concerns:
module Commentable
extend ActiveSupport::Concern
included do
has_many :comments, as: :commentable
end
end
and just include this module in your models:
class Post
include Mongoid::Document
include Commentable
# ...
end

Resources