Thanks for looking into this, it definitely seems like there are some
fundamental design issues that are surfacing here.
I agree with the thrust of your proposal, but it would be good to
investigate whether it is possible to implement this lazy evaluation
without changing the syntax to have blocks all over the place.
For example, perhaps it would be possible to store the lambda-scopes on
the actual ActiveRecord::Relation object and only evaluate the lambdas
at the very last minute (i.e. when to_a is called).
I haven't thought about this particularly long and hard so there may be
other issues with this approach...
Cheers,
Jon
But here are some considerations anyway. You couldn't just store lambdas on the Relation, because the order of operations matter.
class Post < ActiveRecord::Base
scope :localized, lambda { where( :locale => Locales.current ) }
end
# those two are different
Post.localized.where(:locale => :en)
Post.where(:locale => :en).localized
You could store a queue of previously applied Relation and lambda objects though. Of course two consecutive Relation objects could be merged the traditional way.
Although it would look transparent to the user and rails apps wouldn't require any special migration to 3.1, the AR code would become significantly more complex.
By making every named and default scope a lambda you just need to change two AR methods that prepare the Relation just before putting it on the current scope stack. On the other hand by introducing a relation-lambdas queue you have to make some methods aware of this queue. You mentioned `to_a`, but this could be postponed until `build_arel` which is called by `to_a` and similar.
Then there are info methods like `where_clauses` which are used by AR, but also by some gems I presume. Those would need to flatten the whole queue to return proper results.
Furthermore there are methods like `except` and `reverse_order` which would need to be reimplemented as actual elements on the queue. Currently `except` just drops some commands from the Relation object and `reverse_order` just changes the order clauses ASC and DESC properties. If we were to put those methods on top of lambas queue, you would have to remember the requested operation and apply it only after the lambda would be evaluated.
I'm worried about AR maintainability here. And the performance hit would probably be bigger than one additional proc evaluation every time named scope is used.
All this and it wouldn't solve the default scope issue unless you tried to mark the elements on the queue as coming from default scope and dropping them from queues coming from named scopes.
Adam
Thanks for your thoughtful response. I have re-read your previous email,
and this email, and thought about it some more.
I have changed my mind. I now agree that the best thing to do would be
to have 'scope' take blocks.
Essentially what we're getting at is that scopes should be lazily
evaluated. There may be some hackery we can apply to have laziness
without 'scope' taking a block, but ultimately it will always be
hackery. The only way to make the laziness completely transparent to the
users of the API would be to make it obvious, i.e. by requiring them to
use a block. Having a block makes it instantly clear that the evaluation
does not happen immediately.
My initial reticence was essentially because I think scope :foo
{ where(:bla) } is ugly (it's also syntactically invalid without
parentheses actually). But in reality, I think if this were the syntax
then I would just declare all my scopes as:
scope :foo do
where(:bla)
end
This actually appeals to me additionally because it makes it look more
like a method. Scope are essentially just hyped-up class methods which
get cached when they are accessed through associations.
So, in short, I approve of this proposal.
One question though. Why do we need:
default_scope do
lambda { where(:bla} }
end
Why not just:
default_scope do
where(:bla)
end
?
Also, have you thought about how to make the transition from the current
syntax? My proposal would be to deprecate the non-block syntax in 3.1
and explain to people why it might not give them what they want, but to
not actually remove it until 3.2. I think we should allow time to
upgrade as this is a feature that basically everybody uses.
Jon
You're also right, you need parentheses if you want to pass "{" block along with some arguments. Which is too bad, because it looks even worse with them.
As for your question about returning lambda from within a block. I've corrected myself in the first reply to this thread:
We have named scopes that should be used in the case that you've described:
Message.unscoped.other_scope.all
Adam
> --
> You received this message because you are subscribed to the Google Groups "Ruby on Rails: Core" group.
> To post to this group, send email to rubyonra...@googlegroups.com.
> To unsubscribe from this group, send email to rubyonrails-co...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/rubyonrails-core?hl=en.
>