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/rubyonrails-core@googlegroups.com/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:

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/rubyonrails-core@googlegroups.com/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
  

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

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

Jeremy Kemper 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)

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 Kemper 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.

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

head explodes

:wink:
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.

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

Jeremy:

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

orders

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

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

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

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

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.

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 Moore

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

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?