I recently upgraded my edge rails in order to take advantage of the
view_paths patch Rick applied the other day, and discovered with_scope
is now protected. I'm well aware of the abuses of with_scope, but
isn't this approach sort of heavy-handed? Since when has Rails been
about protecting programmers from themselves?
If core needed with_scope to implement assocations doesn't it stand to
reason that a Rails developer might need similar functionality? Let
me give you my use case:
class User < ActiveRecord::Base
def accessible_property_find(*args)
Property.with_scope(:find => accessible_property_options) do
Property.find(*args)
end
end
end
A couple of things to note here:
* I'm not using this in a controller in a mysterious way, it's in a
model as it should be, but the protected method is still inaccessible
since I'm calling it from a different model.
* An association doesn't do the trick here, the actual conditions vary
depending on user type and aren't a simple foreign key.
* I know I could work around this by refactoring the functionality
into Property and passing the user as a parameter, but that makes my
code both more complicated and more opaque. The User should know what
properties it can access. A Property shouldn't have any concept of
the user.
So I'm requesting that this change be rolled back. Thoughts?
All that making it protected does is make sure people realize they
are taking their lives into their own hands when they play with it in
scopes where they shouldn't be playing with it.
There were many fights about with_scope back when it was public. Rails is opinionated, and it is the opinion of the core team that it should become protected to ensure clean MVC. When you think about it, it makes sense. You can still leverage with_scope in your public model methods that accept a block.
There were also people that wanted to be able to access sessions and controller params in their models. Naturally, the core team wouldn’t allow that. Imagine what would happen if they did…
Oh … now I see buggy GMail keeps me out of sync, and that you’re discussing association extensions and model-to-model stuff. My bad, sorry - I will show myself out.
Come on now, that's a totally bogus analogy. The separation between M
and VC is huge. You gain so incredibly much from having your models
independent that there's absolutely no reason to support that.
The whole with_scope thing is much more nuanced. Please review my use
case and tell me how it should be done. If you saw my whole
architecture, you would see that giving Property any knowledge of User
to work around this would just be ugly. Sending the method to
Property is definitely the best solution unless there is some other
avenue I haven't thought of.
I just get this feeling that there's been this huge crackdown due to
misuse without looking at the actual use cases where it might be
justified.
Oh ... now I see buggy GMail keeps me out of sync, and that you're
discussing association extensions and model-to-model stuff. My bad, sorry -
I will show myself out.
class User < ActiveRecord::Base
def accessible_property_find(*args)
Property.with_scope(:find => accessible_property_options) do
Property.find(*args)
end
end
end
NFI what accessible_property_options is, but I'm guessing it has to do
with the current user? This is closer to how I'd approach the
problem:
class Property < AR::Base
def find_with_user(user, *args)
with_user(user) { find *args }
end
protected
def with_user(user, &block)
with_scope :find => user.accessible_property_options, &block
end
end
class User < AR::Base
def accessible_property_find(*args)
Property.with_user(*args)
end
end
The Property store should be the one responsible for scoping its own data retrieval. If the User needs to specify conditions then it shouldn't be done w/o the Property datastore's knowledge (with_scope), it should tell the Property data store the conditions it wants applied, and the Property store can use whatever it needs to make that happen (in this case an AR with_scope). See Rick's example for a better separation of responsibilities.
I see what you're saying, but I think it's a little presumptuous to
dictate this architecture just to satisfy an abstract concept of how
things should be structured. David often says how he hates contrived
examples, and removing with_scope access to other models feels like
it's based on certain assumptions that don't always hold. Here's
why:
* The first thing is that ideally this would be modelled by User
has_many :accessible_properties, but that is not possible because
accessible_properties does not have static conditions (they depend on
the instance, therefore they can't hardcoded with a class macro... if
there is a way around this please do tell). An association proxies to
another model without requiring extra hooks to be defined on the
target model. I want to do the exact same thing because:
* The notion of accessible properties is mostly meaningful to the
user. I think if you saw my entire app, you would see my point of
view. I have a pretty clean division of concerns where the User model
is in charge of policing its own access to things, while the other
models just remain focused on their own data. I realize this is sort
of a 6-of-one-half-a-dozen-of-the-other aesthetic issue, but:
* If I decide the only meaningful interface is through User, why
should I have to have twice the methods and add a layer of indirection
where with_scope is specifically designed for this situation? Rails
does place high value on aesthetics after all.
In short, I'm not trying to make a nuisance of myself on an old issue.
When I heard with_scope would be protected it didn't bother me
because I just assumed it would work in all models, but I didn't
realize it would mean you could only scope within the same model.
Given the limitations of associations, I think that's an undue
restriction.
class << ActiveRecord::Base
public :with_scope
end
Second thing:
* The first thing is that ideally this would be modelled by User
has_many :accessible_properties, but that is not possible because
accessible_properties does not have static conditions (they depend on
the instance, therefore they can't hardcoded with a class macro... if
there is a way around this please do tell).
You can do this. If you pass in a conditions string between single
quotes, it will be interpolated by your instance. That is: