Hello
I am still learning to specify named_scopes, having some troubles and
needing some help.
This is the situation.
I have already spec two or three named_scopes independently.
but now, I want to spec a function that combines somes of these
named_scopes dynamically.
Something like this
class Thing
named_scope foo, …
named_scope bar, …
named_scope baz, …
def complex_query(condition1, condition2, condition3)
scope = Thing.scoped({})
scope = scope.scoped( Thing.foo.proxy_options ) if condition1
scope = scope.scoped( Thing.bar.proxy_options ) if condition2
scope = scope.scoped( Thing.baz.proxy_options ) if condition3
scope
end
end
I don’t know if there is a better way to implement it, but it works.
The problem is with the specification. I don’t know how to do it with
mocking.
I have already spec and test the named_scopes against the database and
think
that this time I have to test only the chaining and not the result.
Juanma C.
Juanma C. [email protected] writes:
scope = scope.scoped( Thing.bar.proxy_options ) if condition2
that this time I have to test only the chaining and not the result.
Hey Juanma,
I think it might be time to write a custom expectation matcher for
this. There’s no built-in support for specifing chained method calls
like you need (besides chaining mocks) - because chained method calls
are usually trainwrecks and trainwrecks are bad!
But named_scopes are kinda unique in that it doesn’t matter what order
they’re called in. That is, chained named_scopes are compositional and
don’t couple the client code to some internal structure.
Maybe you want a matcher like
Thing.should receive_scoped(:foo, :bar, :baz)
so you can do
Thing.foo.bar.baz
Thing.foo.baz.bar
Thing.bar.foo.baz
etc
or perhaps
Thing.should receive_scoped.foo(“foo scope param”).bar.baz(123)
I’m not 100% sure. But, to tell you the truth, I would still avoid
that. I’d just spec Thing.complex_query straight up, no mocks, and
hitting the db. There are a few reasons:
- Using mocks here would mean you’re mocking methods on the target
object… which is sometimes okay but usually wrong. At the very
least, it’s a smell indicating that you need just the slightest bit of
force to compel you to extract it to a new object
- This would really be testing implementation details. The fact that
the complex_query uses named_scopes is incidental, really - you could
imagine writing the query with find_by_sql, doing it all in memory,
whatever. Best just to specify that it returns what you want. There
aren’t any interesting collaborators here you want to isolate your
code from
- Economics - you can take the hit of slightly duplicated specs (if you
add constraints to Thing.foo then you’ll have to update its spec as
well as Thing.complex_query). Or you could spend your time writing a
non-trivial custom matcher that encourages bad habits. Personally, I
would go for the straight-forward specification that gives great
examples of expected behavior, even if it means there’s a tiny bit of
duplication.
One thing’s clear though - you’re on the right track! Encapsulating the
scope composition in Thing.complex_query is a great idea. See
for some more of my detailed thoughts on the technique you’re using.
Cheers,
Pat
I think you’re right Pat.
Thanks for your answer.
Juanma