Skip to content
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

Redefine Model.this with enable_default_scope #9

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

drosile
Copy link
Contributor

@drosile drosile commented Mar 5, 2015

Since enable_default_scope changes the dataset for a model, every
instance of that model is affected in its this method (which is used
for e.g. updating). This means that once (soft-)deleted, an instance can
no longer be updated, because its this method returns something like:

SELECT * FROM model WHERE ((id = 1) AND (deleted_at IS NULL));

This returns 0 rows instead of 1, and any changes therefore fail. This
includes the recover method.

@lipanski @sdepold I couldn't figure out a way to implement this without
essentially rewriting Jeremy's method: https://github.com/jeremyevans/sequel/blob/master/lib/sequel/model/base.rb#L1669-1684

Would love your feedback if there is a better way to solve this issue.

Since `enable_default_scope` changes the dataset for a model, every
instance of that model is affected in its `this` method (which is used
for e.g. updating). This means that once (soft-)deleted, an instance can
no longer be updated, because its `this` method returns something like:

SELECT * FROM model WHERE ((`id` = 1) AND (`deleted_at` IS NULL));

This returns 0 rows instead of 1, and any changes therefore fail. This
includes the `recover` method.
@drosile drosile force-pushed the fix_recover_with_default_scope branch from efccb47 to 9a0183d Compare March 5, 2015 20:13
@lipanski
Copy link
Contributor

lipanski commented Mar 8, 2015

I'd say soft deleted objects shouldn't be updated unless recovered first.
in what way is the recover method affected by this? I was looking into the reset_instance_dataset method which might be a better option if you can go a bit more into detail regarding this issue.

@drosile
Copy link
Contributor Author

drosile commented Mar 8, 2015

The recover method calls save, which in turns uses _update_dataset, which calls this. I was also looking at that method, but it seemed conflicting - the documentation says:

Note that you should not use this to change the model's dataset at runtime.

I'm guessing it will result in a race condition if you do that.

@sdepold
Copy link
Owner

sdepold commented Mar 14, 2015

IMHO you should not try to touch a deleted entry as it is deleted :D If you want to do that anyways, you should first of all disable the default scope by calling unfiltered.

@drosile
Copy link
Contributor Author

drosile commented Mar 16, 2015

I agree with the principle, but you have to touch it in order to recover it--and that change uses the _update_dataset

Here is the execution chain:

  1. recover calls save
  2. save calls _save
  3. _save calls _update_columns
  4. _update_columns calls _update
  5. _update calls _update_without_checking
  6. _update_without_checking calls _update_dataset
  7. _update_dataset uses this

Unfortunately, we cannot just use this.unfiltered as the _update_dataset, because that returns the dataset of ALL of the related model, not just one (no WHERE clause). For example, SELECT * FROM models instead of SELECT * FROM models WHERE deleted_at IS NULL AND id = 123 - the unfiltered removes both the deleted_at qualifier and the primary key.

@sdepold
Copy link
Owner

sdepold commented Mar 16, 2015

Yeah sure. I totally knew that :D that's what I meant with first of all:

Model.unfiltered.your.other.stuff

@drosile
Copy link
Contributor Author

drosile commented Mar 16, 2015

I don't think it works like that, since the model is instantiated separately from the dataset:

[3] pry(main)> User.unfiltered
=> #<Sequel::Mysql2::Dataset: "SELECT * FROM `users` ORDER BY `id`">
[4] pry(main)> User.unfiltered[1].this
=> #<Sequel::Mysql2::Dataset: "SELECT * FROM `users` WHERE ((`users`.`deleted_at` IS NULL) AND (`id` = 1)) ORDER BY `id` LIMIT 1">

@sdepold
Copy link
Owner

sdepold commented Mar 28, 2015

So what are we going to do with this PR ? Do you think the change in this PR is sane and production ready?

@drosile
Copy link
Contributor Author

drosile commented Apr 9, 2015

@sdepold I will take another look in the next few days to see if there is some other way around this issue

@drosile
Copy link
Contributor Author

drosile commented Apr 20, 2015

I found this SO answer from Jeremy which says not to use a default scope: http://stackoverflow.com/a/11677788

The best workaround is avoiding the problem by not having a default scope. In most cases a default scope is a bad idea. If you want most of your queries to use a scope, then apply the scope manually in those queries, don't use a default scope and try to backout the scope in other queries. A default scope only makes sense if all of your queries will use that scope.

I checked the other uses of this in Sequel::Model and it appears that it would affect updating, deleting, freezing, and the exists? method. But this PR (as it currently is) is funny because we set_dataset to one thing and then change this to another. I think a more ideal solution would be to modify the _update_dataset at runtime. I'll look into doing that.

By returning `super` in the modified `this` method call, we can avoid
using this code except in cases when it is needed for updating a deleted
entity.

This commit also adds a test of the recover method with the
`enable_default_scope` option.
@drosile
Copy link
Contributor Author

drosile commented Jul 8, 2015

@sdepold i've just updated this PR after a long hiatus. after a lot of a review, I think the way I did it back in March was the best option. the change I made today was to only call this code when the instance is deleted, which should help reduce the scope of its changes to Sequel.

@sdepold
Copy link
Owner

sdepold commented Dec 22, 2015

tbh, I don't have an opinion about this PR. Maybe we can invite some others to comment on it. @reidmix ?

@sdepold
Copy link
Owner

sdepold commented Feb 19, 2016

@drosile do you want to take care of this project and take ownership over it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants