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?
-
1\$\begingroup\$ One idea is to avoid classname wrangling altogether and define lambdas for all your filters. \$\endgroup\$Mark Thomas– Mark Thomas2015年06月23日 01:38:32 +00:00Commented Jun 23, 2015 at 1:38
-
\$\begingroup\$ @MarkThomas, could you give me an example? \$\endgroup\$dax– dax2015年06月23日 13:27:25 +00:00Commented Jun 23, 2015 at 13:27
1 Answer 1
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
-
\$\begingroup\$ The idea of using a module-scoped namespace, e.g.
Filter::BestValue
is a good one. \$\endgroup\$Mark Thomas– Mark Thomas2015年06月23日 01:37:26 +00:00Commented Jun 23, 2015 at 1:37