I'm trying to determine the best way to represent the following using Bookshelf.js:
I have an Appointment model and each Appointment has:
1 or more Pet(s)
1 or more Service(s)
I'm able to easily represent this using many to many relationships in Bookshelf, which results in three tables: appointment, appointment_pet, and appointment_service.
I now have a new requirement that requires the ability to specify the Service(s) that are associated with each Pet on a per-appointment basis so that the services associated with a particular pet may be easily retrieved. For example:
Appointment 1
Has 1 Pet
Has 2 Services (walk, water plants)
Pet is associated with walk service only.
Getting all services for the Pet associated with Appointment 1 would return walk.
Is there an optimal way to represent this scenario using Bookshelf?
I'm considering that any Pet is always associated with a Service. In that case, I would structure it like:
As in, the Service model works as the bridge between an Appointment and the Pets. The Service can have a pet (e.g. when it's walk) or not (e.g. when it's water plants). The BookshelfJS model would look like:
var Appointment = bookshelf.Model.extend({
services: function () {
return this.hasMany('Service');
},
pets: function () {
return this.hasMany('Pet').through('Service');
},
});
A possible issue is that you'll end up with many different Services of the same type, but for different combinations of Appointment and Pet. This isn't problematic, if there're not many attributes on Service. For example, if Service has only a type attribute.
If you want to add more attributes to Service, say a price, time_duration, etc., you could extract it in its own table, as:
Then the Service table will have each type of service with its attributes (e.g. price, time_duration, ...). The code would look like:
var Appointment = bookshelf.Model.extend({
services: function () {
return this.hasMany('Service').through('AppointmentService');
},
pets: function () {
return this.hasMany('Pet').through('AppointmentService');
},
});
I would suggest going with the simpler case, and changing that if needed afterwards. You also might need to use the withPivot method to get the values from the relationship tables.
Related
I am trying to find the best way to store a user in google datastore (using nodejs). I have the userId and a userData object:
userId: "some-random-string"
userData: {
info: {
name: "name",
email: "test#email.com"
},
profile: {
username: "user",
status: "using"
},
stats: {
stuffDone: 12,
stuffNotDone: 20
},
inventory: {
money: 100,
items: ["first", "second", "third"]
}
}
I know I can store all this as a single entity but would it be worth it to split it up into an entity group if I will be updating all the nested objects separately (info, profile, stats, inventory).
So I would have the root entity (that probably wouldn't exist):
datastore.key(["Users", userId])
then I would create 4 children to store the userData:
datastore.key(["Users", userId, "UserData", "Info"); --> userData.info
datastore.key(["Users", userId, "UserData", "Profile"); --> userData.profile
datastore.key(["Users", userId, "UserData", "Stats"); --> userData.stats
datastore.key(["Users", userId, "UserData", "Inventory"); --> userData.inventory
Only the user would be updating the data so contention should not be an issue. Once the user is created I wouldn't need to update more than one child at a time.
So say the stats is updated every minute, I can just update it directly with the key:
datastore.key(["Users", userId, "UserData", "Stats");
Would this be the best practice to split it up instead of rewriting the whole user object to a single entity and have to rewrite all the indexes?
With the entity group I can still query all the user data at once with:
query = datastore.createQuery().hasAncestor(datastore.key(["Users", userId]));
Then I would just need to process it to get it back into the userData object above. I would only need to do this once when the user logs in, all other times I would need to get user data it would only be a single child and I could just get the child by key.
If I shouldn't be using an entity group like this then I could do the same thing by storing each part of the user in separate entities like:
datastore.key(["UsersInfo", userId); --> userData.info
datastore.key(["UsersProfile", userId); --> userData.profile
datastore.key(["UsersStats", userId); --> userData.stats
datastore.key(["UsersInventory", userId); --> userData.inventory
Then I could still update them individually but I think would be more taxing to get all the data since I would need to do 4 queries instead of an ancestor query.
Would these entity groups or multiple entities be necessary if I am only updating the userData.stats and userData.profile around once per minute, or should I just be using a single entity. The stats and profile objects will get bigger than only a couple properties.
Splitting an entity into multiple related ones based on the property update patterns may be a good idea, especially for large entities - to avoid unnecessarily re-writing the entire entity when just a portion of it changes (with the associated increase in the duration of the update of the entity itself and of all its related indexes. See related re-using an entity's ID for other entities of different kinds - sane idea?
Placing all the entity "pieces" into the same entity group - which, as you observed, allows you to make ancestor queries to retrieve the user data, is OK as long as you respect the max 1 write/second per entire entity group. Just double-check if you can safely assume that the query results come in a specific order, otherwise you may need to take steps to ensure each result goes to the right object.
Using separate, non-ancestor related entity "pieces" allows a higher overall write rate than the split with ancestor: max 1 write/second per each entity. And technically you don't need to do 4 queries to retrieve the entities, but 4 key lookup operations, see Retrieving an entity.
But splitting the entity can also increase your datastore costs: the single read/write operation for a single entity will be multiplied by the number of "pieces" in which the entity was split - in your case 4 if you read/write all of them together.
So you'll have to balance these pros and cons in the context of your application.
I've got a question regarding outside-aggregate validation.
In our domain partner can place orders that contain certain products (1).
Once order is placed (2) he can mark it as paid (3) in our system.
Once order is marked as paid (4) we assign licences to products in external library service (5).
Once we know licences are assigned (6) we close entire saga.
Here's a small drawing illustrating the process:
At this moment besides commands, command handlers and events there are two domain classes that are involved in entire process:
Order aggregate containing business logic
Order saga coordinating entire process and assigning licences
Now, there is one invariant that is not modelled in this process yet - before we mark order as paid we have to check if user does not already have particular licence assigned. We get this from library service as well.
Where would you put this validation? Command handler? Wrap Order in some domain service? Pass some validator to Order constructor?
class Order
{
public function __construct(OrderValidator $validator)
{
if (!$validator->isValid($this)) {
throw new \DomainException();
}
// else proceed
}
}
class OrderValidator
{
private $libraryServiceClient;
public function isValid(Order $order)
{
// check licence using $libraryServiceClient
}
}
As far as I understood the problem is in step 3 (Mark order as payed). In this step we need a user (let's call it payer) that marks the order as payed. So when creating this payer object (using factory maybe) we need to know if he is allowed to mark an order as payed. In order to get this information a call should be made to the external library.
What I suggest is to have an application service that have ->markOrderAsPayed($orderId, $payerUserId)
This method will make a call to 2 domain services. One for getting the payer and one for marking the order as payed.
$payer = $this->payerService->getPayer($payerUserId);
$this->orderService->payOrder($orderId, $payer);
In the getPayer() function you should make a call to the external library to know how many licences the payer have.
I hope this will be helpful, it is just based on what I understood from the questions and comments.
For example, if I had a 'Conversation' model a simple chat messaging system, I might do the following:
module.exports = {
attributes: {
messages: {
collection: 'Message'
}
}
}
Is this allowed in SailsJs? If not, is it recommended to mimic a "Has" relationship from Conversation to Message by using some form of custom array? Such as below:
module.exports = {
attributes: {
messages: {
type: 'array'
}
}
}
In a more complex scenario, my goal is to have the 'Conversation' know all of its 'Message' objects, but it is unnecessary for those 'Message' objects to know of its associated 'Conversation'.
I'd been using that construct for quite a while but only now did I find that the official docs don't specify it.
They mention that in one-way associations a model is associated with another model and don't mention collections. (Though they should work in just the same manner.)
For one-to-many associations they specify that a model can be associated with many other models (a collection) but don't specify what happens if you ignore the via attribute. They simply mention it is needed.
However, if you simply leave out the via attribute, the id field is used as the key for the association. So the construct you specified is allowed.
On a different note, you might want to reconsider keeping messages as either an array or a collection. Since you might need to add/retrieve/update/remove messages in a random fashion and collections and arrays can only be accessed as a whole, it might make sense to specify a relevant index on the Message collection and forgo having an association. This would let you quickly run queries like "retrieve the last 10 messages of thread " and so on.
I have a cakephp 1.3 application and I have run into a 'data leak' security hole. I am looking for the best solution using cake and not just something that will work. The application is a grade tracking system that lets teachers enter grades and students can retrieve their grades. Everything is working as expected but when I started to audit security I found that the basic CRUD operations have leaks. Meaning that student X can see student Y's grades. Students should only see their own grades. I will limit this questions to the read operation.
Using cake, I have a grade_controller.php file with this view function:
function view($id = null) {
// Extra, not related code removed
$this->set('grade', $this->grade->read(null, $id));
}
And
http://localhost/grade/view/5
Shows the grade for student $id=5. That's great. But if student #5 manipulates the URL and changes it to a 6, person #6's grades are shown. The classic data leak security hole.
I had two thoughts on the best way to resolve this. 1) I can add checks to every CRUD operations called in the controller. Or 2) add code to the model (for example using beforeFind()) to check if person X has access to that data element.
Option #1 seems like it is time consuming and error prone.
Option #2 seem like the best way to go. But, it required calling find() before some operations. The read() example above never executes beforeFind() and there is no beforeRead() callback.
Suggestions?
Instead of having a generic read() in your controller, you should move ALL finds, queries..etc into the respective model.
Then, go through each model and add any type of security checks you need on any finds that need to be restricted. 1) it will be much more DRY coding, and 2) you'll better be able to manage security risks like this since you know where all your queries are held.
For your example, I would create a getGrade($id) method in my Grade model and check the student_id field (or whatever) against your Auth user id CakeSession::read("Auth.User.id");
You could also build some generic method(s) similar to is_owner() to re-use the same logic throughout multiple methods.
If CakePHP supports isAuthorized, here's something you could do:
Create a column, that has the types of users (eg. 'student', 'teacher', ...)
Now, it the type of User is 'student', you can limit their access, to view only their data. An example of isAuthorized is as follows. I am allowing the student to edit only their profile information. You can extend the concept.
if ((($role['User']['role'] & $this->user_type['student']) == $this->user_type['student']) {
if (in_array($this->action, array('view')) == true) {
$id = $this->params->pass[0];
if ($id == $user_id) {
return (true);
}
}
}
}
I have a UserService object that is essentially a Service with additional configuration parameters and is attached to a User. In my View I would like to render a list of these UserServices however the model is formed as such:
UserService = Backbone.Model.extend({
defaults: {
id: 0,
user_id: 0, // This needs to reference the user object somehow
service_id: 0, // This needs to reference the service object somehow
length: 216000,
price: 1000
}
});
If I bind this model to the view, what is rendered ends up being the service_id instead of the parameter I need to render: service.name.
My questions are:
What should be stored in the UserService model at service? The full service object? Mongoose ID? Some other ID? (Please specify a suggestion)
Where should I get the information for this service.name / When should I pull the Service object to get that information? It would be nice to be able to do service.name in the view when rendering...
Is there a function to chain--upon loading the model, load related models that are needed?
Overall I just need an understanding of how related models work in Backbone / Express / Mongoose.
Any help is appreciated!
Update: After doing a bit of reading I have a couple different methods I can see:
Within the constructor / initializer load the Service object into the UserService object based on the reference ID returned from the server.
My questions with that one then become... what is the reference ID? Where do I put the newly retrieved object into, possibly in place of the ID?
Use the toJSON method to return an asthetic version of the UserService where it retreives the Service object and would return an object with the service name in it's place:
{
id: ???,
service_name: "this was retrieved from the service object in the toJSON method",
length: "1 hour", // converted from within the toJSON method
price: 10.00 // converted from cents to dollars in the toJSON method
}
Or maybe a combination? Thoughts?
Parse models handle loading related entities well, there is also library called Backbone Relational that can help with this.
Otherwise, my best recommendation is to store the object's ID and fetch the related entity upon success of fetching the first entity.
Anyone needing a code example just comment here and I'll see what I can come up with.