When building out components in Phlex, it’s very common to need slight variations on a single component to render different classes. For example, a button component might start like this:
class Components::Button < Components::Base
def view_template(&)
div(class: "btn", &)
end
end
Then you call it from your views using Phlex Kit like this:
Button { "Watch the intro video" }
Life is good until your user asks you for a primary & secondary button.
Class variants without gems
In Ruby, there’s a good chance a gem has already solved the problem. That’s true for this problem too with gems like Phlex Variants and the Class Variant gems, but you might not need those.
Subclassing
Since Phlex is just Ruby, we could subclass our button with different classes.
class Components::Button < Components::Base
def view_template(&)
div(class: ["btn", classes], &)
end
def classes
nil
end
end
class PrimaryButton < Button
def classes
"btn-primary"
end
end
class SecondaryButton < Button
def classes
"btn-secondary"
end
end
That feels like a lot of lines of code to have different button variants, so let’s tighten it up a bit.
Endless methods
A little-known feature in newer versions of Ruby is endless methods. It lets us express classes with fewer lines of code.
class Components::Button < Components::Base
def view_template(&)
div(class: ["btn", classes], &)
end
def classes = nil
end
class PrimaryButton < Button
def classes = "btn-primary"
end
class SecondaryButton < Button
def classes = "btn-secondary"
end
Ah, better! It’s easier to read too. When a future developer opens the file, they can quickly see the different types of buttons and the class strings used to style them.
The remaining thing I’m not a huge fan of are classes with namespaces smooshed into them, in our case SecondaryButton
. Let’s clean that up to make the hierarchy more clear.
Nested classes
My first iteration of class variants might stay in one file. In this case I’d nest the primary and secondary buttons in the ./app/components/buttons.rb
file.
class Components::Button < Components::Base
def view_template(&)
div(class: ["btn", classes], &)
end
def classes = nil
class Primary < self
def classes = "btn-primary"
end
class Secondary < self
def classes = "btn-secondary"
end
end
Now, with Phlex Kit, we’d render our buttons like this:
Button::Primary { "Pre-order Phlex on Rails course" }
Button::Secondary { "Watch the intro video" }
Breaking these class variants out into separate files at this point might make it more difficult for a person coming into the project to grok the function of the component.
Limitations
As you start adding more aspects to your class variants, like size & color, you’d have to generate a constant per variant. This could be implemented with a macro that would generate class names for each variation, but then it would become order dependent and make more sense implemented as a parameterized function in a class.
I recommend using endless methods and nested classes in situations where you know there are only tens of variants with few permutations. For more variants, consider using a parameterized function or a macro to generate the class names dynamically or reach for the Phlex Variants and Class Variant gems.