Skip to content

Instantly share code, notes, and snippets.

@kyleshevlin
Last active Jun 29, 2016
Embed
What would you like to do?
Rails Components

With web components coming to browsers soon and most JavaScript MV*s already using them, it seemed the right time to figure out how to do this in Rails. We can do this by making extensive use of partials.

In Handlebars, components can be inline or block form by creating them with or without a {{yield}} respectively. Rails has the same configuration. The differences are how the partial is added to the view and how locals are passed to it.

In this gist, I have created a component generator. This will create the Slim and SCSS files for your component and update a SCSS file (a pattern I have used at work) with the correct @import. The command for doing this is:

Inline component: rails generate component <NAME>

Block component: rails generate component <NAME> --block

The name you pass in will be turned into an underscored name, so my-component turns into my_component; The only option is to pass in the --block flag to indicate that this partial should be in block form with a yield as described below. Once the files have been created, we can then modify these generated files to build up our UI. I have added some examples to this gist.

Lastly, in our view files, we render our components. A block level component, or section, requires the layout: symbol, and also the locals: hash when passing locals. Anything nested under the do will be rendered inside the yield of the section. An inline component does not have the same requirements and locals can simply be passed using symbols in key/value pairs.

Hope you find this useful and that it saves you from writing the same markup over and over. Remember try not to overkill it with this, this is a server-side rendered app; you might take hits to your performance if you're doing extensive view logic. Best of luck.

ruby:
# Ruby code goes here
.<%= @component_name %>
/ Markup goes here
== yield
class ComponentGenerator < Rails::Generators::NamedBase
source_root File.expand_path('../templates', __FILE__)
desc 'Generate all files necessary for a Rails component'
class_option :block, type: :boolean, default: false
def set_component_name_variable
@component_name = component_name
end
def create_component_files
create_slim_file
create_sass_file
update_base_imports_file
end
private
def component_name
file_name.gsub(/-/, '_')
end
def create_slim_file
created_file_name = "app/views/application/_#{component_name}.html.slim"
if options[:block]
template 'block_template.html.slim', created_file_name
else
template 'inline_template.html.slim', created_file_name
end
end
def create_sass_file
style_dir = options[:block] ? 'sections' : 'blocks'
template 'component_template.scss', "app/assets/stylesheets/#{style_dir}/_#{component_name}.scss"
end
def update_base_imports_file
style_dir = options[:block] ? 'sections' : 'blocks'
append_to_file 'app/assets/stylesheets/base.scss', "@import '#{style_dir}/#{component_name}';\n"
end
end
//////////////////////////////
// <%= @component_name %>
//////////////////////////////
.<%= @component_name %> {
}
ruby:
# Ruby code goes here
.<%= @component_name %>
/ Markup goes here
ruby:
foo ||= nil
.my_component
- if foo.present?
.my_component-heading = foo
ruby:
bar ||= nil
.my_section
header.my_section-header
- if bar.present?
.my_section-heading = bar
.my_section-body
== yield
= render layout: 'my_section', locals: { bar: 'Section Heading' } do
= render 'my_component', foo: 'Component Number One'
= render 'my_component', foo: 'Component Number Two'
p I can still add more content into the section's yield, allowing for reusable markup with the flexibility of different inner content
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment