[rspec-users] Controller spec: testing that scope is set

Michael Schuerig michael at schuerig.de
Sun Apr 19 20:19:05 EDT 2009


On Monday 20 April 2009, David Chelimsky wrote:
> On Sun, Apr 19, 2009 at 5:41 PM, Michael Schuerig 
<michael at schuerig.de> wrote:
> > On Sunday 19 April 2009, Zach Dennis wrote:
> >> On Sun, Apr 19, 2009 at 2:09 PM, Michael Schuerig
> >
> > <michael at schuerig.de> wrote:
> >> > On Sunday 19 April 2009, Zach Dennis wrote:
> >> >> On Sun, Apr 19, 2009 at 12:27 PM, Michael Schuerig
> >> >
> >> > <michael at schuerig.de> wrote:
> >> >> > In a Rails controller I set the scope on a model class in an
> >> >> > around filter. I have defined expectations on the model
> >> >> > classes, and ideally, I would add a further expectation for
> >> >> > the scope. Is this already possible in some way? How would I
> >> >> > go about adding support a scope expectation?
> >> >>
> >> >> How are you setting the said scope?
> >> >
> >> > In an around filter. However, I don't want to test the around
> >> > filter mechanism, it might as well be rack middleware instead.
> >>
> >> Sorry, I don't know what scope means to you in your app. Can you
> >> share your around_filter?
> >
> > Oops, sorry, I assumed the concept from ActiveRecord would be
> > familiar.
>
> It *is* familiar, but setting a model scope from the controller
> violates the widely-accepted guideline of skinny controllers and fat
> models. I'm guessing that's why Zach wasn't sure what you were
> talking about.

My controllers are anorexic. The functionality I'm trying to spec is 
completely generic, I just mix it into the controller. 

[snip]
> > Somewhere in QueryScope
> >
> > def query_scope(options = {}, &config_block)
> >  model_class = extract_resource!(options)
> >  builder = QueryScopeBuilder.new(config_block)
> >  around_filter(options) do |controller, action|
> >    req = builder.build_request_conditioner(controller.request)
> >    controller.instance_variable_set(:@offset_limit,
> > req.offset_limit) model_class.send(:with_scope, :find =>
> > req.find_options, &action) end
> > end
>
> Unless I'm mistaken, it is code like this outside models that was the
> underlying motivation for adding named scopes to active record. The
> reason it is problematic is that it tends to result in a lot of
> duplication outside the models, and makes the controllers really hard
> to understand.

You are mistaken as there is no duplication at all. Among other things, 
I have Rack middleware that maps request like (appropriately escaped)

  /resource/?[?name='Dav*'][/name]

to a params hash like

  { :query => [{:attribute => 'name', :op => '=', :target => 'Dav*'}],
    :order => [{:attribute => 'name'}] }

This, in turn, is interpreted by a RequestConditioner (bad name) which 
in this case would, with the help of some mappings passed to it, return

  rc.conditions == ["(firstname || ' ' || lastname) LIKE ?", 'Dav%']
  rc.order      == 'lastname, firstname'
  
As the last step, these pieces are used to define a scope around certain 
controller actions. I could pass them explicitly to, say, #find, but 
then I'd have to manually merge them with other conditions.

As to whether this functionality belongs in the model -- I am against 
it. What I've described is an adapter layer that translates from one 
representation of a query to another. It is not at all related to the 
core logic enclosed in the models.


> Consider this alternative:
>
> describe PeopleController do
>   describe "GET index" do
>     it "assigns a list of all people filtered by virtual name
> attributes" do people = [mock_model(Person)]
>       Person.stub!(:all).and_return(people)
>       people.should_receive(:with_virtual_names).and_return(people)
>       get :index
>     end
>   end
> end
>
> class PeopleController
>   def index
>     @people = PeopleController.all.with_virtual_names
>   end
> end

That code is sclerotic. I'm building a RIA-client that only requests 
JSON-formatted data from the server. Say, I add a date of birth column 
to the people grid. Then the most I want to (and have to) do is ensure 
that the requisite attribute is whitelisted for querying and contained 
in the response data. (Yes, I have JSON "views" with accompanying 
specs.)

Taking your non-generic approach, I'd have to repeatedly write and 
explicitly test, very similar code. Consider adding date of birth for 
sorting and filtering to your example. Then consider writing another 
controller that does roughly the same for movies with titles and release 
dates. You'll end up with repetitive code. Of course, you're going to 
factor out the repetition -- and that's where I already am.

So, long story short, I still think I could make good use of a way to 
define an expectation for a specific scope in effect during a #find. 
Apart from my current case, this would make it possible to check on 
dynamic scopes introduced in Rails 2.3.

Michael

-- 
Michael Schuerig
mailto:michael at schuerig.de
http://www.schuerig.de/michael/



More information about the rspec-users mailing list