Skip to content

Commit 9e43d9d

Browse files
committed
Render example post form dynamically
We modify the Examples::Post to have the ability to set typed nested attributes and re-render the form on refresh with state.
1 parent 170ce29 commit 9e43d9d

File tree

6 files changed

+160
-64
lines changed

6 files changed

+160
-64
lines changed
Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,11 @@
11
class Examples::PostsController < ApplicationController
22
def index
3-
post = Examples::Post.new(postable: build_postable)
3+
post = Examples::Post.new(post_params_new)
44

55
render Examples::Posts::IndexView.new(post: post)
66
end
77

88
def create
9-
if params[:commit] == "Refresh"
10-
post_params = params.require(:examples_post).permit(:title)
11-
post = Examples::Post.new(post_params) do |p|
12-
p.postable = build_postable
13-
end
14-
Rails.logger.info("Post: #{post.inspect}")
15-
return render Examples::Posts::IndexView.new(post: post), status: :unprocessable_entity
16-
end
17-
189
post = Examples::Post.new(post_create_params)
1910

2011
if post.save
@@ -26,19 +17,11 @@ def create
2617

2718
private
2819

29-
def post_create_params
30-
postable_attributes = (params.dig(:examples_post, :postable_type) == "Examples::Posts::Markdown") ? [:body] : [:url]
31-
params.require(:examples_post).permit(:title, :postable_type, postable_attributes: postable_attributes)
32-
end
20+
def post_create_params = params.require(:examples_post, {}).permit(:title)
3321

34-
def build_postable
35-
case params[:type]
36-
when "link"
37-
Examples::Posts::Link.new
38-
when "image"
39-
Examples::Posts::Image.new
40-
else
41-
Examples::Posts::Markdown.new
42-
end
22+
def post_params = params.fetch(:examples_post, {})
23+
24+
def post_params_new
25+
post_params.permit(:title, :postable_type, link_attributes: [:url], image_attributes: [:url], markdown_attributes: [:body])
4326
end
4427
end

app/javascript/controllers/forms/refresh.js

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,13 @@ import debug from '../../utils/debug';
44
const console = debug('app:javascript:controllers:forms:refresh');
55

66
export default class extends Controller {
7+
static targets = ['refreshButton'];
8+
79
connect() {
8-
console.log('connect');
10+
this.refreshButtonTarget.hidden = true;
911
}
1012

1113
refresh() {
12-
this.element.querySelectorAll('[required]').forEach((element) => {
13-
element.required = false;
14-
});
15-
const submitButton = document.createElement('input');
16-
submitButton.type = 'submit';
17-
submitButton.name = 'commit';
18-
submitButton.value = 'Refresh';
19-
submitButton.style.display = 'none';
20-
this.element.closest('form').appendChild(submitButton);
21-
submitButton.click();
14+
this.refreshButtonTarget.click();
2215
}
2316
}

app/javascript/css/components/button.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
font-family: var(--sans-serif);
1010
color: var(--joy-light);
1111

12+
&[hidden] {
13+
display: none;
14+
}
15+
1216
&:is(a) {
1317
text-decoration: none;
1418
}

app/models/examples/post.rb

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,70 @@
11
class Examples::Post < ApplicationRecord
2-
delegated_type :postable, types: %w[
2+
POSTABLE_TYPES = %w[
33
Examples::Posts::Markdown
44
Examples::Posts::Link
55
Examples::Posts::Image
6-
]
6+
].freeze
7+
8+
delegated_type :postable, types: POSTABLE_TYPES
9+
10+
attr_writer :markdown, :link, :image
711

812
accepts_nested_attributes_for :postable
13+
14+
after_initialize :infer_postable
15+
16+
def self.postable_types
17+
POSTABLE_TYPES
18+
end
19+
20+
def markdown
21+
@markdown || (postable if postable.is_a?(Examples::Posts::Markdown))
22+
end
23+
24+
def image
25+
@image || (postable if postable.is_a?(Examples::Posts::Image))
26+
end
27+
28+
def link
29+
@link || (postable if postable.is_a?(Examples::Posts::Link))
30+
end
31+
32+
def markdown?
33+
@markdown.present? || postable.is_a?(Examples::Posts::Markdown)
34+
end
35+
36+
def image?
37+
@image.present? || postable.is_a?(Examples::Posts::Image)
38+
end
39+
40+
def link?
41+
@link.present? || postable.is_a?(Examples::Posts::Link)
42+
end
43+
44+
def markdown_attributes=(attributes)
45+
self.markdown = Examples::Posts::Markdown.new(attributes)
46+
end
47+
48+
def link_attributes=(attributes)
49+
self.link = Examples::Posts::Link.new(attributes)
50+
end
51+
52+
def image_attributes=(attributes)
53+
self.image = Examples::Posts::Image.new(attributes)
54+
end
55+
56+
private
57+
58+
def infer_postable
59+
return unless postable.nil?
60+
61+
case postable_type
62+
when "Examples::Posts::Markdown"
63+
self.postable = markdown if markdown?
64+
when "Examples::Posts::Link"
65+
self.postable = link if link?
66+
when "Examples::Posts::Image"
67+
self.postable = image if image?
68+
end
69+
end
970
end

app/views/examples/posts/index_view.rb

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
class Examples::Posts::IndexView < ApplicationView
44
include Phlex::Rails::Helpers::FormWith
55
include Phlex::Rails::Helpers::LinkTo
6-
include Phlex::Rails::Helpers::ContentFor
7-
include Phlex::Rails::Helpers::LabelTag
8-
include Phlex::Rails::Helpers::RadioButtonTag
96
include Phlex::Rails::Helpers::TurboFrameTag
7+
include Phlex::Rails::Helpers::ClassNames
108

119
def initialize(post:)
1210
@post = post
11+
@image = post.image || Examples::Posts::Image.new
12+
@link = post.link || Examples::Posts::Link.new
13+
@markdown = post.markdown || Examples::Posts::Markdown.new
14+
@post.postable ||= @markdown
1315
end
1416

1517
def view_template
@@ -22,42 +24,60 @@ def view_template
2224
turbo_frame_tag :examples_post_form do
2325
form_with model: @post, url: examples_posts_path, method: :post, data: {controller: "form-refresh"} do |form|
2426
div(class: "tabs") do
25-
link_to "Text", url_for(type: "markdown"), class: "tab"
26-
link_to "Link", url_for(type: "link"), class: "tab"
27-
link_to "Image", url_for(type: "image"), class: "tab"
27+
form.label :postable_type, "Text", value: @markdown.class.name, class: "tab"
28+
form.radio_button :postable_type, @markdown.class.name, checked: markdown?, data: {action: "form-refresh#refresh"}
29+
form.label :postable_type, "Link", value: @link.class.name, class: "tab"
30+
form.radio_button :postable_type, @link.class.name, checked: link?, data: {action: "form-refresh#refresh"}
31+
form.label :postable_type, "Image", value: @image.class.name, class: "tab"
32+
form.radio_button :postable_type, @image.class.name, checked: image?, data: {action: "form-refresh#refresh"}
2833
end
2934

3035
fieldset do
3136
form.label :title
3237
form.text_field :title, placeholder: "My Post 🎸", required: false
3338
end
3439

35-
form.hidden_field :postable_type
36-
form.fields_for :postable, @post.postable do |postable_form|
37-
case @post.postable
38-
when Examples::Posts::Markdown
39-
fieldset do
40-
postable_form.label :body, "Text (Markdown)"
41-
postable_form.text_area :body, placeholder: "# This is a post about guitars 🎸", required: false
42-
end
43-
when Examples::Posts::Link
44-
fieldset do
45-
postable_form.label :url, "Link URL"
46-
postable_form.text_field :url, placeholder: "https://example.com", required: false
47-
end
48-
when Examples::Posts::Image
49-
fieldset do
50-
postable_form.label :url, "Image URL"
51-
postable_form.text_field :url, placeholder: "https://example.com/image.jpg", required: true
52-
end
40+
form.fields_for :markdown, @markdown do |postable_form|
41+
fieldset(class: class_names(hidden: !markdown?)) do
42+
postable_form.label :body, "Text (Markdown)"
43+
postable_form.text_area :body, placeholder: "# This is a post about guitars 🎸", required: false
44+
end
45+
end
46+
form.fields_for :link, @link do |postable_form|
47+
fieldset(class: class_names(hidden: !link?)) do
48+
postable_form.label :url, "Link URL"
49+
postable_form.text_field :url, placeholder: "https://example.com", required: false
50+
end
51+
end
52+
form.fields_for :image, @image do |postable_form|
53+
fieldset(class: class_names(hidden: !image?)) do
54+
postable_form.label :url, "Image URL"
55+
postable_form.text_field :url, placeholder: "https://example.com/image.jpg", required: false
5356
end
5457
end
5558

59+
fieldset do
60+
form.submit "Refresh",
61+
class: "button primary",
62+
formaction: examples_posts_path,
63+
formmethod: "get",
64+
formnovalidate: true,
65+
data: {form_refresh_target: "refreshButton"}
66+
end
67+
5668
fieldset do
5769
form.submit "Save", class: "button primary"
5870
end
5971
end
6072
end
6173
end
6274
end
75+
76+
private
77+
78+
def markdown? = @post.postable == @markdown
79+
80+
def link? = @post.postable == @link
81+
82+
def image? = @post.postable == @image
6383
end

spec/system/examples/posts_spec.rb

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,65 @@
11
require "rails_helper"
22

33
RSpec.describe "Examples: Posts", type: :system do
4-
it "renders the form" do
4+
it "renders the form inputs dynamically and retains previously entered values" do
55
visit examples_posts_path
66

77
within "form" do
88
expect(page).to have_content("Title")
9-
expect(page).to have_content("Markdown")
9+
expect(page).to have_content("Text (Markdown)")
10+
11+
expect(page).not_to have_content("Link URL")
12+
13+
fill_in "Title", with: "My Post 🎸"
14+
fill_in "Text (Markdown)", with: "# This is a post about guitars 🎸"
1015
end
1116

12-
click_link "Link"
17+
choose "Link"
1318

1419
within "form" do
1520
expect(page).to have_content("Title")
1621
expect(page).to have_content("Link URL")
1722

1823
expect(page).not_to have_content("Markdown")
24+
25+
fill_in "Link URL", with: "https://example.com/link"
1926
end
2027

21-
click_link "Image"
28+
choose "Image"
2229

2330
within "form" do
2431
expect(page).to have_content("Title")
2532
expect(page).to have_content("Image URL")
2633

2734
expect(page).not_to have_content("Link URL")
35+
36+
fill_in "Image URL", with: "https://example.com/image.jpg"
37+
end
38+
39+
choose "Text"
40+
41+
within "form" do
42+
expect(page).to have_content("Title")
43+
expect(page).to have_content("Markdown")
44+
45+
expect(page).not_to have_content("Image URL")
46+
47+
expect(page).to have_field("Title", with: "My Post 🎸")
48+
expect(page).to have_field("Text (Markdown)", with: "# This is a post about guitars 🎸")
49+
end
50+
51+
choose "Link"
52+
53+
within "form" do
54+
expect(page).to have_field("Title", with: "My Post 🎸")
55+
expect(page).to have_field("Link URL", with: "https://example.com/link")
56+
end
57+
58+
choose "Image"
59+
60+
within "form" do
61+
expect(page).to have_field("Title", with: "My Post 🎸")
62+
expect(page).to have_field("Image URL", with: "https://example.com/image.jpg")
2863
end
2964
end
3065
end

0 commit comments

Comments
 (0)