how to run code when embedded document is created - mongoid

I have the following models:
class User
include Mongoid::Document
embeds_one :courier, class_name: "Users::Courier"
validates_associated :courier
accepts_nested_attributes_for :courier
end
module Users
class Courier
include Mongoid::Document
embedded_in :user
after_create :foo
def foo
puts "courier created"
end
end
but this callback is only run if i call save directly on the courier object, not when i save the parent object.
Thus having a nested form and a controller that creates the user including the courier does not run the create callback of the courier.
The mongoid documentation says that this is by design:
Callbacks are available on any document, whether it is embedded within another document or not. Note that to be efficient, Mongoid only fires the callback of the document that the persistence action was executed on. This is that Mongoid aims to support large hierarchies and to handle optimized atomic updates callbacks can't be firing all over the document hierarchy.
But how can i write code that gets executed whenever a courier is created? In my case i cannot run the code in the user's after_create callback, because there are users that do not have the embedded document courier. But when a courier gets added i want to run a callback.
Whats the best option to do so?

found the answer:
embeds_one :courier, class_name: "Users::Courier", cascade_callbacks: true

Related

Get View Output In CakePHP 2.x Model

Context: I'm generating a PDF in a model callback (afterSave). I found a library I'm comfortable with called FPDF. I'm adding the FPDF class as a vendor.
So, without going into too much detail, essentially, once all checks have been completed for a particular contract application, the app needs to prepopulate a PDF file and attach it to an email.
I can figure everything out except how to generate the PDF in the model. I want to use a view to pass view vars to so that I can populate the template file and use the FPDF class to save a PDF file.
This file will in turn be attached the automated email and sent to the applicant.
So the flow is:
Once all checks have been complete (via crons and a CakePHP Shell), we trigger a function inside the model afterSave callback.
The function performs some logic and determines whether a declined or approved email should be sent.
If approved, then a PDF is generated using a view file and saved in /Views/pdf/
This file is attached to CakeEmail object and sent.
It's just the view rendering part here that I'm stuck with:
3. If approved, then a PDF is generated using a view file and saved in /Views/pdf/
How can I populate a view file with view variables and return the result into a variable?
For example, think of how the CakeEmail class does it with the CakeEmail->template('example') function......
Any ideas?
The answer is to construct your view class manually:
$view = new View(null, false);
$view->set(compact('variable1', 'variable2'));
$view->viewPath = 'ViewFolder';
$output = $view->render('view_file', 'layout');

Mongoid embeds_many: push document without save in order to preserve dirty state

In Mongoid, pushing a document into an embeds_many relation automatically persists the document to the database. Normally, this is fine, but I run into problems when I need to track changes to the embedded document.
Say you have two models:
class List
include Mongoid::Document
embeds_many :items
field :title
end
class Item
include Mongoid::Document
embedded_in :list
field :name
end
This happens to the .changes attribute:
list = List.new(title: 'List title')
list.save #list is now persisted
item = Item.new(name: 'Item name')
item.changes #returns Hash with {'name' => [nil, 'Item name']}
list.items << item #saves item to database under the hood
item.changes #returns empty Hash, because item was autosaved with list
I could use item.previous_changes to inspect the changes that were made before pushing the item into the list, but in my specific case, this would give me all kinds of troubles to keep things manageable.
What I would like to achieve, is to be able to initialize an Item document and then add it to list (via << or push) without persisting it immediately.
I'm aware that Mongoid does provide an option to set up embeds_many relations without persisting (see http://mongoid.org/en/mongoid/docs/relations.html#embeds_many):
list.items.build(name: 'Another item')
The problem there is that Mongoid creates the Item instance for you. In my case, the documents in the embeds_many relation may be subclasses of Item (e.g. SpecialItem < Item), which wouldn't work well with build. But if anyone knows of a way to get around this limitation, I'd also be happy to accept it as an answer.
To answer my own question: the problem is solved by assigning the parent document to the child, instead of adding the child to the list of children.
Continuing on the example above, you should do
item.list = list #no database query
instead of
list.items << item #automatic database insert
to set the parent - child reference without autosaving anything to the database.
To follow up on the "building a subclass" issue using your example, you can:
list.items.build({
name: "Another Item"
}, SpecialItem)
To specify the (sub)class that you want Mongoid to build for you.

Mongoid embeds_many build nested object twice?

My models are:
class Node
include Mongoid::Document
end
class PhysicalServer < Node
embeds_many :network_interfaces
end
class NetworkInterface
include Mongoid::Document
embedded_in :physical_server
end
If I do:
server.network_interfaces.build()
server.save!
when I check database, I will see 2 NetworkInterface embedded documents with duplicate ids.
However if I do:
server.network_interfaces.create()
it'll work correctly (only 1 embedded document created).
Is it a bug in Mongoid, or there is something wrong with my code?
I'm using Ruby1.9.3 + Rails 3.2.9 + Mongoid 3.0.13
Not quite sure what the issue is "yet", but I was having the same issue which I have a workaround for the time being.
By doing a forced new lookup in my controller's update action, I was able to get rid of the '$pushAll' creating a duplicate on each call to update. I have a feeling it has something to do with Mongoid's build-up of atomic operations; using a new object simply just removes the 'build' action.
I've created a gist of the issue (hopefully making it possible to re-create both the failing scenario and a workaround: https://gist.github.com/jsmestad/d0103ba0197df9f4505b)

Mongoid save fails silently without embedded_in relation

I have one document embedded in another in Mongoid.
class A < B
include Mongoid::Document
embeds_one :shipping_address, class_name: 'Address'
I have, in my case, omitted the inverse relation:
class Address
# embedded_in :A
Why is it, that although the API works fine and completely as expected:
address = A.address
address.zip = 1234
a.changed? #true
address.save
a.changed? #false
The document is not actually saved?
If i return the embedded_in statement, the save actually works fine.
My understanding of the Mongoid source is not the best so don't kick me too hard mods.
I assume that Mongoid is similar to ActiveRecord in this regard. With ActiveRecord, defining a :has_many does not change the parent object but includes methods for accessing the child. belongs_to on the other hand pulls methods for managing foreign keys.
Looking at the source code for Mongoid it seems that persistence is called from the embedded class to the parent and not the other way around (source). Removing the embedded_in would remove the additional methods for inserting the child into the parent.
Feel free to correct me if I am way off :)
While you can gain a lot when you choose to embed documents in MongoDB, you do give up the ability to query everything outside of the context of the parent. If you want to be able to work with Address documents independently, outside of the context of the parent document, you should link documents with has_many instead of embedding with embeds_many. This comes with it's own set of pros and cons.
If you choose to embed documents, you do specify embedded_in in the model and you access the embedded documents like this:
a = A.new # Parent document
a.addresses # Embedded Address documents
( Documentation Reference )

Rails 3 user activity history data

So this one has me a bit confused, and it may be unjustified but I would like some input regardless.
I have a rails 3.1.3 application with users who can view media files. I want to keep a history of their activities with regards to their media viewing. The goal is to be able to record a history such that I can track it from the user or from the media e.g. get data for user.history() and media.history() to show both all of the media accessed by a user and all of the users who accessed a particular piece of media, along with other data regarding each transaction.
So far the only way I have been able to come up with that reasonably satisfies this is to create a single history object for each transaction, sort of like this:
class history_item
belongs_to: media_object
belongs_to: user
(other transaction-specific data)
end
What concerns me about this is the number of database entries will quickly skyrocket with the typical use of this application. Is there a better way to approach this problem that I have not thought of? I am new to rails and large web application development in general, and I'm not entirely sure about how this sort of thing is normally done.
I would create such a model as a Viewing class as a HABTM model between User and MediaObject. This makes the actual structure more obvious than just having a relation called history. You can of course add methods for User#history and MediaObject#history as well, if you want that exact interface that you described.
class User
has_many :viewings
has_many :viewed_media_objects, :through => :viewings
end
class MediaObject
has_many :viewings
has_many :viewers, :through => :viewings
end
class Viewing
belongs_to :user
belongs_to :media_object
end
When querying these objects, for example given a user = User.first, if you know that you want to iterate over the user's viewed_media_objects in an action, you could use #user = User.include(:viewed_media_objects).find(params[:id]) to make sure rails fetches the related viewings and media objects at the same time as the user. This way, you won't get a whole bunch of database calls.

Resources