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

ActiveRelation: support custom Scopes #2146

Closed
nizsheanez opened this Issue Jan 24, 2014 · 57 comments

Comments

Projects
None yet
@nizsheanez

$this->hasOne(Report::className(), ['fk_goal' => 'id'])->myScope()->one();
have exception
Calling unknown method: yii\db\ActiveRelation::myScope()
I must to redeclare createActiveRelation ?

Now i solve i like:

class Report extends ActiveRecord {

    public static function createQuery()
    {
        return new ReportQuery(['modelClass' => get_called_class()]);
    }

    public static function createActiveRelation($config = [])
    {
        return new ReportRelation($config);
    }

}

//scopes
trait ReportScopes
{
    public function myScope() {
        return $this;
    }
}

//apply scopes to query
class ReportQuery extends ActiveQuery
{
    use ReportScopes;
}

//apply scopes to relations
class ReportRelation extends ActiveRelation
{
    use ReportScopes;
}
@samdark

This comment has been minimized.

Show comment
Hide comment
@samdark

samdark Jan 24, 2014

Member

Looks like showstopper for beta release. @yiisoft/core-developers any idea how to solve it?

Member

samdark commented Jan 24, 2014

Looks like showstopper for beta release. @yiisoft/core-developers any idea how to solve it?

@qiangxue

This comment has been minimized.

Show comment
Hide comment
@qiangxue

qiangxue Jan 24, 2014

Member

I would say bring back the previous support for declaring scopes in AR classes.

Member

qiangxue commented Jan 24, 2014

I would say bring back the previous support for declaring scopes in AR classes.

@samdark

This comment has been minimized.

Show comment
Hide comment
@samdark

samdark Jan 24, 2014

Member

And make it the only way to declare scopes?

Member

samdark commented Jan 24, 2014

And make it the only way to declare scopes?

@qiangxue

This comment has been minimized.

Show comment
Hide comment
@qiangxue

qiangxue Jan 24, 2014

Member

The methods declared in ActiveQuery are just normal methods, nothing special.
The methods declared in ActiveRecord can be called scope methods, which are specially handled.

Member

qiangxue commented Jan 24, 2014

The methods declared in ActiveQuery are just normal methods, nothing special.
The methods declared in ActiveRecord can be called scope methods, which are specially handled.

@nizsheanez

This comment has been minimized.

Show comment
Hide comment
@nizsheanez

nizsheanez Jan 24, 2014

What the case of creating method in ActiveQuery and don't create in ActiveRelation?
Or extends ActiveQuery and don't extends ActiveRelation?

What the case of creating method in ActiveQuery and don't create in ActiveRelation?
Or extends ActiveQuery and don't extends ActiveRelation?

@samdark

This comment has been minimized.

Show comment
Hide comment
@samdark

samdark Jan 24, 2014

Member

@nizsheanez forcing users to create method in ActiveRelation as well makes it 3 extra classes per model that doesn't sound good.

Member

samdark commented Jan 24, 2014

@nizsheanez forcing users to create method in ActiveRelation as well makes it 3 extra classes per model that doesn't sound good.

@nizsheanez

This comment has been minimized.

Show comment
Hide comment
@nizsheanez

nizsheanez Jan 24, 2014

@samdark, if don't talk about scopes, if user want to create/redeclare method in ActiveQuery then we will have same situation: method will work after find() and don't after hasOne()?

A bit abstract question, but hard to say clearly.

@samdark, if don't talk about scopes, if user want to create/redeclare method in ActiveQuery then we will have same situation: method will work after find() and don't after hasOne()?

A bit abstract question, but hard to say clearly.

@samdark

This comment has been minimized.

Show comment
Hide comment
Member

samdark commented Jan 24, 2014

@samdark

This comment has been minimized.

Show comment
Hide comment
@samdark

samdark Jan 24, 2014

Member

@qiangxue I don't like the idea of reverting the change. Scope methods are definitely query-related and not instance ones.

Member

samdark commented Jan 24, 2014

@qiangxue I don't like the idea of reverting the change. Scope methods are definitely query-related and not instance ones.

@nizsheanez

This comment has been minimized.

Show comment
Hide comment
@Ragazzo

This comment has been minimized.

Show comment
Hide comment
@Ragazzo

Ragazzo Jan 24, 2014

Contributor

Hm, @samdark but i would like to have this feature, extremely flexible, especially when user dont want to specify each time scopes in with in callback. Need to make some workaround here to have this feature. I also was told by other developers that they dont like total removing scopes from AR for this reason:

  1. Scopes are business logic of urrent ar;
  2. If extracting scopes to queries why then not extracting relations there, because they are also about simple extra-query.

Not starting long discussion about should/should not we bring this feature back, just notice.

Contributor

Ragazzo commented Jan 24, 2014

Hm, @samdark but i would like to have this feature, extremely flexible, especially when user dont want to specify each time scopes in with in callback. Need to make some workaround here to have this feature. I also was told by other developers that they dont like total removing scopes from AR for this reason:

  1. Scopes are business logic of urrent ar;
  2. If extracting scopes to queries why then not extracting relations there, because they are also about simple extra-query.

Not starting long discussion about should/should not we bring this feature back, just notice.

@qiangxue

This comment has been minimized.

Show comment
Hide comment
@qiangxue

qiangxue Jan 24, 2014

Member

Scope methods are definitely query-related and not instance ones.

True theoretically. The old implementation was all for practical reason like this one. Another example is that for convenience we often put query methods directly in AR, for example, User::findByUsername().

Member

qiangxue commented Jan 24, 2014

Scope methods are definitely query-related and not instance ones.

True theoretically. The old implementation was all for practical reason like this one. Another example is that for convenience we often put query methods directly in AR, for example, User::findByUsername().

@creocoder

This comment has been minimized.

Show comment
Hide comment
@creocoder

creocoder Jan 24, 2014

Contributor

Another example is that for convenience we often put query methods directly in AR, for example, User::findByUsername().

Agreed. Another examples Category::findAll(), Tag::findAllByName().

Contributor

creocoder commented Jan 24, 2014

Another example is that for convenience we often put query methods directly in AR, for example, User::findByUsername().

Agreed. Another examples Category::findAll(), Tag::findAllByName().

@creocoder

This comment has been minimized.

Show comment
Hide comment
@creocoder

creocoder Jan 24, 2014

Contributor

Seems there is only two ways:

  • remove this feature $this->hasOne(Report::className(), ['fk_goal' => 'id'])->myScope()->one();
  • rollback scopes to AR

I'm for first way. [active_relation]->[scope] seems excess. Same things can be achived other ways.

Contributor

creocoder commented Jan 24, 2014

Seems there is only two ways:

  • remove this feature $this->hasOne(Report::className(), ['fk_goal' => 'id'])->myScope()->one();
  • rollback scopes to AR

I'm for first way. [active_relation]->[scope] seems excess. Same things can be achived other ways.

@qiangxue

This comment has been minimized.

Show comment
Hide comment
@qiangxue

qiangxue Jan 24, 2014

Member

Well, it's about code reuse: you use scope because you want to reuse the same condition in multiple places.

Member

qiangxue commented Jan 24, 2014

Well, it's about code reuse: you use scope because you want to reuse the same condition in multiple places.

@creocoder

This comment has been minimized.

Show comment
Hide comment
@creocoder

creocoder Jan 24, 2014

Contributor

@qiangxue Yes, scopes goal is clean. But can you show example when you'll have any benefits from using scope inside relation definition? I see only drawbacks like "want unclean model? use scopes inside relation definition!" :)

Contributor

creocoder commented Jan 24, 2014

@qiangxue Yes, scopes goal is clean. But can you show example when you'll have any benefits from using scope inside relation definition? I see only drawbacks like "want unclean model? use scopes inside relation definition!" :)

@qiangxue

This comment has been minimized.

Show comment
Hide comment
@qiangxue

qiangxue Jan 24, 2014

Member

I would say if used judiciously, scope methods are very convenient and useful.

A simple example is most content models (e.g. Article, Comment, Post) have a status column. Introducing scope methods such as published() in the AR classes would make it very convenient and consistent when performing relational and non-relational queries (including defining relations).

Yes, you can certainly directly write the condition when you define the relation, but why do you want to repeat? You want to write the condition explicitly in a relation method, but don't want to do so in a scope method? What's the difference?

Member

qiangxue commented Jan 24, 2014

I would say if used judiciously, scope methods are very convenient and useful.

A simple example is most content models (e.g. Article, Comment, Post) have a status column. Introducing scope methods such as published() in the AR classes would make it very convenient and consistent when performing relational and non-relational queries (including defining relations).

Yes, you can certainly directly write the condition when you define the relation, but why do you want to repeat? You want to write the condition explicitly in a relation method, but don't want to do so in a scope method? What's the difference?

@creocoder

This comment has been minimized.

Show comment
Hide comment
@creocoder

creocoder Jan 24, 2014

Contributor

@qiangxue

Yes, you can certainly directly write the condition when you define the relation, but why do you want to repeat? You want to write the condition explicitly in a relation method, but don't want to do so in a scope method? What's the difference?

You understand me wrong, i would not write condition/scope/with when defining relation. Never. This way lead to unclean models/excess queries/non-error free models. There is one reason. When you'll need relation without condition/scope/with you'll need to find all places where such "smart" optimization was used and write condition/scope/with explicitly in that places. As result time will be wasted. But i agree scopes is usefull to reuse conditions. I do not say scopes is excess feature. I just say that possibility to use scopes (and many other things) inside relation definition usually bad practice or "too smart" optimization. In reality situations when you can use scope inside relation definition is sooo rare and also you need to be 1000% ensure that this relation will not be needed in other places without that scope in future.

Contributor

creocoder commented Jan 24, 2014

@qiangxue

Yes, you can certainly directly write the condition when you define the relation, but why do you want to repeat? You want to write the condition explicitly in a relation method, but don't want to do so in a scope method? What's the difference?

You understand me wrong, i would not write condition/scope/with when defining relation. Never. This way lead to unclean models/excess queries/non-error free models. There is one reason. When you'll need relation without condition/scope/with you'll need to find all places where such "smart" optimization was used and write condition/scope/with explicitly in that places. As result time will be wasted. But i agree scopes is usefull to reuse conditions. I do not say scopes is excess feature. I just say that possibility to use scopes (and many other things) inside relation definition usually bad practice or "too smart" optimization. In reality situations when you can use scope inside relation definition is sooo rare and also you need to be 1000% ensure that this relation will not be needed in other places without that scope in future.

@qiangxue

This comment has been minimized.

Show comment
Hide comment
@qiangxue

qiangxue Jan 24, 2014

Member

So are you opposing to supporting scope method definition in AR classes?

Member

qiangxue commented Jan 24, 2014

So are you opposing to supporting scope method definition in AR classes?

@creocoder

This comment has been minimized.

Show comment
Hide comment
@creocoder

creocoder Jan 24, 2014

Contributor

@qiangxue After @samdark PR we not support scopes in AR classes, only in AQ classes. And i think currently all fine and current issue with ActiveRelation is invalid since approach was changed.

Contributor

creocoder commented Jan 24, 2014

@qiangxue After @samdark PR we not support scopes in AR classes, only in AQ classes. And i think currently all fine and current issue with ActiveRelation is invalid since approach was changed.

@creocoder

This comment has been minimized.

Show comment
Hide comment
@creocoder

creocoder Jan 24, 2014

Contributor

@qiangxue On other hand class ActiveRelation extends ActiveQuery...

Contributor

creocoder commented Jan 24, 2014

@qiangxue On other hand class ActiveRelation extends ActiveQuery...

@creocoder

This comment has been minimized.

Show comment
Hide comment
@creocoder

creocoder Jan 24, 2014

Contributor

@qiangxue Also issue starter solution seems not optimal. Optimal solution is:

class Report extends ActiveRecord {

    public static function createQuery()
    {
        return new ReportQuery(['modelClass' => get_called_class()]);
    }

    public static function createActiveRelation($config = [])
    {
        return new ReportRelation($config);
    }

}

class ReportQuery extends ActiveQuery
{
    public function myScope() {
        ...
    }
}

class ReportRelation extends ReportQuery
{
}

So maybe there is no problems at all. Want scopes? Define *Query model. Want use scopes inside relation definition? Define *Relation model extends yours *Query model. Seems clean if take into account that using scopes inside relation definitions really rare.

P.S. Seems createActiveRelation() need to be renamed to createRelation() to be consistent with createQuery() method.

Contributor

creocoder commented Jan 24, 2014

@qiangxue Also issue starter solution seems not optimal. Optimal solution is:

class Report extends ActiveRecord {

    public static function createQuery()
    {
        return new ReportQuery(['modelClass' => get_called_class()]);
    }

    public static function createActiveRelation($config = [])
    {
        return new ReportRelation($config);
    }

}

class ReportQuery extends ActiveQuery
{
    public function myScope() {
        ...
    }
}

class ReportRelation extends ReportQuery
{
}

So maybe there is no problems at all. Want scopes? Define *Query model. Want use scopes inside relation definition? Define *Relation model extends yours *Query model. Seems clean if take into account that using scopes inside relation definitions really rare.

P.S. Seems createActiveRelation() need to be renamed to createRelation() to be consistent with createQuery() method.

@qiangxue

This comment has been minimized.

Show comment
Hide comment
@qiangxue

qiangxue Jan 24, 2014

Member

Nope, this won't work: ReportRelation extends ReportQuery

Member

qiangxue commented Jan 24, 2014

Nope, this won't work: ReportRelation extends ReportQuery

@qiangxue

This comment has been minimized.

Show comment
Hide comment
@qiangxue

qiangxue Jan 24, 2014

Member

The issue reported is the complaint about excessive number of query classes needed, which I think is valid.

Scope is not just used in relation definition. You can use it also DURING performing relational queries, e.g., $post->getComments()->active()->all().

Member

qiangxue commented Jan 24, 2014

The issue reported is the complaint about excessive number of query classes needed, which I think is valid.

Scope is not just used in relation definition. You can use it also DURING performing relational queries, e.g., $post->getComments()->active()->all().

@samdark

This comment has been minimized.

Show comment
Hide comment
@samdark

samdark Jan 24, 2014

Member

Losing ability to use scope for relations doesn't look like acceptable loss but just reverting recent change means that there's one fully working and valid approach for scopes and it's magical definition in AR class.

Member

samdark commented Jan 24, 2014

Losing ability to use scope for relations doesn't look like acceptable loss but just reverting recent change means that there's one fully working and valid approach for scopes and it's magical definition in AR class.

@qiangxue

This comment has been minimized.

Show comment
Hide comment
@qiangxue

qiangxue Jan 24, 2014

Member

Yes, it is magical, and that's why it is called a feature.

Scope methods defined in query classes aren't any special at all. We don't even need to talk about them because they are just normal methods without any special support.

Member

qiangxue commented Jan 24, 2014

Yes, it is magical, and that's why it is called a feature.

Scope methods defined in query classes aren't any special at all. We don't even need to talk about them because they are just normal methods without any special support.

@samdark

This comment has been minimized.

Show comment
Hide comment
@samdark

samdark Jan 24, 2014

Member

Well, it's not expected that these methods are available in one case and not available in another. Currently I see two ways solving it:

  1. Revert removal of scopes from AR. Remove any mentioning of query classes from docs, make AR-level scopes the only official way to do it.
  2. Redesign relations somehow to use query scopes.
Member

samdark commented Jan 24, 2014

Well, it's not expected that these methods are available in one case and not available in another. Currently I see two ways solving it:

  1. Revert removal of scopes from AR. Remove any mentioning of query classes from docs, make AR-level scopes the only official way to do it.
  2. Redesign relations somehow to use query scopes.
@slavcodev

This comment has been minimized.

Show comment
Hide comment
@slavcodev

slavcodev Jan 24, 2014

Contributor

@samdark

Redesign relations somehow to use related model query :)
Contributor

slavcodev commented Jan 24, 2014

@samdark

Redesign relations somehow to use related model query :)
@qiangxue

This comment has been minimized.

Show comment
Hide comment
@qiangxue

qiangxue Jan 24, 2014

Member

Redesign relations somehow to use query scopes.

This means combining ActiveQuery and ActiveRelation into a single class.

Member

qiangxue commented Jan 24, 2014

Redesign relations somehow to use query scopes.

This means combining ActiveQuery and ActiveRelation into a single class.

@qiangxue

This comment has been minimized.

Show comment
Hide comment
@qiangxue

qiangxue Jan 24, 2014

Member

Combining ActiveQuery and ActiveRelation is not too bad. The only drawback is ActiveQuery will contain quite some methods/properties that are not used in non-relational queries.

Member

qiangxue commented Jan 24, 2014

Combining ActiveQuery and ActiveRelation is not too bad. The only drawback is ActiveQuery will contain quite some methods/properties that are not used in non-relational queries.

@creocoder

This comment has been minimized.

Show comment
Hide comment
@creocoder

creocoder Jan 24, 2014

Contributor

@qiangxue

Nope, this won't work: ReportRelation extends ReportQuery

Yeah, lack of multi inheritance in php (

Contributor

creocoder commented Jan 24, 2014

@qiangxue

Nope, this won't work: ReportRelation extends ReportQuery

Yeah, lack of multi inheritance in php (

@creocoder

This comment has been minimized.

Show comment
Hide comment
@creocoder

creocoder Jan 24, 2014

Contributor

@samdark

Remove any mentioning of query classes from docs, make AR-level scopes the only official way to do it.

This is unacceptable. Since there is behaviors with scopes on board.

Contributor

creocoder commented Jan 24, 2014

@samdark

Remove any mentioning of query classes from docs, make AR-level scopes the only official way to do it.

This is unacceptable. Since there is behaviors with scopes on board.

@lucianobaraglia

This comment has been minimized.

Show comment
Hide comment
@lucianobaraglia

lucianobaraglia Jan 25, 2014

Contributor

Combining ActiveQuery and ActiveRelation is not too bad. The only drawback is ActiveQuery will contain quite some methods/properties that are not used in non-relational queries.

This make my think in Symfony 1/Doctrine.
There is a Table Class, that handle relations and find methods, and the Object Class itself, that handle setters, getters and object methods...

Well...I really like this approach...

Contributor

lucianobaraglia commented Jan 25, 2014

Combining ActiveQuery and ActiveRelation is not too bad. The only drawback is ActiveQuery will contain quite some methods/properties that are not used in non-relational queries.

This make my think in Symfony 1/Doctrine.
There is a Table Class, that handle relations and find methods, and the Object Class itself, that handle setters, getters and object methods...

Well...I really like this approach...

@creocoder

This comment has been minimized.

Show comment
Hide comment
@creocoder

creocoder Jan 25, 2014

Contributor

Combining ActiveQuery and ActiveRelation is not too bad. The only drawback is ActiveQuery will contain quite some methods/properties that are not used in non-relational queries.

Probably it will be good balance between rollback and issue.

Contributor

creocoder commented Jan 25, 2014

Combining ActiveQuery and ActiveRelation is not too bad. The only drawback is ActiveQuery will contain quite some methods/properties that are not used in non-relational queries.

Probably it will be good balance between rollback and issue.

@Ragazzo

This comment has been minimized.

Show comment
Hide comment
@Ragazzo

Ragazzo Jan 25, 2014

Contributor

The only drawback is ActiveQuery will contain quite some methods/properties that are not used in non-relational queries.

well its a bigger hack rather then rollback, maybe if method is not found in relation that search it in AR query (also hack but without code mess and other things)?

Contributor

Ragazzo commented Jan 25, 2014

The only drawback is ActiveQuery will contain quite some methods/properties that are not used in non-relational queries.

well its a bigger hack rather then rollback, maybe if method is not found in relation that search it in AR query (also hack but without code mess and other things)?

@cebe cebe self-assigned this Feb 4, 2014

@rawtaz

This comment has been minimized.

Show comment
Hide comment
@rawtaz

rawtaz Feb 18, 2014

Contributor

Tricky. If I understand this correctly, we're basically having trouble shoehorning scope methods into both ActiveQuery and ActiveRelation without requiring a bunch of extra code (two classes, one trait, two create*() methods).

Question (since I haven't been following Yii2 lately): Would people often extend ActiveQuery and/or ActiveRelation?

If not (i.e. if it's more common to just make that one ActiveRecord model class), would the following make sense as a way to support scopes in both ActiveRelation and ActiveQuery:

  • ActiveRecord implements a function createScopes() which checks if a class named get_called_class().'Scopes' is defined (e.g. ReportScopes). If yes, returns an instance of it.
  • ActiveQueryTrait::__call() has pretty much the same code that was removed in #2025 but instead of checking if the model has the requested scope method it calls createScopes() on it, to receive an instance of the model's scopes class. It then checks if that instance has the requested scope method, and if so calls it. The scope method does its work.
  • Naturally, the user can always override the createScopes() method in the model class (returning another scopes class instance), but if we don't like the "Scopes" convention for models' scopes class names, or think it's too magic, we may choose to require that the user MUST define a createScopes() method that returns an instance of a class containing scope methods (otherwise scopes won't be used).

Pros (almost all from #2016):

  • Scopes should now work both with/on ActiveQuery and ActiveRelation.
  • Scopes are maintained in a separate class, instead of in the model.
  • The scopes class is plain, simple and less coupled than when defining scopes in ActiveQuery (which is a mix of "scopes" and mehods returning data, while scopes are really only about applying criteria).
  • Not a lot of extra work for the user (just one scopes class, and possibly one createScopes() method).
  • IDE completion should work if/when the user has overridden the createScopes() to specify scopes class name, since the IDE can then see which class is returned.

Cons:

  • I'm not sure if IDE completion would work when the user has not overridden createScopes() - Will IDEs be able to figure out what's available after e.g. $this->hasOne(Report::className(), ['fk_goal'=>'id'])-> and see the scope methods, in the scope class, through the __call()?
  • I'm not too fond of naming a class SomethingScopes, plural is rarely great in a class name, but it seems pragmatic enough.

Might be a good idea to make an interface or base class for the scopes class, so the user doesn't return ANY class when overriding createScopes().

All that said, personally I'm not sure it adds much value to force people into putting their scopes in a separate class or an ActiveQuery. Especially when people that do want to write super clean code have all the ability to do so by putting scopes in an ActiveQuery and using createQuery().

I couldn't live without scopes, I think they're very useful, and I always tuck mine at the bottom of the model classes. Nicely separated with some extra blank lines above, and they all look almost the same so it's easy to identify as well.

Put simply, they don't interfere, and it works just fine. It's pragmatic and I don't feel much of a need to separate those functions into a separate class, even if I understand the rationale behind doing so. It'd be different if we were discussing code that did more than just tweak one or two settings in a criteria. Had it been more complex code than that, it might make more sense to separate it, but this.. Probably not.

So with that in mind, I think my vote would go to just adding back the ability to have scopes directly in ActiveRecord, keeping it simple.

Contributor

rawtaz commented Feb 18, 2014

Tricky. If I understand this correctly, we're basically having trouble shoehorning scope methods into both ActiveQuery and ActiveRelation without requiring a bunch of extra code (two classes, one trait, two create*() methods).

Question (since I haven't been following Yii2 lately): Would people often extend ActiveQuery and/or ActiveRelation?

If not (i.e. if it's more common to just make that one ActiveRecord model class), would the following make sense as a way to support scopes in both ActiveRelation and ActiveQuery:

  • ActiveRecord implements a function createScopes() which checks if a class named get_called_class().'Scopes' is defined (e.g. ReportScopes). If yes, returns an instance of it.
  • ActiveQueryTrait::__call() has pretty much the same code that was removed in #2025 but instead of checking if the model has the requested scope method it calls createScopes() on it, to receive an instance of the model's scopes class. It then checks if that instance has the requested scope method, and if so calls it. The scope method does its work.
  • Naturally, the user can always override the createScopes() method in the model class (returning another scopes class instance), but if we don't like the "Scopes" convention for models' scopes class names, or think it's too magic, we may choose to require that the user MUST define a createScopes() method that returns an instance of a class containing scope methods (otherwise scopes won't be used).

Pros (almost all from #2016):

  • Scopes should now work both with/on ActiveQuery and ActiveRelation.
  • Scopes are maintained in a separate class, instead of in the model.
  • The scopes class is plain, simple and less coupled than when defining scopes in ActiveQuery (which is a mix of "scopes" and mehods returning data, while scopes are really only about applying criteria).
  • Not a lot of extra work for the user (just one scopes class, and possibly one createScopes() method).
  • IDE completion should work if/when the user has overridden the createScopes() to specify scopes class name, since the IDE can then see which class is returned.

Cons:

  • I'm not sure if IDE completion would work when the user has not overridden createScopes() - Will IDEs be able to figure out what's available after e.g. $this->hasOne(Report::className(), ['fk_goal'=>'id'])-> and see the scope methods, in the scope class, through the __call()?
  • I'm not too fond of naming a class SomethingScopes, plural is rarely great in a class name, but it seems pragmatic enough.

Might be a good idea to make an interface or base class for the scopes class, so the user doesn't return ANY class when overriding createScopes().

All that said, personally I'm not sure it adds much value to force people into putting their scopes in a separate class or an ActiveQuery. Especially when people that do want to write super clean code have all the ability to do so by putting scopes in an ActiveQuery and using createQuery().

I couldn't live without scopes, I think they're very useful, and I always tuck mine at the bottom of the model classes. Nicely separated with some extra blank lines above, and they all look almost the same so it's easy to identify as well.

Put simply, they don't interfere, and it works just fine. It's pragmatic and I don't feel much of a need to separate those functions into a separate class, even if I understand the rationale behind doing so. It'd be different if we were discussing code that did more than just tweak one or two settings in a criteria. Had it been more complex code than that, it might make more sense to separate it, but this.. Probably not.

So with that in mind, I think my vote would go to just adding back the ability to have scopes directly in ActiveRecord, keeping it simple.

cebe added a commit that referenced this issue Feb 20, 2014

WIP merge ActiveRelation into ActiveQuery
allow extending only one class to add scopes, fixes #2146

TODO:

- [ ] adjust guide docs
- [ ] adjust README files of extensions
- [ ] finish work and fix test breaks

cebe added a commit that referenced this issue Feb 20, 2014

WIP merge ActiveRelation into ActiveQuery
allow extending only one class to add scopes, fixes #2146

TODO:

- [ ] adjust guide docs
- [ ] adjust README files of extensions
- [ ] finish work and fix test breaks

@cebe cebe referenced this issue Feb 20, 2014

Merged

merge ActiveRelation into ActiveQuery #2497

3 of 3 tasks complete

@cebe cebe closed this in #2497 Feb 21, 2014

@cebe

This comment has been minimized.

Show comment
Hide comment
@cebe

cebe Feb 21, 2014

Member

Now we have the current implementation working with relations but @rawtaz has some valid points.
We decided to remove scopes from AR because of the fact that scope methods in AR differ from that in query significantly so this will cause confusion. #2016 (comment)
If there is a better way to have scopes in AR I'd vote to add it back.

Member

cebe commented Feb 21, 2014

Now we have the current implementation working with relations but @rawtaz has some valid points.
We decided to remove scopes from AR because of the fact that scope methods in AR differ from that in query significantly so this will cause confusion. #2016 (comment)
If there is a better way to have scopes in AR I'd vote to add it back.

@qiangxue

This comment has been minimized.

Show comment
Hide comment
@qiangxue

qiangxue Feb 21, 2014

Member

Let's see if more people are requesting to add it back. If so, we may consider prefixing all scope methods in AR with scope.

Member

qiangxue commented Feb 21, 2014

Let's see if more people are requesting to add it back. If so, we may consider prefixing all scope methods in AR with scope.

@rawtaz

This comment has been minimized.

Show comment
Hide comment
@rawtaz

rawtaz Feb 21, 2014

Contributor

@qiangxue How would one apply those scopes, are you thinking ->scopeForCountry($country) or ->forCountry($country)? The former doesn't make much sense IMO, but I guess you're thinking the latter :-)

Contributor

rawtaz commented Feb 21, 2014

@qiangxue How would one apply those scopes, are you thinking ->scopeForCountry($country) or ->forCountry($country)? The former doesn't make much sense IMO, but I guess you're thinking the latter :-)

@samdark

This comment has been minimized.

Show comment
Hide comment
@samdark

samdark Feb 21, 2014

Member

Magic again?

Member

samdark commented Feb 21, 2014

Magic again?

@qiangxue

This comment has been minimized.

Show comment
Hide comment
@qiangxue

qiangxue Feb 21, 2014

Member

Yes, it's the latter:

class Post extends ActiveRecord
{
    public static function scopeVisible($query)
    {
        $query->andWhere(['visible' => 1]);
    }
}

Post::find()->visible()->all();

Yes, the signature is different, but so do the method name. This is a magical feature, anyway.

Member

qiangxue commented Feb 21, 2014

Yes, it's the latter:

class Post extends ActiveRecord
{
    public static function scopeVisible($query)
    {
        $query->andWhere(['visible' => 1]);
    }
}

Post::find()->visible()->all();

Yes, the signature is different, but so do the method name. This is a magical feature, anyway.

@rawtaz

This comment has been minimized.

Show comment
Hide comment
@rawtaz

rawtaz Feb 21, 2014

Contributor

Similar to what I said in my earlier post; Even if one can make scopes like this (in the model, with a scopeFoo() method), people who want to write "cleaner" code are still free to do so (by extending ActiveQuery and overriding createQuery()). Maybe this is a good combination of pragmatism, allowing a practical approach to code, as well as clean structure? I don't think I have anything against it, seems like a good compromise.

I mean, if all one wants to do is add some scopes, then having to extend a class and override a method does feel a bit overkill. Again, usually one just adds a little piece of condition, that's all. Would be different if the scope did much more than that.

Contributor

rawtaz commented Feb 21, 2014

Similar to what I said in my earlier post; Even if one can make scopes like this (in the model, with a scopeFoo() method), people who want to write "cleaner" code are still free to do so (by extending ActiveQuery and overriding createQuery()). Maybe this is a good combination of pragmatism, allowing a practical approach to code, as well as clean structure? I don't think I have anything against it, seems like a good compromise.

I mean, if all one wants to do is add some scopes, then having to extend a class and override a method does feel a bit overkill. Again, usually one just adds a little piece of condition, that's all. Would be different if the scope did much more than that.

@samdark

This comment has been minimized.

Show comment
Hide comment
@samdark

samdark Feb 21, 2014

Member

I think for now we should keep it as is and wait for feedback.

Member

samdark commented Feb 21, 2014

I think for now we should keep it as is and wait for feedback.

@Ragazzo

This comment has been minimized.

Show comment
Hide comment
@Ragazzo

Ragazzo Feb 22, 2014

Contributor

I am against scope prefix, it is not good, better as it was before - simple method, i also against $query in method signature, i was proposing how to solve it. It is more and more like L4 and Yii2.

Contributor

Ragazzo commented Feb 22, 2014

I am against scope prefix, it is not good, better as it was before - simple method, i also against $query in method signature, i was proposing how to solve it. It is more and more like L4 and Yii2.

@jfragoulis

This comment has been minimized.

Show comment
Hide comment
@jfragoulis

jfragoulis Feb 22, 2014

Contributor

If you go for supporting scopes in AR model, I am 200% positive for going down the scope prefix road.
For me at least, it is most definitely useful to be able to distinguish scope methods from other methods just by looking at the method's name.

Contributor

jfragoulis commented Feb 22, 2014

If you go for supporting scopes in AR model, I am 200% positive for going down the scope prefix road.
For me at least, it is most definitely useful to be able to distinguish scope methods from other methods just by looking at the method's name.

@rawtaz

This comment has been minimized.

Show comment
Hide comment
@rawtaz

rawtaz Feb 22, 2014

Contributor

@Ragazzo Sorry, but where did you propose how to solve it / not use $query?

Contributor

rawtaz commented Feb 22, 2014

@Ragazzo Sorry, but where did you propose how to solve it / not use $query?

@Ragazzo

This comment has been minimized.

Show comment
Hide comment
Contributor

Ragazzo commented Feb 22, 2014

@rawtaz

This comment has been minimized.

Show comment
Hide comment
@rawtaz

rawtaz Feb 22, 2014

Contributor

It's indeed way nicer without the darn $query in the scope signature. But what matters is how one calls it, it should be ->foo(param1, param2, ...) regardless of the method signature having $query in it or not.

Contributor

rawtaz commented Feb 22, 2014

It's indeed way nicer without the darn $query in the scope signature. But what matters is how one calls it, it should be ->foo(param1, param2, ...) regardless of the method signature having $query in it or not.

@Ragazzo

This comment has been minimized.

Show comment
Hide comment
@Ragazzo

Ragazzo Feb 22, 2014

Contributor

@rawtaz currently scopes should be defined in query-object if you need scopes, so this problem does not exists currenlty. link to issue was just FYI, and maybe if one will migrate them back to AR.

Contributor

Ragazzo commented Feb 22, 2014

@rawtaz currently scopes should be defined in query-object if you need scopes, so this problem does not exists currenlty. link to issue was just FYI, and maybe if one will migrate them back to AR.

@Gcaufy

This comment has been minimized.

Show comment
Hide comment
@Gcaufy

Gcaufy Jan 14, 2015

@qiangxue

class Post extends ActiveRecord
{
    public static function scopeVisible($query)
    {
        $query->andWhere(['visible' => 1]);
    }
}
Post::find()->visible()->all();

is this working now in current yii2?

Gcaufy commented Jan 14, 2015

@qiangxue

class Post extends ActiveRecord
{
    public static function scopeVisible($query)
    {
        $query->andWhere(['visible' => 1]);
    }
}
Post::find()->visible()->all();

is this working now in current yii2?

@cebe

This comment has been minimized.

Show comment
Hide comment
@cebe

cebe Jan 14, 2015

Member

no, scopes are only supported in custom query classes.
http://www.yiiframework.com/doc-2.0/guide-db-active-record.html#scopes

Member

cebe commented Jan 14, 2015

no, scopes are only supported in custom query classes.
http://www.yiiframework.com/doc-2.0/guide-db-active-record.html#scopes

@SamMousa

This comment has been minimized.

Show comment
Hide comment
@SamMousa

SamMousa Apr 7, 2015

Contributor

I know this has been discussed in depth, but i'm unclear what's the argument against the example code by @qiangxue ..

This example query basically implements support for this feature without breaking anything else.

    class TestQuery extends ActiveQuery {
        public function __call($name, array $arguments) {
            // Check if this is a scope.
            $method = 'scope' . ucfirst($name);
            if (method_exists($this->modelClass, $method)) {
                array_unshift($arguments, $this);
                $result = forward_static_call_array([$this->modelClass, $method], $arguments);
            } else {
                $result = parent::__call($name, $arguments);
            }
        }
    }

This code has little overhead and as far as I can tell does not break BC. The argument against having different names for scopes in an ActiveQuery class vs scopes in an ActiveRecord class is not valid in my opinion. The added ease of use of this code outweights the "inconsistency" in my opinion.

Obviously I can and do implement this in my base ActiveQuery / ActiveRecord classes, but I think it would improve the framework to include this.

Contributor

SamMousa commented Apr 7, 2015

I know this has been discussed in depth, but i'm unclear what's the argument against the example code by @qiangxue ..

This example query basically implements support for this feature without breaking anything else.

    class TestQuery extends ActiveQuery {
        public function __call($name, array $arguments) {
            // Check if this is a scope.
            $method = 'scope' . ucfirst($name);
            if (method_exists($this->modelClass, $method)) {
                array_unshift($arguments, $this);
                $result = forward_static_call_array([$this->modelClass, $method], $arguments);
            } else {
                $result = parent::__call($name, $arguments);
            }
        }
    }

This code has little overhead and as far as I can tell does not break BC. The argument against having different names for scopes in an ActiveQuery class vs scopes in an ActiveRecord class is not valid in my opinion. The added ease of use of this code outweights the "inconsistency" in my opinion.

Obviously I can and do implement this in my base ActiveQuery / ActiveRecord classes, but I think it would improve the framework to include this.

@klimov-paul

This comment has been minimized.

Show comment
Hide comment
@klimov-paul

klimov-paul Apr 7, 2015

Member

This sounds interesting.
However instead of composing method name with prefix 'scope' it is better to check if the method is static using reflection. This can lead us back to original implementation, where static methods of Active Record class serve as a scopes.

Member

klimov-paul commented Apr 7, 2015

This sounds interesting.
However instead of composing method name with prefix 'scope' it is better to check if the method is static using reflection. This can lead us back to original implementation, where static methods of Active Record class serve as a scopes.

@klimov-paul klimov-paul reopened this Apr 7, 2015

@SamMousa

This comment has been minimized.

Show comment
Hide comment
@SamMousa

SamMousa Apr 7, 2015

Contributor

Making all static methods a scope automatically seems bad to me. If we use reflection we could go as far as to check the signature to see if it is a scope. However for me this adds no real benefit.

I like having the scope prefix in my ActiveRecord class and I like not having to use the scope prefix when running the query.

Also there are some static methods that are never scopes (like getDb, createQuery, tableName) I'd rather not have to think about these possible collisions; also for BC the prefix is safer since I can easily verify that any existing static methods don't start with "scope"; it is harder for me to verify that any static methods I have defined will not pass the check for being interpreted as a scope.

What is bad about the prefix method? Is it bad practice?

Contributor

SamMousa commented Apr 7, 2015

Making all static methods a scope automatically seems bad to me. If we use reflection we could go as far as to check the signature to see if it is a scope. However for me this adds no real benefit.

I like having the scope prefix in my ActiveRecord class and I like not having to use the scope prefix when running the query.

Also there are some static methods that are never scopes (like getDb, createQuery, tableName) I'd rather not have to think about these possible collisions; also for BC the prefix is safer since I can easily verify that any existing static methods don't start with "scope"; it is harder for me to verify that any static methods I have defined will not pass the check for being interpreted as a scope.

What is bad about the prefix method? Is it bad practice?

@klimov-paul

This comment has been minimized.

Show comment
Hide comment
@klimov-paul

klimov-paul Apr 7, 2015

Member

Using the prefix for scopes method introduces special magic. Also not everyone may actually like it.
For example: I prefer to declare scope methods in format 'where...':

public static function whereActive($query)
{
    ...
    return $query;
}

This way scope methods looks pretty both while declaring and while applying.

Also originally (back at 'beta' version) Active Record declared its scopes in this way - just a static methods without any prefix.

Member

klimov-paul commented Apr 7, 2015

Using the prefix for scopes method introduces special magic. Also not everyone may actually like it.
For example: I prefer to declare scope methods in format 'where...':

public static function whereActive($query)
{
    ...
    return $query;
}

This way scope methods looks pretty both while declaring and while applying.

Also originally (back at 'beta' version) Active Record declared its scopes in this way - just a static methods without any prefix.

@klimov-paul

This comment has been minimized.

Show comment
Hide comment
@klimov-paul

klimov-paul Apr 7, 2015

Member

It is obvious we are running circles around here.
I have reread the entire conversation and found final resolution reference:
#2016 (comment)

Change does not make sense.

Member

klimov-paul commented Apr 7, 2015

It is obvious we are running circles around here.
I have reread the entire conversation and found final resolution reference:
#2016 (comment)

Change does not make sense.

@klimov-paul klimov-paul closed this Apr 7, 2015

@SamMousa

This comment has been minimized.

Show comment
Hide comment
@SamMousa

SamMousa Apr 7, 2015

Contributor

Ok, clear on the final arguments. Will consider releasing this as a separate module. Thanks for the input!

Contributor

SamMousa commented Apr 7, 2015

Ok, clear on the final arguments. Will consider releasing this as a separate module. Thanks for the input!

@cebe cebe modified the milestones: 2.0 Beta, 2.0.x Apr 11, 2015

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment