I'm not very confident about this schema design (suggestions welcome), but I have two collections:
Business
Customer
and each of them contains embedded documents:
Businesses contain Events
Customers contain Registrations
The class definitions are below, but first, the question. When I create a Business and add an Event, everything looks fine. When I create a Customer and add a Registration, everything looks fine. At this point, I can do:
ruby-1.9.2-p180 :380 > customer1.registrations.first.customer
=> #<Customer _id: BSON::ObjectId('4eb22a5bbae7a01331000019'), business_id: BSON::ObjectId('4eb215a9bae7a01331000001'), registrations: nil>
Perfect! But then... I add the Registration to the Event using event1.registrations << registration1, and now event1 has a different customer reference depending on whether I access it through the Event or through the Customer:
ruby-1.9.2-p180 :444 > customer1.registrations.first.customer
=> #<Customer _id: BSON::ObjectId('4eb22a5bbae7a01331000019'), business_id: BSON::ObjectId('4eb215a9bae7a01331000001'), posts: nil>
ruby-1.9.2-p180 :445 > business1.events.first.registrations.first.customer
=> #<Event _id: BSON::ObjectId('4eb21ab2bae7a0133100000f'), business_id: BSON::ObjectId('4eb215a9bae7a01331000001'), posts: nil>
ruby-1.9.2-p180 :446 > business1.events.first.registrations.first == customer1.registrations.first
=> true
Not perfect.... my best guess is that duplicates of registration1 have been embedded in both customer1 and event1? What I wanted was links between the Event and its many Registrations (which are owned and embedded in Customers). Is that possible with this schema?
This is what my models look like. They all have additional (and irrelevant) keys that are not displayed:
class Business
include MongoMapper::Document
many :events
many :customers
end
class Event
include MongoMapper::EmbeddedDocument
embedded_in :business
many :registrations
end
class Customer
include MongoMapper::Document
belongs_to :business
key :business_id, ObjectId
many :registrations
end
class Registration
include MongoMapper::EmbeddedDocument
embedded_in :customer
belongs_to :event
key :event_id, ObjectId
end
Yep. Registration is a MongoMapper::EmbeddedDocument so it's always embedded. That means because both Customer and Event have many :registrations, different registration objects will be embedded in each.
In MongoMapper, the embedded_in :customer just alias a customer method to return the document's parent. It's just a fancier way of calling _parent_document. That's why your event's registration's customer is an event object. (See source here).
The rule of thumb is to only embed when the child will always be used in context of its parent.
Related
Let's say I have a REST API adhering to basic HATEOAS principles. Items belong to a User.
GET /item/13
{
id: 13,
name: 'someItem',
type: 'someType',
_links: [
{
rel: 'user',
href: '/user/42'
}
]
}
Now I need a way to change the user for a given item. Using either a PUT or a PATCH, which is the preferable way of performing that modification?
Establish the new relation by setting the id of the new linked resource as a simple property in the JSON body
PATCH /item/13
{
userId: 43
}
Establish the new relation by having the client pass the link itself as the input
PATCH /item/13
{
_links: [
rel: 'user',
href: '/user/43'
]
}
I usually think of links as read-only representations of relations stored in other formats (such as id:s to other resources), returned from GET calls. It doesn't feel very natural to me to have links as input to POST/PUT/PATCH calls, and the fact that links is an array makes it even stranger (should you be able to update all links? One single link?), but I have seen it suggested in various articles. Is there a best practice? What would be the benefits of using the links approach?
The point of REST is (at least one of them) is to make everything visible through a standard interface. In other words, if the 'relations' are a thing, than it too should have its own resource.
The API should also be more descriptive. This might be subjective, and I don't know all the details of your model/design, but 'items' don't have 'links'. 'Items' might instead have a single 'owner'. If this is the case, it might look something like:
GET /item/123/owner
So POSTing or PUTing an URL of a user (or some simple representation) would 'change' the owner of the item. It might be not allowed to DELETE the owner, depending on if the model allows unowned items.
Note, that the representation under "/item/123" would in this case have to link to "/item/123/owner", since the client only follows links it gets from the server.
So, think about what are important 'things', all of those should have a resource. Also, try to add as much 'meaning'/semantics as you can. The relation should not be called 'user', it should be called 'owner' (or whatever the meaning should be in your model).
I am using Symfony2 as Rest Api for a JS Frontend App. I came across a scenario where I want users to "invite" (=add) Users to a Group. But I want to only allow them to add Users to the existing Relation and not "overwrite" the whole relation, which is the standard behaviour in combination with a regular Symfony2 Form.
What would be the best practice to achieve this behaviour?
Additional Comment:
I am using Ember-Data in the frontend and my frontend would probably send a put request with the whole Group including additional users (but not all).
My JSON Payload would look something like this:
{
"usergroup": {
"name":"yxcv2",
"stake":"sdfghj",
"imageName":null,
"userCount":5,
"users":[
5,
6,
7
],
"gameGroup":"13",
}
}
In this scenario User 1,2,3 and 4 are already members of the group. And instead of replacing 1,2,3,4 with 5,6,7, I want to ADD 5,6,7 to the already existing members.
A LINK request should be used to add an item to an existing collection instead of overwriting it with a POST request.
Using a symfony form you'd post the User (id) plus a hidden field _method with value LINK to something like /groups/{id}.
routing would be something like this:
group_invite:
path: /groups/{id}
defaults: { _controller: YourBundle:Group:inviteUser }
methods: [LINK]
You could use FOSRestBundle's implicit resource name definition, too.
For the method override to work the config setting framework.http_method_override needs to be set to true ( = default value - available since symfony version 2.3).
More information can be found in the documentation chapter:
How to use HTTP Methods beyond GET and POST in Routes
this is my first stack overflow question, so I'm keeping my fingers crossed!
Scenario:
I have a relationship between contracts and customers, expressed in the model as:
'customer' => array(self::BELONGS_TO, 'Customer', 'customer_id'),
Now, this is fine - I can access my related model in the view without a problem.
What I want, however, is:
to add the ability to add a 'New Customer' button from within the Create Contract page (which is fine)
have it fire up the /views/customer/create form (which is also fine)
but then, once it's created, have it capture the new ID, close the window and return to the Create Contract page with the newly-created Customer ID pre-populated. I cannot for the life of me work out how to do this :(
Any help appreciated.
Gary
One way is this that when your actionSave() saves the customer you can redirect the page to something like
www.website.com/contract/create/CUSTOMER_ID
Now this way you can pass the user ID to the form and have it pre-populated.
With your contract create action being like : -
public function actionCreate($user_id = NULL){
...
$model = new Contract();
if($user_id)
//You can also check here if the user ID is valid or not
$model->user_id = $user_id;
$this->render('create', array(
'model' => $model
));
}
Another way is that you can put the user ID in session and redirect the user to Contract Create page and fetch the user ID there and again pass it to the model as above.
Hope it helps with the issue.
My User model has_and_belongs_to_many :conversations.
The Conversation model embeds_many :messages.
The Message model needs to have a sender and a recipient.
I was not able to find referenced_in at the Mongoid documentation.
How do I assign the users in the message? I tried to follow something similar to this implementation, but kept getting BSON::InvalidDocument: Cannot serialize an object of class Mongoid::Relations::Referenced::In into BSON.
November 2013 Update: reference_in no longer works with Mongoid 3.0? Changed to belongs_to and it seems to work the same.
As it turns out, my structure of the Message referencing the User was appropriate, and the serialization error was related to associating the Users with the Conversation. Here is my structure and the creation steps. I appreciate any feedback on better practices, thanks.
class User
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Paranoia
has_and_belongs_to_many :conversations
end
class Conversation
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Paranoia
has_and_belongs_to_many :users
embeds_many :messages
end
class Message
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Paranoia
embedded_in :conversation
embeds_one :sender, class_name: 'User'
embeds_one :recipient, class_name: 'User'
field :content
field :read_at, type: DateTime
field :sender_deleted, type: Boolean, default: false
field :recipient_deleted, type: Boolean, default: false
belongs_to :sender, class_name: "User", inverse_of: :sender, foreign_key: 'sender_id'
belongs_to :recipient, class_name: "User", inverse_of: :recipient, foreign_key: 'recipient_id'
end
Where before I was trying to #conversation.build(user_ids: [#user_one,#user_two]), the appropriate way is to #conversation.users.concat([#user_one,#user_two]). Then you can simply #conversation.messages.build(sender: #user_one, recipient: #user_two).
I need users to be able to become fans of other users. How should I design/set this up?
I need to be able to view details of user fans.
For example. I have user: Foo. Foo has 3 fans. I'd like to be able to find the names of Foo's fans. As such:
foo = User.first
foo.name (returns 'Foo')
foo.fans.first.user.name (should return 'bar', since 'bar' is a fan of 'foo')
This is how I have it set up at the moment:
User model:
embeds_many :fans
references_many :fans
Fan model:
embedded_in :user, :inverse_of => :fans
referenced_in :user
In console, I do:
User.first.fans.create!(:user => User.first)
and I get:
Mongoid::Errors::InvalidCollection: Access to the collection for Fan is not allowed since it is an embedded document, please access a collection from the root document. I think the problem is, because the fan model is embedded in the user model which has a self-reference to the user model as well....
Your thoughts will be much appreciated.
How about a self-referential association:
class User
include Mongoid::Document
references_many :fans,
:class_name => 'User',
:stored_as => :array,
:inverse_of => :fan_of
references_many :fan_of,
:class_name => 'User',
:stored_as => :array,
:inverse_of => :fans
end
# let's say we have users: al, ed, sports_star, movie_star
sports_star.fans << al
movie_star.fans << al
sports_star.fans << ed
movie_star.fans << ed
movie_star.fans # => al, ed
al.fan_of # => sports_star, movie_star
The problem is that you are trying to do relational association using only embedded documents. When you have a Fan embedded inside a User, you can only access the Fan through its parent User. You can't do something like Fan.find(some_id) because there is no collection of Fan records.
Eventually, MongoDB will support virtual collections that will allow you to do this. For now, you have to use relational-type associations. If you want to use embedded documents in this case, you have to create some ugly and inefficient custom methods to search through parent records.
With MongoDB and Mongoid, I have found that you can switch between embedded and relational associations easily. SQL-type relationships and embedded relationships both have their place and can be used together to great effect.