Skip to content

Instantly share code, notes, and snippets.

@andrewmcodes
Last active January 10, 2021 00:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save andrewmcodes/072e7f7319c20f8bd39368ffd322f101 to your computer and use it in GitHub Desktop.
Save andrewmcodes/072e7f7319c20f8bd39368ffd322f101 to your computer and use it in GitHub Desktop.
OpenStruct example with Components in Rails/Bridgetown/etc. Hopefully you can follow this - I tossed it together quickly.
<%#
This is a page or partial in my Rails app.
While this is not how I typically use them,
I think it was a perfect example and exactly
why I reached for it today.
Here is a page with a simple component:
%>
<%= render FieldComponent.new(type: "email", name: "email_address", label: "Email Address") %>
<%# This will output HTML like:
<field-component>
<label>Email Address</label>
<input type="email" name="email_address" />
</field-component>
%>
#
# Initial snippet came from: https://www.bridgetownrb.com/docs/erb-and-beyond#rendering-ruby-components
# But I tweaked it bc I really didn't like the choice of syntax (no offense in the least - total preference)
# Here is what it was:
#
# class FieldComponent
# def initialize(type: "text", name:, label:)
# @type, @name, @label = type, name, label
# end
# def render_in(view_context)
# <<~HTML
# <field-component>
# <label>#{@label}</label>
# <input type="#{@type}" name="#{@name}" />
# </field-component>
# HTML
# end
# end
#
class FieldComponent
attr_reader :name, :label, :type
def initialize(name, label, type: "text")
@name = name # | required
@label = label # | required
@type = type # | optional, default: "text"
end
def render_in(view_context)
<<~HTML
<field-component>
<label>#{label}</label>
<input type="#{type}" name="#{name}" />
</field-component>
HTML
end
end
#
# The output will be the same, we just have different stylistic preferences
# and so do all teams. Imagine the author of that snippet committed his
# version to the codebase, and this is a new one. Already we have two
# different ways of doing things for something we are going to be making over and over again
# and maybe the person coming behind me prefers his attr_readers are private methods
#
# Im sure you can see where we will be in a year or two at this rate.
#
# (╯°□°)╯︵ ┻━┻
#
# 🎉 Enter the OptStruct gem.
#
# 1. It is less code (yh yah I know some of yall dont care about that but this is a simple example keep an open mind)
# 2. It is arguably more readable - especially given the context of what we are doing.
# 3. It sets a pattern out of the gate that wont suffer from some of the more pedantic patterns we all have.
#
class FieldComponent < OptStruct.new
required :label, :name
option :type, default: "text"
def render_in(view_context)
<<~HTML
<field-component>
<label>#{label}</label>
<input type="#{type}" name="#{name}" />
</field-component>
HTML
end
end
#
# 👀 example showing how you can build new options using existing ones
# ```
# def display_name
# options.fetch(:display_name) { "[#{type}] -> #{name}" }
# end
# ```
#
# 🤙 The OptStruct has callbacks you can hook into as well: https://github.com/carlzulauf/opt_struct#on-initialization
#
# And more!
#
# 🤔 When (and basically the only time) do I reach for this? When I am about to create possibly
# hundreds of POROs that are all going to be representing the same concept.
#
# But if it is not something we are going to be constantly repeating and wouldnt benefit
# from trying to mitigate the technical debt now vs. later..
#
# I have just gone with an example from 01. Again - simple example.
#
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment