2
\$\begingroup\$

I have a lot of filter classes which are named after the method names they filter by.

For example:

  • OrderByBestValueFilter corresponds to #best_value
  • OrderByPriceFilter corresponds to #price

and so on...

Since I'm doing a lot of these transformations, and the point of this set us is to make filters easy to add or remove, I have a base class where most of the work is done. To extract the method name from the calling class, I have this method:

def criteria
  class_name = self.class.name.split('::').last
  class_name.sub!(/Order/, '')
  class_name.sub!(/By/, '')
  class_name.sub!(/Filter/, '')
  class_name.scan(/[A-Z][^A-Z]*/) 
  class_name.map(&:downcase).join('_')
end

which I pared down to the following:

def criteria
  class_name = self.class.name.split('::').last
  name = class_name.scan(/[A-Z][^A-Z]*/) - %w[Order By Filter]
  name.map(&:downcase).join('_')
end

but this is still gross. Any suggestions?

\$\endgroup\$
  • 1
    \$\begingroup\$ One idea is to avoid classname wrangling altogether and define lambdas for all your filters. \$\endgroup\$ – Mark Thomas Jun 23 '15 at 1:38
  • \$\begingroup\$ @MarkThomas, could you give me an example? \$\endgroup\$ – dax Jun 23 '15 at 13:27
4
\$\begingroup\$

If this is Rails, you've got the #underscore mixin from ActiveSupport at your disposal, which'll at least take care of the last bit.

As for the naming in general, I'm wondering what purpose the full names serve, if so much of them is just dropped. I'm not saying it's wrong, I'm just wondering. Especially as it seems like the transformations are one-way.

What I mean is that the class OrderByBestValueFilter becomes just best_value, effectively "throwing away" a lot of information in the name. If said information can be thrown away, was it even necessary in the first place?

As for the transformations being one-way, it's just another indication that the class name → criterion* string rule is a little informal. The string best_value could come from a class called BestValue, ByBestValue, BestValueFilter, or some other combination. Yet I'd imagine an OrderByXyzFilter is semantically different than a ByXyzFilter: The former takes a collection and sorts its elements without removing anything, while the latter takes a collection and removes elements not matching some predicate. Yet despite being very different, both would just end being called xyz. I just find that a little suspicious.

My thinking is also that you can side-step the entire thing by, for instance, putting your classes in a Filters module. Within that namespace, they could just be called BestValue and so forth. Or each class can declare its "criterion name" in some explicit way, e.g. as a class method.

Again, I don't know your code. I'm just thinking out loud.

As for the way you're doing things now, it's not too bad, though using array subtraction is a bit much just to remove parts of a string. It could be made a little neater with a combined regexp:

name = self.class.name.split('::').last
name.gsub(/(^(Order)?(By)?|Filter$)/, '').gsub(/([a-z])([A-Z])/, '\1_\2').downcase

I've anchored matching to avoid something like ByFilterSizeFilter becoming just size.

*) it's criterion when it's singular; criteria is plural

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ The idea of using a module-scoped namespace, e.g. Filter::BestValue is a good one. \$\endgroup\$ – Mark Thomas Jun 23 '15 at 1:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.