Views, from the top

Ef3623d04d4727965ca295a6689b4d2f?s=47 Tim Riley
February 08, 2019

Views, from the top

/Veni, vidi, view-ci/ – I came, I saw, I left the views in a mess.

With server-rendered HTML still delivering most of the web, our views deserve more than a grab bag of helpers.

Come along and learn the tools and techniques to conquer the design challenges that a complex view layer presents.

Ef3623d04d4727965ca295a6689b4d2f?s=128

Tim Riley

February 08, 2019
Tweet

Transcript

  1. Views, from the top RubyConf AU 2019

  2. @timriley

  3. @timriley @icelab

  4. @timriley @icelab dry-rb

  5. @timriley @icelab dry-rb Hanami

  6. dry-rb Hanami

  7. dry-rb Hanami +

  8. Views, from the top

  9. None
  10. None
  11. None
  12. Views, from the top

  13. 11am meeting: Views

  14. Server-rendered views

  15. Things are out of hand

  16. Controllers Helpers Templates Past and present:

  17. Controllers Helpers Templates Past and present:

  18. None
  19. None
  20. Controllers Helpers Templates

  21. Controllers Helpers Templates

  22. Controllers Helpers Templates Too many responsibilities

  23. Controllers Helpers Templates Too many responsibilities

  24. Controllers Helpers Templates Too many responsibilities Disorganised, mostly gross

  25. Controllers Helpers Templates Too many responsibilities Disorganised, mostly gross Decorators?

  26. Controllers Helpers Templates Too many responsibilities Disorganised, mostly gross Decorators?

    Friction, limited
  27. Controllers Helpers Templates Too many responsibilities Disorganised, mostly gross Decorators?

    Friction, limited
  28. Controllers Helpers Templates Too many responsibilities Disorganised, mostly gross Decorators?

    Friction, limited Cluttered, too much logic
  29. It’s too hard to write good view code

  30. Good view code

  31. Good view code

  32. Let’s build a better view system

  33. dry-view

  34. pssssst! dry-view already exists! This guy is just “acting” "

  35. dry-view requirements

  36. dry-view requirements • Views as objects

  37. class Show < Dry::View end

  38. class Show < Dry::View end view = Show.new

  39. class Show < Dry::View attr_reader :article_repo def initialize(article_repo:) @article_repo =

    article_repo end end view = Show.new(repo: article_repo)
  40. class Show < Dry::View attr_reader :article_repo def initialize(article_repo:) @article_repo =

    article_repo end end view = Show.new(article_repo: repo)
  41. dry-view requirements • Views as objects • Explicit template locals

  42. class Show < Dry::View config.template = "articles/show" expose :article do

    |slug:| article_repo.find_by_slug(slug) end end view = Show.new(article_repo: repo)
  43. class Show < Dry::View config.template = "articles/show" expose :article do

    |slug:| article_repo.find_by_slug(slug) end end view = Show.new(article_repo: repo)
  44. class Show < Dry::View config.template = "articles/show" expose :article do

    |slug:| article_repo.find_by_slug(slug) end end view = Show.new(article_repo: repo)
  45. dry-view requirements • Views as objects • Explicit template locals

    • Templates!
  46. / Template written in Slim h1 = article.title

  47. view = Show.new(…) view.call( slug: "together-breakfast", ).to_s # => "<h1>Together

    breakfast</h1>
  48. view = Show.new(…) view.call( slug: "together-breakfast", ).to_s # => "<h1>Together

    breakfast</h1>
  49. view = Show.new(…) view.call( slug: "together-breakfast", ).to_s #=> "<h1>Together breakfast</h1>"

  50. dry-view requirements • Views as objects • Explicit template locals

    • Templates!
  51. dry-view requirements • Views as objects • Explicit template locals

    • Templates!
  52. dry-view requirements • Views as objects • Explicit template locals

    • Templates! SIMPLE templates
  53. dry-view requirements • Views as objects • Explicit template locals

    • Templates! SIMPLE templates
  54. h1 = article.title == markdown(article.body)

  55. h1 = article.title == article.body_html

  56. class Parts::Article < Dry::View::Part def body_html render_markdown(body) end private def

    render_markdown(str) # … end end
  57. class Parts::Article < Dry::View::Part def body_html render_markdown(body) end private def

    render_markdown(str) # … end end
  58. dry-view requirements • Views as objects • Explicit template locals

    • Simple templates • View logic on decorated values
  59. dry-view requirements • Views as objects • Explicit template locals

    • Simple templates • View logic on decorated values • View facilities are automatic
  60. class Show < Dry::View config.template = "articles/show" config.part_namespace = Parts

    expose :article # => Parts::Article end
  61. class Show < Dry::View config.template = "articles/show" config.part_namespace = Parts

    expose :article # => Parts::Article end
  62. dry-view requirements • Views as objects • Explicit template locals

    • Simple templates • View logic on decorated values • View facilities are automatic • View facilities are integrated
  63. h1 = article.title == article.body_html .share-widget[ data-share-url=article.url data-share-title=article.title data-share-body=article.body_preview_text ]

  64. h1 = article.title == article.body_html == render(:share_widget, url: article.url, title:

    article.title, preview_text: article.body_preview_text)
  65. class Parts::Article < Dry::View::Part def share_widget render( :share_widget, url: url,

    title: title, preview_text: body_preview_text, ) end end
  66. class Parts::Article < Dry::View::Part def share_widget render( :share_widget, url: url,

    title: title, preview_text: body_preview_text ) end end
  67. h1 = article.title == article.body_html == article.share_widget

  68. dry-view requirements • Views as objects • Explicit template locals

    • Simple templates • View logic on decorated values • View facilities are automatic • View facilities are integrated • View logic on specific templates
  69. == render(:related_article, article: article)

  70. - show_author = \ defined?(show_author) ? show_author : false -

    link_prefix = \ defined?(link_prefix) ? link_prefix : "Related: " .related-article a href=article.url = "#{link_prefix} #{article.title}" - if show_author .author …
  71. - show_author = \ defined?(show_author) ? show_author : false -

    link_prefix = \ defined?(link_prefix) ? link_prefix : "Related: " .related-article a href=article.url = "#{link_prefix} #{article.title}" - if show_author .author …
  72. - show_author = \ defined?(show_author) ? show_author : false -

    link_prefix = \ defined?(link_prefix) ? link_prefix : "Related: " .related-article a href=article.url = "#{link_prefix} #{article.title}" - if show_author .author … 4c391850cabe4809a6b6ab1a155529… 16 March 2016 Totally wrote this code. FIXME!
  73. class Scopes::RelatedArticle < Dry::View::Scope def show_author? locals.fetch(:show_author, false) end def

    link_text prefix = locals.fetch(:link_prefix, "Related:") "#{prefix} #{article.title}” end end
  74. class Scopes::RelatedArticle < Dry::View::Scope def show_author? locals.fetch(:show_author, false) end def

    link_text prefix = locals.fetch(:link_prefix, "Related:") "#{prefix} #{article.title}” end end
  75. class Scopes::RelatedArticle < Dry::View::Scope def show_author? locals.fetch(:show_author, false) end def

    link_text prefix = locals.fetch(:link_prefix, "Related:") "#{prefix} #{article.title}” end end
  76. class Scopes::RelatedArticle < Dry::View::Scope def show_author? locals.fetch(:show_author, false) end def

    link_text prefix = locals.fetch(:link_prefix, "Related:") "#{prefix} #{article.title}" end end
  77. .related-article a href=article.url = link_text - if show_author? .author …

  78. == scope(:related_article, article: article)

  79. == scope(:related_article, article: article).render

  80. dry-view requirements • Views as objects • Explicit template locals

    • Simple templates • View logic on decorated values • View facilities are automatic • View facilities are integrated • View logic on specific templates
  81. dry-view requirements • Views as objects • Explicit template locals

    • Simple templates • View logic on decorated values • View facilities are automatic • View facilities are integrated • View logic on specific templates • Common helpers
  82. class Context < Dry::View::Context def initialize(assets:, **args) @assets = assets

    super(**args) end def asset_path(asset_name) @assets[asset_name] end end
  83. class Context < Dry::View::Context def initialize(assets:, **) @assets = assets

    super end def asset_path(asset_name) @assets[asset_name] end end
  84. class Context < Dry::View::Context def initialize(assets:, **) @assets = assets

    super end def asset_path(asset_name) @assets[asset_name] end end
  85. img src=asset_path("header.png") h1 = article.title == article.body_html == article.share_widget

  86. class Parts::Article < Dry::View::Part def feature_image_url url = value.feature_image_url url

    || asset_path("article.png") end end
  87. class Context < Dry::View::Context def initialize(assets:, **) @assets = assets

    super end def asset_path(asset_name) @assets[asset_name] end end
  88. dry-view requirements • Views as objects • Explicit template locals

    • Simple templates • View logic on decorated values • View facilities are automatic • View facilities are integrated • View logic on specific templates • Common helpers
  89. View concepts

  90. View concepts • View - config, dependencies, exposures

  91. View concepts • View - config, dependencies, exposures • Exposures

    - prepare values
  92. View concepts • View - config, dependencies, exposures • Exposures

    - prepare values • Template & partials - markup
  93. View concepts • View - config, dependencies, exposures • Exposures

    - prepare values • Template & partials - markup • Parts - behaviour on values
  94. View concepts • View - config, dependencies, exposures • Exposures

    - prepare values • Template & partials - markup • Parts - behaviour on values • Scopes - behaviour for templates
  95. View concepts • View - config, dependencies, exposures • Exposures

    - prepare values • Template & partials - markup • Parts - behaviour on values • Scopes - behaviour for templates • Context - baseline rendering environment
  96. What have we learnt?

  97. Views are complex

  98. Views are complex

  99. Views are complex View · Exposures Templates & partials Parts

    · Scopes · Context
  100. Minimum viable views

  101. Better view code

  102. Making the easy thing

  103. also the right thing Making the easy thing

  104. Separation of concerns

  105. Separation of concerns Encapsulation

  106. Separation of concerns Encapsulation Immutability

  107. Separation of concerns Encapsulation Immutability Testability

  108. Better OOP = Better views

  109. Better OOP = Better views

  110. Better OOP = Joyful views

  111. Better apps

  112. Better apps Better Ruby

  113. Diversity

  114. Diversity Flexibility

  115. Diversity Flexibility Innovation

  116. None
  117. None
  118. dry-view!

  119. @timriley dry-rb.org Thank you!

  120. @timriley dry-rb.org Thank you! Say hello! Learn more!