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

Zach Dennis zach.dennis at gmail.com
Mon Apr 20 13:38:04 EDT 2009


On Mon, Apr 20, 2009 at 1:35 PM, Zach Dennis <zach.dennis at gmail.com> wrote:
> On Sun, Apr 19, 2009 at 6: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.
>> If you know ActiveRecord::Base#with_scope that's really all there is. A
>> scope, within a block or through a proxy, defines options that are
>> merged with the arguments to #find et al. This merging happens behind
>> the scenes, therefore the scoped options are effective, but don't show
>> up as arguments anywhere.
>>
>> I'm using this in conjunction with a generic query representation
>> (inspired by JSON Query) that is map through a combination of Rack
>> middleware and generated around_filters, see below for a glimpse.
>>
>> Michael
>>
>>
>> class PeopleController < ApplicationController
>>  include QueryScope
>>
>>  query_scope :only => :index do
>>    # Only allow to filter and order by the
>>    # virtual name attribute.
>>    # This attribute is mapped onto the real
>>    # firstname and lastname attributes.
>>    allow     :name
>>    condition :name =>
>>      "LOWER(firstname || ' ' || lastname) :op LOWER(?)"
>>    order     :name => "lastname :dir, firstname :dir"
>>  end
>>  ...
>>
>> 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
>
> I think I am starting to understand what you're after. You want to
> ensure the scope defined in your query_scope configuration block in
> the controller is used to set the scope on the controller's model.
> Right?
>
> With the assumption that that is correct, I would probably refactor
> how your #query_scope method works. Right now you're implicitly going
> through a QueryScopeBuilder to get a RequestConditioner, in order to
> access the #find_options and #offset_limit behaviour on that
> RequestConditioner. I would make your controller deal with one object,
> perhaps a RequestToQueryTranslator. Your #query_scope method would
> come out looking like:
>
>   def query_scope(options = {}, &config_block)
>    model_class = extract_resource!(options)
>    query = RequestToQueryTranslator.translate(controller.request,
> &config_block)
>    around_filter(options) do |controller, action|
>      controller.instance_variable_set(:@offset_limit, query.offset_limit)
>      model_class.send(:with_scope, :find => query.find_options, &action)
>    end
>   end
>
> This simplifies the #query_scope method and gives you more
> implementation freedom how your query is constructed. This still
> leaves something difficult to spec though, you are passing your
> query_scope config_block through, and I'm guessing it is
> instance_eval'd. You can't be sure of what's going on in that
> config_block unless you actually instance_eval inside of the
> appropriate object. This limits your ability to write a clean
> object-level example expecting the right query is constructed because
> it requires your controller to work with real dependent objects.
>
> Two approaches that come to mind for dealing with this are to change
> how your config_block works. Rather than:
>
>  query_scope :only => :index do
>    allow     :name
>    condition :name =>
>      "LOWER(firstname || ' ' || lastname) :op LOWER(?)"
>    order     :name => "lastname :dir, firstname :dir"
>  end
>
> You could do:
>
>  query_scope :only => :index do |query|
>    query.allow     :name
>    query.condition :name =>
>      "LOWER(firstname || ' ' || lastname) :op LOWER(?)"
>    query.order     :name => "lastname :dir, firstname :dir"
>  end
>
> Now in your spec, you can write a spec against the query_scope, by
> ensuring the passed in object receives #allow, #condition and #order
> with appropriate arguments. Now you don't have instance eval the block
> in some dependent object somewhere, you can simply pass the query your
> RequestToQueryTranslator.translate method returns to the config_block.
> This gives you the advantage of ensuring that the controller sets up
> the proper query, and it allows you to spec your
> RequestToQueryTranslator in isolation to ensure that given a certain
> set of method calls that it builds the right find options.
>

In addition to this I would still have an example that expected
with_scope to be set on the appropriate model based on the results of
query.find_options, and that @offset_limit was assigned based on the
results of query.offest_limit.


> The code is not as pretty I agree because you have to call methods on
> the query object passed to your config block. However, the advantage
> is that its much easier to spec out, and you're able to right examples
> that are clearer than the alternative (ones that ensure magic happens
> with a set dependent objects).
>
> WDYT?
>
>
>
> --
> Zach Dennis
> http://www.continuousthinking.com
> http://www.mutuallyhuman.com
>



-- 
Zach Dennis
http://www.continuousthinking.com
http://www.mutuallyhuman.com


More information about the rspec-users mailing list