20190501 rethinking the view layer with components(1)

振国

2019/05/23 发布于 技术 分类

rails  railsconf 

文字内容
1. Rethinking the View Layer with Components
2. Joel Hawksley hawksley.org
5. Creativity
6. Imagine
7. Something
8. Nothing
9. New ideas
10. Combining
11. Changing
12. Reapplying
13. Existing Ideas
16. Testing
17. Code Coverage
18. Data Flow
19. Standards
20. ActionView::Component
21. Testing
22. Code Coverage
23. Data Flow
24. Standards
25. >200x
27. Views
28. Data → HTML
29. 2004 ERB 1.0 2005 Rails 1.0 2012 Turbolinks 2016 API Mode
31. Rails is not only a great choice when you
32. want to build a full-stack application that uses
33. server-side rendering of HTML templates
34. but also a great companion for the
35. new crop of client-side JavaScript or native applications
36. just needs the backend to speak JSON.
37. 2004 ERB 1.0 2005 Rails 1.0 2012 Turbolinks 2016 API Mode
39. ERB
40. Progressive Enhancement
41. Why?
42. Performance
43. Browser Support
44. <% if supported_browser? %> <%= javascript_bundle 'polyfills' if compatibility_browser? %> <%= javascript_bundle 'frameworks' %> <%= javascript_bundle 'github', async: true %> <%= yield :scripts %> <%= controller_javascript_bundles %> <% else %> <%= javascript_bundle 'unsupported' %> <% end %>
45. Progressive Enhancement
53. class Issue < ApplicationRecord belongs_to :pull_request end class PullRequest < ApplicationRecord has_one :issue, inverse_of: :pull_request end
54. <% if pull_request && pull_request.merged? %> <div class="State State--purple"> <%= octicon('git-merge') %> Merged </div> <% elsif pull_request && pull_request.closed? %> <div class="State State--red"> <%= octicon('git-pull-request') %> Closed </div> <% elsif pull_request && pull_request.draft? %> <div class="State"> <%= octicon('git-pull-request') %> Draft </div> <% elsif pull_request %> <div class="State State--green"> <%= octicon('git-pull-request') %> Open </div> <% elsif issue && issue.closed? %> <div class="State State--red"> <%= octicon('issue-closed') %> Closed </div> <% elsif issue %> <div class="State State--green"> <%= octicon('issue-opened') %> Open </div> <% end %>
58. Rails @ GitHub
59. 209
60. 556
61. 3718
63. Testing
64. 6s
67. What trips you up with Rails views?
68. Data Flow
69. N+1
70. <% if pull_request && pull_request.merged? %> <div class="State State--purple"> <%= octicon('git-merge') %> Merged </div> <% elsif pull_request && pull_request.closed? %> <div class="State State--red"> <%= octicon('git-pull-request') %> Closed </div> <% elsif pull_request && pull_request.draft? %> <div class="State"> <%= octicon('git-pull-request') %> Draft </div> <% elsif pull_request %> <div class="State State--green"> <%= octicon('git-pull-request') %> Open </div> <% elsif issue && issue.closed? %> <div class="State State--red"> <%= octicon('issue-closed') %> Closed </div> <% elsif issue %> <div class="State State--green"> <%= octicon('issue-opened') %> Open </div> <% end %>
71. Unit Testing
72. Partials
73. Code Coverage
74. SimpleCov Coveralls
75. Implicit Arguments
76. <% if pull_request && pull_request.merged? %> <div class="State State--purple"> <%= octicon('git-merge') %> Merged </div> <% elsif pull_request && pull_request.closed? %> <div class="State State--red"> <%= octicon('git-pull-request') %> Closed </div> <% elsif pull_request && pull_request.draft? %> <div class="State"> <%= octicon('git-pull-request') %> Draft </div> <% elsif pull_request %> <div class="State State--green"> <%= octicon('git-pull-request') %> Open </div> <% elsif issue && issue.closed? %> <div class="State State--red"> <%= octicon('issue-closed') %> Closed </div> <% elsif issue %> <div class="State State--green"> <%= octicon('issue-opened') %> Open </div> <% end %>
77. Standards
82. Standards
83. Testing
84. Code Coverage
85. Data Flow
86. Implicit Arguments
87. Standards
88. MVC
89. MvC
91. Components
92. class Greeting extends React.Component { render() { return <div>Hello, {this.props.name}!</div>; } } React.render(<Greeting name="World" />, document.getElementById('example'));
93. class IssueBadge extends React.Component { render() { return ( <div className={ "State " + this._stateClass() }> <i className={this._icon()} /> {this._label()} </div> ) } _icon() { ... } _stateClass() { ... } _label() { ... } }
94. Types
95. IssueBadge.propTypes = { issue: PropTypes.exact({ isClosed: PropTypes.bool.isRequired }).isRequired, pullRequest: PropTypes.exact({ isClosed: PropTypes.bool.isRequired, isMerged: PropTypes.bool.isRequired, isDraft: PropTypes.bool.isRequired }), };
96. class IssueBadge extends React.Component { render() { return ( <div className={ "State " + this._stateClass() }> <i className={this._icon()} /> {this._label()} </div> ) } _icon() { return this.props.issue.isClosed ... } _stateClass() { ... } _label() { ... } }
97. Data Flow
98. Values > Objects
99. Testing
100. it('should render the closed issue badge', function() { expect(shallow(<IssueBadge props={{ issue: { isClosed: true }}} />). contains(<div className="State State--red">Closed</div>)).toBe(true); });
102. Components
103. Types
104. Data flow
105. Testing
109. Tests
110. <% if pull_request && pull_request.merged? %> <div class="State State--purple"> <%= octicon('git-merge') %> Merged </div> <% elsif pull_request && pull_request.closed? %> <div class="State State--red"> <%= octicon('git-pull-request') %> Closed </div> <% elsif pull_request && pull_request.draft? %> <div class="State"> <%= octicon('git-pull-request') %> Draft </div> <% elsif pull_request %> <div class="State State--green"> <%= octicon('git-pull-request') %> Open </div> <% elsif issue && issue.closed? %> <div class="State State--red"> <%= octicon('issue-closed') %> Closed </div> <% elsif issue %> <div class="State State--green"> <%= octicon('issue-opened') %> Open </div> <% end %>
111. it "renders the open issue badge" do create(:issue, :open) get :index assert_select(".State.State--green") assert_select(".octicon-issue-opened") assert_includes(response.body, "Open") end it it it it it it "renders "renders "renders "renders "renders "renders the the the the the the closed issue badge" open pull request badge" closed pull request badge" merged pull request badge" draft pull request badge" closed pull request badge for a closed draft pull request"
113. 7 examples, 7 failures
115. # app/components/issues/badge.rb module Issues class Badge end end
116. API
117. <%= render Issues::Badge, issue: issue, pull_request: issue.pull_request %>
118. module Issues class Badge def html <<-erb <div class="State State--green"> #{octicon('issue-opened')} Open </div> erb end end end
119. 'Issues::Badge' is not an ActiveModel-compatible object.
120. class ActionView::Base module RenderMonkeyPatch def render(component, *_args) return super unless component == Issues::Badge component.new.html end end prepend RenderMonkeyPatch end
121. undefined method 'octicon'
123. module Issues class Badge def html <<-erb <div class="State State--green"> #{octicon('issue-opened')} Open </div> erb end end end
124. module Issues class Badge include OcticonsHelper def html <<-erb <div class="State State--green"> #{octicon('issue-opened')} Open </div> erb end end end
125. Expected element matching ".State.State--green", found 0
126. &lt;div class=&quot;State State--green&quot;...
127. module Issues class Badge include OcticonsHelper def html eval( "output_buffer = ActionView::OutputBuffer.new;" + ActionView::Template::Handlers::ERB.erb_implementation.new(template, trim: true).src ) end def template <<-erb <div class="State State--green"> #{octicon('issue-opened')} Open </div> erb end end end
129. it "renders the closed issue badge" do create(:issue, :closed) get :index assert_select(".State.State--red") assert_select(".octicon-issue-closed") assert_includes(response.body, "Closed") end
130. Expected element matching ".State.State--red", found 0
131. <%= render Issues::Badge, issue: issue, pull_request: issue.pull_request %>
132. class ActionView::Base module RenderMonkeyPatch def render(component, *args) return super unless component == Issues::Badge component.new.html end end prepend RenderMonkeyPatch end
133. class ActionView::Base module RenderMonkeyPatch def render(component, *args) return super unless component == Issues::Badge component.new(*args).html end end prepend RenderMonkeyPatch end
134. module Issues class Badge include OcticonsHelper def initialize(issue:, pull_request: nil) @issue = issue, @pull_request = pull_request end def html; end def template; end end end
135. module Issues class Badge include OcticonsHelper def initialize; end def html; end def template <<-erb <% if @issue.closed? %> <div class="State State--red"> <%= octicon('issue-closed') %> Closed </div> <% else %> <div class="State State--green"> <%= octicon('issue-opened') %> Open </div> <% end %> erb end end end
137. Implicit Arguments
139. def template <<-erb <% if @pull_request && @pull_request.merged? %> <div class="State State--purple"> <%= octicon('git-merge') %> Merged </div> <% elsif @pull_request && @pull_request.closed? %> <div class="State State--red"> <%= octicon('git-pull-request') %> Closed </div> <% elsif @pull_request && @pull_request.draft? %> <div class="State"> <%= octicon('git-pull-request') %> Draft </div> <% elsif @pull_request %> <div class="State State--green"> <%= octicon('git-pull-request') %> Open </div> <% elsif @issue.closed? %> <div class="State State--red"> <%= octicon('issue-closed') %> Closed </div> <% else %> <div class="State State--green"> <%= octicon('issue-opened') %> Open </div> <% end %> erb end
141. <% if @pull_request && @pull_request.merged? %> <div class="State State--purple"> <%= octicon('git-merge') %> Merged </div> <% elsif @pull_request && @pull_request.closed? %> <div class="State State--red"> <%= octicon('git-pull-request') %> Closed </div> <% elsif @pull_request && @pull_request.draft? %> <div class="State"> <%= octicon('git-pull-request') %> Draft </div> <% elsif @pull_request %> <div class="State State--green"> <%= octicon('git-pull-request') %> Open </div> <% elsif @issue.closed? %> <div class="State State--red"> <%= octicon('issue-closed') %> Closed </div> <% else %> <div class="State State--green"> <%= octicon('issue-opened') %> Open </div> <% end %>
142. <%= render Issues::Badge, issue: issue, pull_request: issue.pull_request %>
143. <% if issue.pull_request %> <%= render PullRequests::Badge, pull_request: issue.pull_request %> <% else %> <%= render Issues::Badge, issue: issue %> <% end %>
144. class ActionView::Base module RenderMonkeyPatch def render(component, *args) return super unless component == Issues::Badge component.new(*args).html end end prepend RenderMonkeyPatch end
145. class ActionView::Base module RenderMonkeyPatch def render(component, *args) return super unless [Issues::Badge, PullRequests::Badge].include?(component) component.new(*args).html end end prepend RenderMonkeyPatch end
148. <% if @issue.closed? %> <div class="State State--red"> <%= octicon('issue-closed') %> Closed </div> <% else %> <div class="State State--green"> <%= octicon('issue-opened') %> Open </div> <% end %>
149. <% if @pull_request && @pull_request.merged? %> <div class="State State--purple"> <%= octicon('git-merge') %> Merged </div> <% elsif @pull_request && @pull_request.closed? %> <div class="State State--red"> <%= octicon('git-pull-request') %> Closed </div> <% elsif @pull_request && @pull_request.draft? %> <div class="State"> <%= octicon('git-pull-request') %> Draft </div> <% else %> <div class="State State--green"> <%= octicon('git-pull-request') %> Open </div> <% end %>
150. <div class="State State--green"> <%= octicon('git-pull-request') %> Open </div>
151. <%= render Primer::State, color: :green do %> <%= octicon('git-pull-request') %> Open <% end %>
152. module Primer class State end end
153. class ActionView::Base module RenderMonkeyPatch def render(component, *args) return super unless [Issues::Badge, PullRequests::Badge].include?(component) component.new(*args).html end end prepend RenderMonkeyPatch end
154. class ActionView::Base module RenderMonkeyPatch def render(component, *args) return super unless [Issues::Badge, PullRequests::Badge, Primer::State].include?(component) component.new(*args).html end end prepend RenderMonkeyPatch end
155. module ActionView class Component < ActionView::Base end end
156. module Issues class Badge < ActionView::Component end end module PullRequests class Badge < ActionView::Component end end module Primer class State < ActionView::Component end end
157. class ActionView::Base module RenderMonkeyPatch def render(component, *args) return super unless [Issues::Badge, PullRequests::Badge, Primer::State].include?(component) component.new(*args).html end end prepend RenderMonkeyPatch end
158. class ActionView::Base module RenderMonkeyPatch def render(component, *args) return super unless component < ActionView::Component component.new(*args).html end end prepend RenderMonkeyPatch end
159. module Issues class Badge def html eval( "output_buffer = ActionView::OutputBuffer.new; " + ActionView::Template::Handlers::ERB.erb_implementation.new(template, trim: true).src ) end end end
160. module ActionView class Component < ActionView::Base def html eval( "output_buffer = ActionView::OutputBuffer.new; " + ActionView::Template::Handlers::ERB.erb_implementation.new(template, trim: true).src ) end end end
162. <%= render Primer::State, color: :green do %> <%= octicon('git-pull-request') %> Open <% end %>
164. it('should render the closed issue badge', function() { expect(shallow(<IssueBadge props={{ issue: { isClosed: true }}} />). contains(<div className="State State--red">Closed</div>)).toBe(true); });
165. it "renders content passed to it as a block" do result = render_string("<%= render Primer::State do %>content<% end %>") assert_includes result.css(".State.State--green").text, "content" end
166. def render_string(string) html = ApplicationController.new.view_context.render(inline: string) Nokogiri::HTML(html) end
167. no implicit conversion of Class into Hash
168. def render_string(string) html = ApplicationController.new.view_context.render(inline: string) Nokogiri::HTML(html) end
169. class ActionView::Base module RenderMonkeyPatch def render(component, *args) return super unless component < ActionView::Component component.new(*args).html end end prepend RenderMonkeyPatch end
170. class ActionView::Base module RenderMonkeyPatch def render(component, *args) return super unless component.is_a?(Class) && component < ActionView::Component component.new(*args).html end end prepend RenderMonkeyPatch end
171. Expected " " to include "content".
172. <%= render Issues::Badge, color: :green do %> <%= octicon('issue-opened') %> Open <% end %>
173. class ActionView::Base module RenderMonkeyPatch def render(component, *args) return super unless component.is_a?(Class) && component < ActionView::Component component.new(*args).html end end prepend RenderMonkeyPatch end
174. class ActionView::Base module RenderMonkeyPatch def render(component, *args, &block) return super unless component.is_a?(Class) && component < ActionView::Component component.new(*args).html end end prepend RenderMonkeyPatch end
175. class ActionView::Base module RenderMonkeyPatch def render(component, *args, &block) return super unless component.is_a?(Class) && component < ActionView::Component component.new(*args).html end end prepend RenderMonkeyPatch end
176. class ActionView::Base module RenderMonkeyPatch def render(component, *args, &block) return super unless component.is_a?(Class) && component < ActionView::Component instance = component.new(*args) instance.content = self.capture(&block) if block_given? instance.html end end prepend RenderMonkeyPatch end
177. module ActionView class Component < ActionView::Base def html eval( "output_buffer = ActionView::OutputBuffer.new; " + ActionView::Template::Handlers::ERB.erb_implementation.new(template, trim: true).src ) end end end
178. module ActionView class Component < ActionView::Base attr_accessor :content def html eval( "output_buffer = ActionView::OutputBuffer.new; " + ActionView::Template::Handlers::ERB.erb_implementation.new(template, trim: true).src ) end end end
179. module Primer class State < ActionView::Component def template <<-erb <div class="State State--green"> </div> erb end end end
180. module Primer class State < ActionView::Component def template <<-erb <div class="State State--green"> <%= content %> </div> erb end end end
182. module Primer class State < ActionView::Component def template <<-erb <div class="State State--green"> <%= content %> </div> erb end end end
183. module Primer class State < ActionView::Component def initialize(color:) @color = color end def template <<-erb <div class="State State--green"> <%= content %> </div> erb end end end
186. module Primer class State < ActionView::Component def initialize(color:) @color = color end def template <<-erb <div class="State State--green"> <%= content %> </div> erb end end end
187. module Primer class State < ActionView::Component COLOR_CLASS_MAPPINGS = { default: "", green: "State--green", red: "State--red", purple: "State--purple", }.freeze def initialize; end def template; end end end
188. it "raises an error when color is not one of valid values" do exception = assert_raises ActionView::Template::Error do render_string("<%= render Primer::State, color: :chartreuse do %>foo<% end %>") end assert_includes exception.message, "Color is not included in the list" end
189. ActionView::Template::Error expected but nothing was raised.
191. ActiveModel::Validation
192. module Primer class State < ActionView::Component COLOR_CLASS_MAPPINGS = { default: "", green: "State--green", red: "State--red", purple: "State--purple", }.freeze def initialize; end def template; end end end
193. module Primer class State < ActionView::Component COLOR_CLASS_MAPPINGS = { default: "", green: "State--green", red: "State--red", purple: "State--purple", }.freeze validates :color, inclusion: { in: COLOR_CLASS_MAPPINGS.keys } def initialize; end def template; end end end
194. module Primer class State < ActionView::Component COLOR_CLASS_MAPPINGS = { default: "", green: "State--green", red: "State--red", purple: "State--purple", }.freeze attr_reader :color validates :color, inclusion: { in: COLOR_CLASS_MAPPINGS.keys } def initialize; end def template; end end end
195. module ActionView class Component < ActionView::Base attr_accessor :content def html eval( "output_buffer = ActionView::OutputBuffer.new; " + ActionView::Template::Handlers::ERB.erb_implementation.new(template, trim: true).src ) end end end
196. module ActionView class Component < ActionView::Base include ActiveModel::Validations attr_accessor :content def html eval( "output_buffer = ActionView::OutputBuffer.new; " + ActionView::Template::Handlers::ERB.erb_implementation.new(template, trim: true).src ) end end end
197. class ActionView::Base module RenderMonkeyPatch def render(component, *args, &block) return super unless component.is_a?(Class) && component < ActionView::Component instance = component.new(*args) instance.content = self.capture(&block) if block_given? instance.render end end prepend RenderMonkeyPatch end
198. class ActionView::Base module RenderMonkeyPatch def render(component, *args, &block) return super unless component.is_a?(Class) && component < ActionView::Component instance = component.new(*args) instance.content = self.capture(&block) if block_given? instance.validate! instance.html end end prepend RenderMonkeyPatch end
200. it "assigns the correct CSS class for color" do result = render_string("<%= render Primer::State, color: :purple do %>content<% end %>") assert result.css(".State.State--purple").any? end
201. Expected false to be truthy.
202. module Primer class State < ActionView::Component COLOR_CLASS_MAPPINGS = { default: "", green: "State--green", red: "State--red", purple: "State--purple", }.freeze attr_reader :color validates :color, inclusion: { in: COLOR_CLASS_MAPPINGS.keys } def template <<-erb <div class="State State--green"> <%= content %> </div> erb end end end
203. module Primer class State < ActionView::Component COLOR_CLASS_MAPPINGS = { default: "", green: "State--green", red: "State--red", purple: "State--purple", }.freeze attr_reader :color validates :color, inclusion: { in: COLOR_CLASS_MAPPINGS.keys } def template <<-erb <div class="State State--green"> <%= content %> </div> erb end def class_name COLOR_CLASS_MAPPINGS[color] end end end
204. module Primer class State < ActionView::Component COLOR_CLASS_MAPPINGS = { default: "", green: "State--green", red: "State--red", purple: "State--purple", }.freeze attr_reader :color validates :color, inclusion: { in: COLOR_CLASS_MAPPINGS.keys } def template <<-erb <div class="State <%= class_name %>"> <%= content %> </div> erb end def class_name COLOR_CLASS_MAPPINGS[color] end end end
208. it "raises an error when title is not present" do exception = assert_raises ActionView::Template::Error do render_string("<%= render Primer::State, title: '' do %>foo<% end %>") end assert_includes exception.message, "Title can't be blank" end
209. Expected false to be truthy.
210. module Primer class State < ActionView::Component attr_reader :title validates :title, presence: true end end
212. missing keyword: title
213. module Issues class Badge < ActionView::Component def template <<-erb <% if @issue.closed? %> <%= render Primer::State, color: <%= octicon('issue-closed') %> <% end %> <% else %> <%= render Primer::State, color: <%= octicon('issue-opened') %> <% end %> <% end %> erb end end end :red do %> Closed :green do %> Open
214. module Issues class Badge < ActionView::Component def template <<-erb <% if @issue.closed? %> <%= render Primer::State, color: <%= octicon('issue-closed') %> <% end %> <% else %> <%= render Primer::State, color: <%= octicon('issue-opened') %> <% end %> <% end %> erb end end end :red, title: "Status: Closed" do %> Closed :green, title: "Status: Open" do %> Open
216. Data Flow
217. N+1
218. module Issues class Badge < ActionView::Component include OcticonsHelper def initialize(issue:) @issue = issue end def template <<-erb <% if @issue.closed? %> <%= render Primer::State, color: <%= octicon('issue-closed') %> <% end %> <% else %> <%= render Primer::State, color: <%= octicon('issue-opened') %> <% end %> <% end %> erb end end end :red, title: "Status: Closed" do %> Closed :green, title: "Status: Open" do %> Open
219. class Issue < ApplicationRecord def closed? state == "closed" end end
220. module Issues class Badge < ActionView::Component include OcticonsHelper def initialize(issue:) @issue = issue end def template <<-erb <% if @issue.closed? %> <%= render Primer::State, color: <%= octicon('issue-closed') %> <% end %> <% else %> <%= render Primer::State, color: <%= octicon('issue-opened') %> <% end %> <% end %> erb end end end :red, title: "Status: Closed" do %> Closed :green, title: "Status: Open" do %> Open
221. module Issues class Badge < ActionView::Component include OcticonsHelper def initialize(state:) @state = state end def template <<-erb <% if @issue.closed? %> <%= render Primer::State, color: <%= octicon('issue-closed') %> <% end %> <% else %> <%= render Primer::State, color: <%= octicon('issue-opened') %> <% end %> <% end %> erb end end end :red, title: "Status: Closed" do %> Closed :green, title: "Status: Open" do %> Open
222. module Issues class Badge < ActionView::Component include OcticonsHelper attr_reader :state validates :state, inclusion: { in: [:open, :closed] } def initialize(state:) @state = state end def template <<-erb <% if @issue.closed? %> <%= render Primer::State, color: <%= octicon('issue-closed') %> <% end %> <% else %> <%= render Primer::State, color: <%= octicon('issue-opened') %> <% end %> <% end %> erb end end end :red, title: "Status: Closed" do %> Closed :green, title: "Status: Open" do %> Open
223. module Issues class Badge < ActionView::Component def template <<-erb <% if @issue.closed? %> <%= render Primer::State, color: <%= octicon('issue-closed') %> <% end %> <% else %> <%= render Primer::State, color: <%= octicon('issue-opened') %> <% end %> <% end %> erb end end end :red, title: "Status: Closed" do %> Closed :green, title: "Status: Open" do %> Open
224. module Issues class Badge < ActionView::Component include OcticonsHelper STATES = { open: { color: :green, octicon_name: "issue-opened", label: "Open" }, closed: { color: :red, octicon_name: "issue-closed", label: "Closed" } }.freeze attr_reader :state validates :state, inclusion: { in: STATES.keys } def initialize; end def template; end end end
225. module Issues class Badge < ActionView::Component def template <<-erb <% if @issue.closed? %> <%= render Primer::State, color: <%= octicon('issue-closed') %> <% end %> <% else %> <%= render Primer::State, color: <%= octicon('issue-opened') %> <% end %> <% end %> erb end end end :red, title: "Status: Closed" do %> Closed :green, title: "Status: Open" do %> Open
226. module Issues class Badge < ActionView::Component def template <<-erb <%= render Primer::State, color: color, title: "Status: #{label}" do %> <%= octicon(octicon_name) %> <%= label %> <% end %> erb end def color STATES[state][:color] end def octicon_name STATES[state][:octicon_name] end def label STATES[state][:label] end end end
229. module PullRequests class Badge < ActionView::Component def template <<-erb <% if pull_request.merged? %> <% elsif pull_request.closed? %> <% elsif pull_request.draft? %> <% else %> <% end %> erb end end end
230. class PullRequest < ApplicationRecord def state return :open if open? return :merged if merged? return :closed if closed? end # autogenerated def draft?; end end
231. it "renders the draft state" do result = render_string("<%= render PullRequests::Badge, state: :open, is_draft: true %>") assert_includes result.text, "Draft" assert result.css("[title='Status: Draft']").any? assert result.css(".octicon-git-pull-request").any? end it it it it "renders "renders "renders "renders the the the the closed draft state" merged state" closed state" open state"
232. missing keyword: pull_request
233. module PullRequests class Badge < ActionView::Component def initialize(pull_request:) @pull_request = pull_request end end end
234. module PullRequests class Badge < ActionView::Component def initialize(state:, is_draft:) @state, @is_draft = state, is_draft end end end
235. module PullRequests class Badge < ActionView::Component def template <<-erb <% if pull_request.merged? %> <%= render Primer::State, color: :purple, title: "Status: Merged" <%= octicon('git-merge') %> Merged <% end %> <% elsif pull_request.closed? %> <%= render Primer::State, color: :red, title: "Status: Closed" do <%= octicon('git-pull-request') %> Closed <% end %> <% elsif pull_request.draft? %> <%= render Primer::State, color: :default, title: "Status: Draft" <%= octicon('git-pull-request') %> Draft <% end %> <% else %> <%= render Primer::State, color: :green, title: "Status: Open" do <%= octicon('git-pull-request') %> Open <% end %> <% end %> erb end end end do %> %> do %> %>
236. module PullRequests class Badge < ActionView::Component def template <<-erb <%= render Primer::State, title: title, color: color do %> <%= octicon(octicon_name) %> <%= label %> <% end %> erb end def def def def end end title; end color; end octicon_name; end label; end
237. class IssueBadge extends React.Component { render() { return ( <div className={ "State " + this._stateClass() }> <i className={this._icon()} /> {this._label()} </div> ) } _icon() { ... } _stateClass() { ... } _label() { ... } }
240. Data Flow
241. Values > Objects
242. Code Coverage
245. Production?
246. ! Mid-March
249. Implementation
250. missing keyword: title
251. Standards
252. Reusability
254. Performance
255. 6s
256. 25ms
257. 240x
259. .25s vs. 1m
261. Creativity
262. Imagine
263. Something
264. Nothing
265. New ideas
266. Combining
267. Changing
268. Reapplying
269. Existing Ideas
272. Testing
273. Code Coverage
274. Data Flow
275. Standards
276. ActionView::Component
277. Testing
278. Code Coverage
279. Data Flow
280. Standards
281. MvC
282. MVC
283. Thanks
284. Q&A Slides & source code: hawksley.org