Michael Schuerig
2009-Apr-19 16:27 UTC
[rspec-users] Controller spec: testing that scope is set
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? Michael -- Michael Schuerig mailto:michael at schuerig.de http://www.schuerig.de/michael/
Zach Dennis
2009-Apr-19 16:32 UTC
[rspec-users] Controller spec: testing that scope is set
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?> > Michael > > -- > Michael Schuerig > mailto:michael at schuerig.de > http://www.schuerig.de/michael/ > > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >-- Zach Dennis http://www.continuousthinking.com http://www.mutuallyhuman.com
Michael Schuerig
2009-Apr-19 18:09 UTC
[rspec-users] Controller spec: testing that scope is set
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. Michael -- Michael Schuerig mailto:michael at schuerig.de http://www.schuerig.de/michael/
Zach Dennis
2009-Apr-19 18:26 UTC
[rspec-users] Controller spec: testing that scope is set
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?> > Michael > > -- > Michael Schuerig > mailto:michael at schuerig.de > http://www.schuerig.de/michael/ > > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >-- Zach Dennis http://www.continuousthinking.com http://www.mutuallyhuman.com
Michael Schuerig
2009-Apr-19 22:41 UTC
[rspec-users] Controller spec: testing that scope is set
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 -- Michael Schuerig mailto:michael at schuerig.de http://www.schuerig.de/michael/
David Chelimsky
2009-Apr-19 23:09 UTC
[rspec-users] Controller spec: testing that scope is set
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.> 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 > endUnless 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. 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 Now you can specify what with_virtual_names means in a model spec. This is easier and far less invasive than trying to specify that specific filters are applied from the controller spec. HTH, David> > -- > Michael Schuerig > mailto:michael at schuerig.de > http://www.schuerig.de/michael/ > > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >
Michael Schuerig
2009-Apr-20 00:19 UTC
[rspec-users] Controller spec: testing that scope is set
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 > endThat 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/
David Chelimsky
2009-Apr-20 01:00 UTC
[rspec-users] Controller spec: testing that scope is set
On Sun, Apr 19, 2009 at 7:19 PM, Michael Schuerig <michael at schuerig.de> wrote:> 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.So what does the controller''s index method actually look like?> > Michael > > -- > Michael Schuerig > mailto:michael at schuerig.de > http://www.schuerig.de/michael/ > > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >
Zach Dennis
2009-Apr-20 01:36 UTC
[rspec-users] Controller spec: testing that scope is set
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 > endIn your original post you asked: "How would I go about adding support a scope expectation?" Given the code you''ve shown it is not clear exactly what you are expecting. Do you just want to be able to expect that you call model_class.send(:with_scope) with the appropriate arguments? I''m sorry if I seem dense, but you have a very clear idea of what you want to accomplish, but it''s coming out very piece meal across multiple emails and it''s quite difficult to pick up on, -- Zach Dennis http://www.continuousthinking.com http://www.mutuallyhuman.com
Michael Schuerig
2009-Apr-20 01:47 UTC
[rspec-users] Controller spec: testing that scope is set
On Monday 20 April 2009, David Chelimsky wrote:> >> 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. > > So what does the controller''s index method actually look like?class PeopleController < ApplicationController ... def index respond_to do |format| format.html { render :layout => false } format.json do @people = Person.all( :offset => @offset_limit[0], :limit => @offset_limit[1]) @count = Person.count render end end end ... # app/views/people/index.json.rb { :identifier => Person.primary_key, :totalCount => @count, :items => @people.map { |p| render :partial => ''people/item'', :locals => { :person => p } } } # app/views/people/_item.json.rb { :id => person.id, :name => person.name, :dob => person.date_of_birth } :offset and :limit are the only warts, but I can''t set them in a scope as that would affect the total #count to, not just #find. Michael -- Michael Schuerig mailto:michael at schuerig.de http://www.schuerig.de/michael/
Michael Schuerig
2009-Apr-20 01:57 UTC
[rspec-users] Controller spec: testing that scope is set
On Monday 20 April 2009, Zach Dennis wrote:> In your original post you asked: > > "How would I go about adding support a scope expectation?" > > Given the code you''ve shown it is not clear exactly what you are > expecting. Do you just want to be able to expect that you call > model_class.send(:with_scope) with the appropriate arguments?I''d rather check that a particular scope is in effect for a call to #find. Just as I said. ;-) The reason is that I don''t want to encumber my controller specs with how or where the currently effective scope was set. It might have been multiple nested calls to with_scope or named scopes. In my current code this is not the case, but that''s an implementation detail. In the specs for the code that does set the scope, an expectation on the call to with_scope is the right thing. But that''s an entirely different spec at a lower level ob abstraction. Michael -- Michael Schuerig mailto:michael at schuerig.de http://www.schuerig.de/michael/
On 20 Apr 2009, at 02:57, Michael Schuerig wrote:> I''d rather check that a particular scope is in effect for a call to > #find. Just as I said. ;-)As is always the case with spec''ing Rails, I don''t know which level of abstraction is the right one to check, but it seems like what you''re asking for is literally: Person.should_receive(:all) do find_scope = Person.send(:scope, :find) find_scope[:conditions].should == { name => ["LOWER(firstname || '' '' || lastname) = LOWER(?)", ''Dav%''] } find_scope[:order].should == { :name => "lastname ASC, firstname ASC" } end However this is obviously getting your hands a bit dirty with Rails guts -- I suspect others would suggest that you just set an expectation on with_scope and be done with it (or just load some fixtures, let the find hit the database, and make sure you get the right records back? presumably that''s wrong in another way, as is traditional). Cheers, -Tom
Zach Dennis
2009-Apr-20 17:35 UTC
[rspec-users] Controller spec: testing that scope is set
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 > endI 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. 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
2009-Apr-20 17:38 UTC
[rspec-users] Controller spec: testing that scope is set
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
In a functional test, create some records that will be in the scope and some that will be out of the scope, hit the page and make sure you only see the ones that you want. I would either do this with cucumber, or write a controller spec and verify that only certain records show in the the assigns var. Either way you''re going to have to hit the db because obviously the scope affects the db queries. This is why you''d typically like to keep that stuff in the model. Pat On Sun, Apr 19, 2009 at 9:27 AM, 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? > > Michael > > -- > Michael Schuerig > mailto:michael at schuerig.de > http://www.schuerig.de/michael/ > > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >
Zach Dennis
2009-Apr-20 20:25 UTC
[rspec-users] Controller spec: testing that scope is set
On Mon, Apr 20, 2009 at 3:27 PM, Michael Schuerig <michael at schuerig.de> wrote:> On Monday 20 April 2009, Zach Dennis wrote: >> On Sun, Apr 19, 2009 at 6:41 PM, Michael Schuerig > <michael at schuerig.de> wrote: > [big snip] > >> 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? > > Exactly. > >> 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 > > I don''t agress. Both classes have distinct, although related purposes. > RequestCondition implements the translation from request parameters to > #find-options. QueryScope(Builder) fits it in with ActionController and > adds syntactic sugar. The former is easy to spec, for the latter, I''ve > been to lazy to figure out a good way.I''m just suggesting that RequestCondition and QueryScopeBuilder are encapsulated behind RequestToQueryTranslator, so your controller doesn''t have to know about both of them. Your controller doesn''t actually care about request conditioners and query scope builders. It only cares that it can translate a request to a query so it can be used to set the scope on the model.> >> WDYT? > > I''ve attached the involved files. I try to avoid doing that on mailing > lists, but we''re going in circles otherwise. > > In the meantime I''ve looked into rspec''s message expectation in order to > add an expectation for a particular scope being set on an ActiveRecord > class. What I''d need to implement this is a way to get hold of the > partially mocked class. There may not be one. > > Michael > > -- > Michael Schuerig > mailto:michael at schuerig.de > http://www.schuerig.de/michael/ > > > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >-- Zach Dennis http://www.continuousthinking.com http://www.mutuallyhuman.com
Michael Schuerig
2009-Apr-20 20:26 UTC
[rspec-users] Controller spec: testing that scope is set
On Monday 20 April 2009, Pat Maddox wrote:> In a functional test, create some records that will be in the scope > and some that will be out of the scope, hit the page and make sure > you only see the ones that you want. I would either do this with > cucumber, or write a controller spec and verify that only certain > records show in the the assigns var. Either way you''re going to have > to hit the db because obviously the scope affects the db queries.Yes, and that feels a bit like cheating.> This is why you''d typically like to keep that stuff in the model.I agree on the typically, but disagree specifically. I''ve tried my way into rspec, see below, but got stuck at the point where I''d have to get hold of the model class. Michael require ''spec/mocks/message_expectation'' module Spec #:nodoc: module Mocks #:nodoc: class ScopeExpectation def initialize(args) @args = args end def scope_matches? # here a miracle occurs ... end end module MessageExpectationScopeExtension def self.included(base) base.send(:alias_method, :matches_without_scope, :matches) base.send(:alias_method, :matches, :matches_with_scope) end def within_scope(*args) @scope_expectation = ScopeExpectation.new(args) self end def matches_with_scope(*args, &block) matches_without_scope(*args, &block) && (!@scope_expectation || @scope_expectation.scope_matches?) end end end end Spec::Mocks::MessageExpectation.class_eval do include Spec::Mocks::MessageExpectationScopeExtension end -- Michael Schuerig mailto:michael at schuerig.de http://www.schuerig.de/michael/
On Mon, Apr 20, 2009 at 1:26 PM, Michael Schuerig <michael at schuerig.de> wrote:> On Monday 20 April 2009, Pat Maddox wrote: >> In a functional test, create some records that will be in the scope >> and some that will be out of the scope, hit the page and make sure >> you only see the ones that you want. ?I would either do this with >> cucumber, or write a controller spec and verify that only certain >> records show in the the assigns var. ?Either way you''re going to have >> to hit the db because obviously the scope affects the db queries. > > Yes, and that feels a bit like cheating.What feels like cheating, and why? Look at the BDD process again. 1. Write failing acceptance test 2. Drill down, write failing unit tests 3. Make unit tests pass. Go to 2 until 4. Acceptance test passes, go to the pub In this case, you would 1. Write failing acceptance test 2. Realize that the code you''re writing is not unit testable 3. Unit test other parts of code 4. Acceptance test passes, go to the pub>> This is why you''d typically like to keep that stuff in the model. > > I agree on the typically, but disagree specifically.Agreed, which is why I gave a solution instead of just saying to put it in the model. Pat
Michael Schuerig
2009-Apr-21 08:02 UTC
[rspec-users] Controller spec: testing that scope is set
On Monday 20 April 2009, Zach Dennis wrote:> > I don''t agress. Both classes have distinct, although related > > purposes. RequestCondition implements the translation from request > > parameters to #find-options. QueryScope(Builder) fits it in with > > ActionController and adds syntactic sugar. The former is easy to > > spec, for the latter, I''ve been to lazy to figure out a good way. > > I''m just suggesting that RequestCondition and QueryScopeBuilder are > encapsulated behind RequestToQueryTranslator, so your controller > doesn''t have to know about both of them. Your controller doesn''t > actually care about request conditioners and query scope builders. It > only cares that it can translate a request to a query so it can be > used to set the scope on the model.That''s already the case. Both of these classes are only internally used by module QueryScope, which is the only thing a controller sees: 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 Michael. -- Michael Schuerig mailto:michael at schuerig.de http://www.schuerig.de/michael/
Zach Dennis
2009-Apr-21 12:52 UTC
[rspec-users] Controller spec: testing that scope is set
On Tue, Apr 21, 2009 at 4:02 AM, Michael Schuerig <michael at schuerig.de> wrote:> On Monday 20 April 2009, Zach Dennis wrote: >> > I don''t agress. Both classes have distinct, although related >> > purposes. RequestCondition implements the translation from request >> > parameters to #find-options. QueryScope(Builder) fits it in with >> > ActionController and adds syntactic sugar. The former is easy to >> > spec, for the latter, I''ve been to lazy to figure out a good way. >> >> I''m just suggesting that RequestCondition and QueryScopeBuilder are >> encapsulated behind RequestToQueryTranslator, so your controller >> doesn''t have to know about both of them. Your controller doesn''t >> actually care about request conditioners and query scope builders. It >> only cares that it can translate a request to a query so it can be >> used to set the scope on the model. > > That''s already the case. Both of these classes are only internally used > by module QueryScope, which is the only thing a controller sees:But you''re mixing in QueryScope to the controller, it''s just like if you put #query_scope inside of the controller itself. Mixins are a way to organize and re-use code, but putting code in modules does not automatically mean you are decoupling parts of your app. Your controller spec will have to take into account whatever dependencies your #query_scope method has unless you completely mock/stub it out, but that seems to work against your goal,> > 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 > > Michael. > > -- > Michael Schuerig > mailto:michael at schuerig.de > http://www.schuerig.de/michael/ > > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >-- Zach Dennis http://www.continuousthinking.com http://www.mutuallyhuman.com