So how can I rewrite my app without using with_scope?

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.

According to DHH -
http://www.mail-archive.com/[email protected]/msg00579.html

  • 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?

Jon

Jon wrote:

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.

Example:
client.events.find :all
client.events.create …

Jack

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

On 11/1/06, S. Robert J. [email protected] wrote:

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

Code smell.

jeremy

Yet another syntax which may be the best:

def self.scoped_to_client(client, meth, *params)
with_scope(…) { send(meth, *params) }
end

Usage:
Event.scoped_to_client(client, :find, :first, …)
Event.scoped_to_client(client, :find_by_sql, “…”)

Jeremy K. wrote:

On 11/1/06, S. Robert J. [email protected] wrote:

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)

On 11/2/06, S. Robert J. [email protected] wrote:

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.

jeremy

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.

Scoping is the right way to handle these.

On 11/2/06, S. Robert J. [email protected] wrote:

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) }

Meaningfully abstracted:
client.events

jeremy

On 11/2/06, S. Robert J. [email protected] wrote:

Yet another syntax which may be the best:

def self.scoped_to_client(client, meth, *params)
with_scope(…) { send(meth, *params) }
end

Usage:
Event.scoped_to_client(client, :find, :first, …)
Event.scoped_to_client(client, :find_by_sql, “…”)

head explodes

:wink:
jeremy

Jeremy:

I’ve got a question about using with_scope for a single table. For
example, I’ve got a table:

orders

id
amount
warehouse
shipping_method
closed_at

I like the idea of using with_scope to encapsulate some business
logic about my model. I might create a method to find orders from a
local warehouse:

def self.local_scope
with_scope(:find => {:conditions => “warehouse = ‘PARK PLACE’ OR
warehouse = ‘BOARDWALK’”}) do
yield
end
end

And a method to find Fedex shipments:

def self.fedex_scope
with_scope(:find => {:conditions => "shipping_method = ‘FEDEX’}) do
yield
end
end

So, if I needed a method to find orders that are local and shipped
via Fedex, I could do this:

def self.find_by_local_and_fedex(options = {})
local_scope do
fedex_scope do
self.find(:all, options)
end
end
end

This lets me easily write queries such as:

Order.find_by_local_and_fedex(:conditions => {‘closed_at = yesterday’})
Order.find_by_local_and_fedex(:conditions => {‘amount > 999’, :order
=> ‘amount DESC’})

I know this might seem like an overly simple example, but I’m
avoiding repeating :conditions clauses for each combination of
business logic.

Is this a valid use for with_scope, or is there a better way?

Thanks,

-Anthony

Jeremy:

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?

Thanks,

-Anthony

On 11/2/06, Anthony C. [email protected] wrote:

shipping_method
end
So, if I needed a method to find orders that are local and shipped via
This lets me easily write queries such as:

Order.find_by_local_and_fedex(:conditions => {‘closed_at = yesterday’})
Order.find_by_local_and_fedex(:conditions => {‘amount > 999’, :order =>
‘amount DESC’})

I know this might seem like an overly simple example, but I’m avoiding
repeating :conditions clauses for each combination of business logic.

Is this a valid use for with_scope, or is there a better way?

Anthony, I think this use of with_scope is just fine. It reflects the
lack
of a convenient way to combine conditions.

What do you think of

Local = “warehouse in (‘PARK PLACE’, ‘BOARDWALK’)”
Fedex = “shipping_method = ‘FEDEX’”

def self.find_all_by_local_and_fedex(options = {})
find :all, merge_conditions(options, Local, Fedex)
end

jeremy

Nicholas,

Where is the authoritative source of stuff you can do with the AJAX
libraries included with ROR?
http://demo.script.aculo.us/ajax/sortable_elements

Look at
http://demo.script.aculo.us/

for demos and code samples.

Alain

Where is the authoritative source of stuff you can do with the AJAX
libraries included with ROR?

http://demo.script.aculo.us/ajax/sortable_elements

----------~----~----~----~------~----~------~–~—

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?