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?

Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Jun 22, 2015 at 13:50
\$\endgroup\$
2
  • 1
    \$\begingroup\$ One idea is to avoid classname wrangling altogether and define lambdas for all your filters. \$\endgroup\$ Commented Jun 23, 2015 at 1:38
  • \$\begingroup\$ @MarkThomas, could you give me an example? \$\endgroup\$ Commented Jun 23, 2015 at 13:27

1 Answer 1

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

answered Jun 22, 2015 at 16:02
\$\endgroup\$
1
  • \$\begingroup\$ The idea of using a module-scoped namespace, e.g. Filter::BestValue is a good one. \$\endgroup\$ Commented Jun 23, 2015 at 1:37

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.