Skip to content

Instantly share code, notes, and snippets.

@rob-murray
Last active April 15, 2022 14:52
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save rob-murray/0beab3e2072730e2039d to your computer and use it in GitHub Desktop.
Save rob-murray/0beab3e2072730e2039d to your computer and use it in GitHub Desktop.
Rails STI form components conditional by type
# app/models/blog_post.rb
class BlogPost < Post
has_many :comments
end
# app/models/comment.rb
class Comment < ActiveRecord::Base
end
# app/models/news_post.rb
class NewsPost < Post
end
# app/models/post.rb
class Post < ActiveRecord::Base
def self.types
%w(BlogPost NewsPost)
end
validates :name, presence: true
validates :slug, presence: true
end
# app/views/posts/_form.html.erb
<%= form_for(@post.becomes(Post)) do |f| %>
<% if @post.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2>
<ul>
<% @post.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :type %><br>
<%= f.select :type, Post.types %>
</div>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :slug %><br>
<%= f.text_field :slug %>
</div>
<%= render_view_component(@post, :form, form: f) %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
# app/views/posts/blog_posts/_show_components.html.erb
<h4>Comments</h4>
<p>
There are <%= post.comments.count %> comments.
</p>
# app/views/posts/news_posts/_form_components.html.erb
<div class="field">
<%= form.label :published_at %><br>
<%= form.datetime_select :published_at %>
</div>
# app/views/posts/news_posts/_show_components.html.erb
<p>
<strong>Published at:</strong>
<%= post.published_at.to_s(:short) %>
</p>
# app/views/posts/show.html.erb
<p id="notice"><%= notice %></p>
<p>
<strong>Type:</strong>
<%= @post.type %>
</p>
<p>
<strong>Name:</strong>
<%= @post.name %>
</p>
<p>
<strong>Slug:</strong>
<%= @post.slug %>
</p>
<%= render_view_component(@post) %>
<%= link_to 'Edit', edit_post_path(@post) %> |
<%= link_to 'Back', posts_path %>
# app/helpers/posts_helper.rb
module PostsHelper
def render_view_component(post, component_name = action_name, locals = {})
return unless post_view_component_exists?(component_name, post)
render(
partial: post_view_component_name(component_name, post),
locals: locals.merge(post: post)
)
end
private
def post_view_component_exists?(component_name, post)
lookup_context.exists?(post_view_component_name(component_name, post), [], partial: true)
end
def post_view_component_name(component_name, post)
"posts/#{post.class.name.demodulize.pluralize.underscore}/#{component_name}_component"
end
end
ActiveRecord::Schema.define(version: 20150418093537) do
create_table "comments", force: :cascade do |t|
t.string "content"
t.integer "blog_post_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "posts", force: :cascade do |t|
t.string "type"
t.string "name"
t.string "slug"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "published_at"
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment