-
Notifications
You must be signed in to change notification settings - Fork 603
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
create beforeFind lifecycle callback #525
Conversation
As for implementing soft deletes, I would expect the soft delete to be set as a model attribute through waterline-schema... similar to autoCreatedAt and autoUpdatedAt (see here) But can imagine other great uses for a beforeFind callback, like implementing access control. How's your soft delete implementation working with the beforeFind callback? Any issues you ran into? |
The only issue is, if your model looks something like this:
Since you didn't default isDeleted to false, the query will not work as expected since isDeleted will be null. This isn't an issue in my implementation though. I have an idea to improve my beforeFind though, I think a better idea for my implementation would be to allow the user's query to override the beforefind, so doing:
will return users instead of what it currently does. |
@particlebanana any update on this? |
I definitelly +1 this one. This is really useful, for several scenarios. |
If this is just for soft-deletes, thats on the roadmap. Not sure how I feel about a Are there examples of other ORMs that allow these kind of helpers? I know there is a concept of default scope in Rails but I tend to stay away from that as it's hard to change later (ex: We give you instance and class methods on models that could handle access control, though I absolutely agree that we need a way to instantiate instances without saving or creating (ex: I'll leave this open for a while and get some feedback. I'm not totally opposed, just worried of the precedent it would set and need some convincing. |
@particlebanana yeah, I've been using it in Yii: I've used it there. |
Soft deletes is not the only use I have for this. It can be used to pass any default value to a query on a model. Lets say I'm making an application for students. There is a class model and a semester model.
This prevents you from having to query for the current semester's ID first when you wish to search for a class, which is what I'd want in this scenario most of the time. Continuing with the class scenario, imagine that users eventually "graduate", so they will not be taking classes with your app anymore. If you wanted to query user's, you wouldn't want the graduated user's to come back so you'd do this beforeFind.
If these user's graduate, maybe they can still access the app, but won't be enrolled in any classes. They shouldn't be deleted. but they won't be used very often. Admittedly, the second option sounds kinda like soft delete but these are just contrived examples. |
The issue here is most of the time. When you add this logic you don't have any other options for querying. I'd argue that these scenarios are what controllers in your app are for. You can define what logic you want to query with and attach them to routes. Alternatively you could have class methods on the models for this. So for the students example you could have two methods, |
I would like to just add cross logging... performance measures, or whatever On Mon, Aug 4, 2014 at 9:19 PM, Cody Stoltman [email protected]
|
In order to achieve detailed access control I implemented hooks (or class methods) like controlBeforeFind, controlAfterFind, controlBeforeUpdate, controlBeforeDestroy, etc. . The before* hook is messing with the query criteria and the after* hook is messing with the result set after evaluating populated associations. I think this kind of custom behavior belongs in the controller, but extending a default action in Sails is actually kind of hard and not very DRY. The current blueprints for find, findOne, create, update, etc. handle quite a lot of stuff, from parsing criteria and values to handling database queries to pub/sub'ing records. So from my current point of view I would either appreciate hooks in the default blueprints to tap into the query criteria and later into the result set OR a thorough slimming / modularization of the code in the blueprints (beginning by exposing the actionUtils in the controllers) so that extending the default behavior through a controller action is much simpler. |
@mphasize Could you share your actions? I am just about to start doing something like that, for RBAC, and Entity Access Control |
@luislobo I decided to go down this way, because I am not doing classic RBAC, otherwise I would have opted for something like I made a copy of the original Sails blueprints (and the actionUtils.js), put them in "api/blueprints" to override the defaults and adapted them as shown in the Gist. That gives me a very flexible set of model specific before/after hooks, but it also makes the code harder to read/follow. |
Could you solve this with a mixin? // PerennialModel.js
module.exports = {
// ...
destroy: function (xyz) {
// custom implementation
}
}; // MyModel.js
_.merge(module.exports, require('./PerennialModel'), {
// lalala
}); |
+1 for this pull request! |
very useful improvement |
I created a Policy that enables this behavior. Here's the Gist: https://gist.github.com/mphasize/e9ed62f9d139d2152445 |
I have to agree with particlebanana that this will set a bad precedent. The database shouldn't control access control. My main use was for default values in queries but that's really not very useful since we could easily just create a static function on the model to do that. |
I'm working on solidifying sails-permissions for this kind of access control behavior: https://github.com/tjwebb/sails-permissions. There's still work to do, but hopefully some will find it to be a good starting point. |
I was hoping for this feature, not for access control, but for the scrubbing of query params among other reasons. I hate to see it go. Another use case would be query limiting where you want to make sure people specify certain params always and set defaults when doing a query. What this mostly does, it gives us freedom. By having this (and afterFind), give the ability for the users to implement specific use cases that waterline does not cover, helps keep controllers slim and our code DRY. Not to mention that is falls in line with the convention of giving lifecycle callbacks to all core methods. |
I liked this feature too. That said, it may be that it could be too easily used for evil. I don't think it should be forgotten forever. I'm willing to re-open it given a sufficiently convincing argument. |
I think that @aclave1 made a good point. I found my way here looking for a way to do soft deletes in waterline. Particlebanana mentioned that they were on the roadmap, but disappointingly that no longer seems to be the case.
Well, before_find in ActiveRecord, load() in SQLAlchemy, postLoad() in Doctrine, etc. |
I'm not sure why this is evil? Or any more or less evil than beforeValidate,beforeCreate,beforeUpdate ? I'm trying to think of a framework that had callbacks, but did not have @tjwebb I'm not sure what a convincing argument would be that has not been made. What is the arguments for beforeValidate,beforeCreate,beforeUpdate? Most people today in MVC are talking about skinny controllers and dry programming. When we put functionality in the model, we help accomplish these goals. I get that their needs to be a difference between controller logic and model logic, but we are already bluring these lines with the toJSON/toObject methods (as well as all the other callbacks). A lot of these things can be done in Policies, but that is not ideal. Policies can be dangerous because they can be bypassed easy enough when writing out your model calls in a controller. Example: I need to make sure that I include a security credential with every call to my user database to filter by. The check for this security credential is done at the policy level, however I could write a controller where I forgot to use the credential when searching despite being present. A beforeFind callback could check for the existence of this required field for searching, thus forcing me to specify it or returning an error. This goes a long way to providing better security for an app. In the same manner I can also add checks in the before find to make sure that certain criteria are specified in the query. For instance if in order to limit bloated search results I required that every search require a date range. BeforeFind: soft deletes. I know their is a plan in the works, but my preference on the the soft deletes is to use a date field BeforeFind: Scrub data for simplified data params. If I know that every search in my user table needs to search Last Name, First Name and Email Address, I can setup my before find to look for a generic I could keep going. Currently I find that I'm using creative policies to do a lot of this work, but I don't think they belong in Policies because they are very model specific. I'll revert to my previous comment
I get that freedom can be dangerous, but I don't think any more than the other callbacks. This is also popular subject having been referenced here: balderdashy/sails#1141 Its also been mentioned in different forms on StackOverflow and the Google Groups and I did not even look in the individual adapter repos. |
I still think we need this. There are tons of use cases. |
@luislobo can you provide us with some more use cases? I would love for this feature to be included, but I may have to rewrite some of this code as waterline has likely changed enough that merging this would be very hard. |
Some of them were already mentioned, by @randallmeeker and others, but I'll enumerate them:
Some considerations mentioned:
Those are some of the reasons |
Bear in mind that this is doable and much more configurable using custom collection methods. You could alternately extend |
I don't think anyone is saying, "I can't do XXXX because I don't have the beforeFind() callback." I think what is being said is the beforeFind() solves many use cases, features and issues that have been brought up. I think plenty of reasons have been given and I understand their are bad use cases out there, but can someone make the case of why implementing a beforeFind (AND afterFind) is the wrong thing for sails to do? How it would affect the framework in a negative fashion? and why its more evil than other model callbacks? |
Actually I think it's a reasonable feature! Was just wondering if there's a solution that solves a particular need more precisely. It sounded like most of the use-cases had to do with default criteria. |
Should I reopen this? |
I meant to click comment instead of reopem. Oh well! |
👍 I'm good with this as well. |
Alright guys you've convinced me. The community has spoken! I'm not ready for a You guys cool with that? |
@particlebanana, I'm cool with that. In my opinion our strategy should be: |
Seems reasonable to me! beforeFind, through associations, and custom join tables are in sight. I know @particlebanana has made strides on recursive populate. So we're more-or-less on the right track feature-wise. I'd be be curious to add composite indexes too. What about other features for which we already have PRs? |
If I could offer the suggestion that 0.11.0 also include afterFind as well. Might as well since were doing a beforefind and it would complete our lifecycle callbacks on all model methods. I don't think there is a ready pull request, but I can work on that one. The only argument to be made agaist is that is duplicates some of the work being done with toJson/toObject |
@particlebanana thanks! if you want me to implement afterFind as well, let me know! i'll submit another PR with that feature. |
Alright guys, added a 0.11 branch. Can we re-submit this against that branch? |
I think that baked-in support for dealing with soft deletes (would be preferable to having lifecycle callbacks, for that particular problem. Any chance of those being roadmapped in 0.11? |
@connor4312 the soft delete is still open here https://github.com/balderdashy/waterline/issues/134 |
👍 |
resubmitting against 0.11 |
new pull request: #902 |
Any news about this feature? |
Yes, what happened to this? Did it get in or not? |
1 similar comment
Yes, what happened to this? Did it get in or not? |
Any news? I can see there are comments in it for "FUTURE: this is where it would go. It's also in the list of allowed overrides, but just no code. |
I created a beforeFind lifecycle callback to allow a user to always modify a model's find or findOne queries with some criteria. A good use case would be if you're using a soft-delete system and you'd like to tack on an isDeleted:false to any find query on a model. Please feel free to critique the code/suggest changes, I tried to stick to the style that already exists.