So, I hear that with_scope is going to be deprecated.
Which is a bit of a shame, given the stuff I’ve been writing recently.
I have a CMS with multiple clients. A ‘client’ is essentially a
company, with multiple users. Content on the site belongs to a client
content could be messages, images, schedules, etc etc. People
belonging to one client should not be able to see content created by
people from another client.
I’ve wrapped this all up very nicely into a controller method, looking
something like
class SchedulerController < ApplicationController
client_filter_on :events, :compositions
…
end
which uses some with_scope magic to ensure that any Event or
Composition objects that are created or found within that controller
have a client id matching the currently logged in user.
with_scope is going to be deprecated. So how do I accomplish this
without with_scope? Add conditions to every single find & create
method across my entire site? That doesn’t sound too clever.
Basecamp must have some sort of similar set-up. How are the 37signals
team preventing one company seeing another company’s secret messages,
without having to remember to filter every query?
I’ve wrapped this all up very nicely into a controller method, looking
which uses some with_scope magic to ensure that any Event or
Basecamp must have some sort of similar set-up. How are the 37signals
team preventing one company seeing another company’s secret messages,
without having to remember to filter every query?
Jon
Use associations instead. They will do what you want.
Associations first instantiate all of the objects. Which is not good
if your client has 1000 events, and you only want to find some.
But I think the answer is that with_scope used by AR will not be
deprecated, so you can:
def self.find_for_client(*params)
with_scope(…) { find(*params }
end
Associations first instantiate all of the objects. Which is not good
if your client has 1000 events, and you only want to find some.
Not true: crack open ./script/console, tail -f log/development.log in
another terminal, and watch the queries as you work with your
associations.
Most methods which ‘ought to’ be a single query really are.
But I think the answer is that with_scope used by AR will not be
deprecated, so you can:
def self.find_for_client(*params)
with_scope(…) { find(*params }
end
Associations first instantiate all of the objects. Which is not good
if your client has 1000 events, and you only want to find some.
Not true: crack open ./script/console, tail -f log/development.log in
another terminal, and watch the queries as you work with your associations.
Most methods which ‘ought to’ be a single query really are.
Note that I didn’t say “use 1000 queries” but rather “instantiate 1000
objects,” which, indeed, AR will do. This can be a huge performance
hit. Again, it all depends on the app.
But I think the answer is that with_scope used by AR will not be
deprecated, so you can:
def self.find_for_client(*params)
with_scope(…) { find(*params }
end
Code smell.
Pray tell what you find objectionable?
BTW, I should have written: def self.find_for_client(client, *params)
Most methods which ‘ought to’ be a single query really are.
Note that I didn’t say “use 1000 queries” but rather “instantiate 1000
objects,” which, indeed, AR will do. This can be a huge performance
hit. Again, it all depends on the app.
No, it won’t. client.events is a proxy object that loads itself only if
needed. client.events.find(:first) does not instantiate all 1000 events
then return just the first.
But I think the answer is that with_scope used by AR will not be
deprecated, so you can:
def self.find_for_client(*params)
with_scope(…) { find(*params }
end
Code smell.
Pray tell what you find objectionable?
BTW, I should have written: def self.find_for_client(client, *params)
Associations already represent this relation cleanly and understandably.
Another very important use of scoping is to eliminate certain types of
records.
For instance:
Forum - some comments may be marked as “private” - visible by some
but not others.
Planner - some events marked as “minor” - not shown unless asked for.
Financial - some transactions marked as “voided” - recorded but not
(normally) shown.
There’s no way to handle these on the database side using associations.
Of course, you can always just retreive all of the objects and then
filter with Enumerable#select, but that won’t work for large datasets.
There’s no way to handle these on the database side using associations.
Of course, you can always just retreive all of the objects and then
filter with Enumerable#select, but that won’t work for large datasets.
Scoping is the right way to handle these.
Scoping is attractive when you’re no longer dealing with a
foreign-key-based
relation. This is rare.
In nearly every case I’ve seen, it’s been used unnecessarily. The
has_many
:conditions option satisfies the scenarios above, for example.
I encourage you to formulate usable abstractions for non-fk relations.
That’s how has_many and friends came to life (foreign key scope on
another
class), after all.
A useless mess of code:
Event.with_scope(:find => { :conditions => [‘client_id = ?’,
client.id] })
{ Event.find(:all) }
That’s a pretty interesting solution. I guess merge_conditions would
be trivial to write. Maybe if with_scope goes away, they’ll include
merge_conditions.
Just thinking out loud…I think with_scope queries are compatible
with dynamic finders:
def self.find_fedex_by_warehouse(warehouse)
fedex_scope do
self.find_by_warehouse(warehouse)
end
end
Can you write it in your solution as the following?
def self.find_fedex_by_warehouse(warehouse)
self.find_by_warehouse(warehouse, :conditions => Fedex)
end
(I think that works!)
I might be able to get used to it. I can’t see any downside to it
right now. And, I guess that if Local or Fedex had some kind of
dynamic component would you really write a *_scope for it? Maybe, but
there’s too many possibilities.
It’s a viable alternative-- especially if with_scope disappears!
What do you think?
Here’s what I did as a sort of merge_conditions. It’s a find that takes
an
array of hashes containing conditions -
[{:name_field => ‘foo’}, {:whatever => ‘bar’}, {:order => ‘name_field’
}].
At the time I needed something to support user searches on multiple
fields.
Seemed less than elegant, but functional.
James M.
class ActiveRecord::Base
def self.find_with_multiple_conditions(conditions, order = ‘’)
conditions = conditions.to_a
if conditions.blank?
order_hash = order.blank? ? {} : {:order => order}
return find(:all, order_hash)
else
k, v = conditions.first
case k
when :include, :limit then sql_conditions = {k => v}
when :order then order = v
else
sql_conditions = {:conditions => ["#{k} = ?", v]}
end
# if this pass didn’t add any conditions (:order, for example)
# just continue processing the condition list
if sql_conditions.blank?
return find_with_multiple_conditions(conditions.slice(1,
conditions.length), order)
else
scope_conditions = {:find => sql_conditions}
with_scope(scope_conditions) do
return find_with_multiple_conditions(conditions.slice(1,
conditions.length), order)
end
end
end
end
end
Two questions for you Jeremy (or anyone else who won’t miss
with_scope):
how would you implement the act_as_paranoid plugin omitting
with_scope? We’ve several similar, non-FK usages of with_scope in our
app that we modelled after act_as_paranoid (e.g. filtering items
published_at the last X days)
is Rails adding merge_conditions to replace with_scope?
Thanks.
def self.local_scope
with_scope(:find => {:conditions => “warehouse = ‘PARK PLACE’ OR
warehouse = ‘BOARDWALK’”}) do
yield
end
end
From the referenced message:
The decision has been rendered as follows: with_scope will go protected
from Rails 2.0. That means you’ll be able to use with_scope in your own
private finders (the only legitimate use I’ve seen), but not in, say,
filters.
Where does this say with_scope will be deprecated?
This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.