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 - [Rails-core] Re: with_scope and :create - 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?