rack-component 0.4.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +0 -2
- data/.travis.yml +3 -1
- data/CHANGELOG.md +52 -0
- data/Gemfile.lock +22 -11
- data/README.md +194 -134
- data/lib/rack/component.rb +64 -109
- data/lib/rack/component/renderer.rb +31 -0
- data/lib/rack/component/version.rb +1 -1
- data/rack-component.gemspec +5 -2
- metadata +49 -5
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: f4f080e40de93c62a1a6b1aa9aabf3ee3f2a92fa10823f52c9dade5b075d440f
         | 
| 4 | 
            +
              data.tar.gz: '08d3866748d7e14320a274f67e3682b67a51448154ecdae5034c2c01ec5a3e3f'
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 6e5c02e8c994ed4fa58e2a62004d248033d796c4f7163217c8a164c127a7ccf26c01a99c3a1fd0415aecff7af2ceae07eec800b2d04ab755da77bb3cd1dd4ab5
         | 
| 7 | 
            +
              data.tar.gz: 96405e2d4e8b8f9add4d47f1129f035f00a5a9fbf00d6c363c8f056db88d43c962c1c43d367f88cf7557efccfb11386ae760a73b69cab20a795387efd0bab1f2
         | 
    
        data/.rubocop.yml
    CHANGED
    
    
    
        data/.travis.yml
    CHANGED
    
    
    
        data/CHANGELOG.md
    ADDED
    
    | @@ -0,0 +1,52 @@ | |
| 1 | 
            +
            # Changelog
         | 
| 2 | 
            +
            All notable changes to this project will be documented in this file.
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
         | 
| 5 | 
            +
            and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            ## 0.5.0
         | 
| 8 | 
            +
            ### Fixed
         | 
| 9 | 
            +
            - The `env` argument of the `render` block is now optional, as per standard Ruby
         | 
| 10 | 
            +
              block behavior.
         | 
| 11 | 
            +
              ```ruby
         | 
| 12 | 
            +
              class WorksInThisVersion < Rack::Component
         | 
| 13 | 
            +
                render do
         | 
| 14 | 
            +
                  'This component raised an ArgumentError in old versions but works now.'
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              class StillWorks < Rack::Component
         | 
| 19 | 
            +
                render do |env|
         | 
| 20 | 
            +
                  'This style still works. Using |keyword:, arguments:| in env is nice.'
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
              ```
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            ### Added
         | 
| 26 | 
            +
            - A changelog
         | 
| 27 | 
            +
            - Templating via [tilt](https://github.com/rtomayko/tilt), with support for
         | 
| 28 | 
            +
              escaping HTML by default
         | 
| 29 | 
            +
             | 
| 30 | 
            +
            ### Removed
         | 
| 31 | 
            +
            - Calling `Component.memoized(env)` is no longer supported. Use Sam Saffron's
         | 
| 32 | 
            +
              [lru_redux](https://github.com/SamSaffron/lru_redux) as an almost drop-in
         | 
| 33 | 
            +
              replacement, like this:
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                ```ruby
         | 
| 36 | 
            +
                require 'rack/component'
         | 
| 37 | 
            +
                require 'lru_redux'
         | 
| 38 | 
            +
                class MyComponent < Rack::Component
         | 
| 39 | 
            +
                  Cache = LruRedux::ThreadSafeCache.new(100)
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  render do |env|
         | 
| 42 | 
            +
                    Cache.getset(env) { 'this block will render after checking the cache' }
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
                ```
         | 
| 46 | 
            +
             | 
| 47 | 
            +
            ## 0.4.2 - 2019-01-04
         | 
| 48 | 
            +
            ### Added
         | 
| 49 | 
            +
            - `#h` method for escaping HTML inside interpolated strings
         | 
| 50 | 
            +
             | 
| 51 | 
            +
            ## 0.4.1 - 2019-01-02
         | 
| 52 | 
            +
            - First public, documented release
         | 
    
        data/Gemfile.lock
    CHANGED
    
    | @@ -21,26 +21,33 @@ GEM | |
| 21 21 | 
             
                  thread_safe (~> 0.3, >= 0.3.1)
         | 
| 22 22 | 
             
                diff-lcs (1.3)
         | 
| 23 23 | 
             
                equalizer (0.0.11)
         | 
| 24 | 
            +
                erubi (1.8.0)
         | 
| 25 | 
            +
                haml (5.0.4)
         | 
| 26 | 
            +
                  temple (>= 0.8.0)
         | 
| 27 | 
            +
                  tilt
         | 
| 24 28 | 
             
                ice_nine (0.11.2)
         | 
| 25 | 
            -
                jaro_winkler (1.5. | 
| 29 | 
            +
                jaro_winkler (1.5.2)
         | 
| 26 30 | 
             
                kwalify (0.7.2)
         | 
| 27 | 
            -
                 | 
| 31 | 
            +
                liquid (4.0.1)
         | 
| 32 | 
            +
                method_source (0.9.2)
         | 
| 28 33 | 
             
                parallel (1.12.1)
         | 
| 29 | 
            -
                parser (2.5. | 
| 34 | 
            +
                parser (2.5.3.0)
         | 
| 30 35 | 
             
                  ast (~> 2.4.0)
         | 
| 31 36 | 
             
                powerpack (0.1.2)
         | 
| 32 | 
            -
                pry (0. | 
| 37 | 
            +
                pry (0.12.2)
         | 
| 33 38 | 
             
                  coderay (~> 1.1.0)
         | 
| 34 39 | 
             
                  method_source (~> 0.9.0)
         | 
| 40 | 
            +
                psych (3.1.0)
         | 
| 35 41 | 
             
                rack (2.0.6)
         | 
| 36 42 | 
             
                rack-test (0.8.3)
         | 
| 37 43 | 
             
                  rack (>= 1.0, < 3)
         | 
| 38 44 | 
             
                rainbow (3.0.0)
         | 
| 39 45 | 
             
                rake (10.5.0)
         | 
| 40 | 
            -
                reek (5. | 
| 46 | 
            +
                reek (5.3.0)
         | 
| 41 47 | 
             
                  codeclimate-engine-rb (~> 0.4.0)
         | 
| 42 48 | 
             
                  kwalify (~> 0.7.0)
         | 
| 43 49 | 
             
                  parser (>= 2.5.0.0, < 2.6, != 2.5.1.1)
         | 
| 50 | 
            +
                  psych (~> 3.1.0)
         | 
| 44 51 | 
             
                  rainbow (>= 2.0, < 4.0)
         | 
| 45 52 | 
             
                rspec (3.8.0)
         | 
| 46 53 | 
             
                  rspec-core (~> 3.8.0)
         | 
| @@ -55,18 +62,19 @@ GEM | |
| 55 62 | 
             
                  diff-lcs (>= 1.2.0, < 2.0)
         | 
| 56 63 | 
             
                  rspec-support (~> 3.8.0)
         | 
| 57 64 | 
             
                rspec-support (3.8.0)
         | 
| 58 | 
            -
                rubocop (0. | 
| 65 | 
            +
                rubocop (0.62.0)
         | 
| 59 66 | 
             
                  jaro_winkler (~> 1.5.1)
         | 
| 60 67 | 
             
                  parallel (~> 1.10)
         | 
| 61 68 | 
             
                  parser (>= 2.5, != 2.5.1.1)
         | 
| 62 69 | 
             
                  powerpack (~> 0.1)
         | 
| 63 70 | 
             
                  rainbow (>= 2.2.2, < 4.0)
         | 
| 64 71 | 
             
                  ruby-progressbar (~> 1.7)
         | 
| 65 | 
            -
                  unicode-display_width (~> 1. | 
| 72 | 
            +
                  unicode-display_width (~> 1.4.0)
         | 
| 66 73 | 
             
                ruby-progressbar (1.10.0)
         | 
| 74 | 
            +
                temple (0.8.0)
         | 
| 67 75 | 
             
                thread_safe (0.3.6)
         | 
| 68 | 
            -
                tilt (2.0. | 
| 69 | 
            -
                unicode-display_width (1.4. | 
| 76 | 
            +
                tilt (2.0.9)
         | 
| 77 | 
            +
                unicode-display_width (1.4.1)
         | 
| 70 78 | 
             
                virtus (1.0.5)
         | 
| 71 79 | 
             
                  axiom-types (~> 0.1)
         | 
| 72 80 | 
             
                  coercible (~> 1.0)
         | 
| @@ -79,7 +87,10 @@ PLATFORMS | |
| 79 87 |  | 
| 80 88 | 
             
            DEPENDENCIES
         | 
| 81 89 | 
             
              benchmark-ips (~> 2.7)
         | 
| 82 | 
            -
              bundler (~>  | 
| 90 | 
            +
              bundler (~> 2)
         | 
| 91 | 
            +
              erubi (~> 1.8)
         | 
| 92 | 
            +
              haml (~> 5)
         | 
| 93 | 
            +
              liquid (~> 4)
         | 
| 83 94 | 
             
              pry (~> 0.11)
         | 
| 84 95 | 
             
              rack (~> 2.0.6)
         | 
| 85 96 | 
             
              rack-component!
         | 
| @@ -92,4 +103,4 @@ DEPENDENCIES | |
| 92 103 | 
             
              yard (~> 0.9)
         | 
| 93 104 |  | 
| 94 105 | 
             
            BUNDLED WITH
         | 
| 95 | 
            -
                | 
| 106 | 
            +
               2.0.1
         | 
    
        data/README.md
    CHANGED
    
    | @@ -13,6 +13,7 @@ gem 'rack-component' | |
| 13 13 | 
             
            ```
         | 
| 14 14 |  | 
| 15 15 | 
             
            ## Quickstart with Sinatra
         | 
| 16 | 
            +
             | 
| 16 17 | 
             
            ```ruby
         | 
| 17 18 | 
             
            # config.ru
         | 
| 18 19 | 
             
            require 'sinatra'
         | 
| @@ -20,7 +21,7 @@ require 'rack/component' | |
| 20 21 |  | 
| 21 22 | 
             
            class Hello < Rack::Component
         | 
| 22 23 | 
             
              render do |env|
         | 
| 23 | 
            -
                "<h1>Hello, #{h | 
| 24 | 
            +
                "<h1>Hello, #{h env[:name]}</h1>"
         | 
| 24 25 | 
             
              end
         | 
| 25 26 | 
             
            end
         | 
| 26 27 |  | 
| @@ -31,27 +32,25 @@ end | |
| 31 32 | 
             
            run Sinatra::Application
         | 
| 32 33 | 
             
            ```
         | 
| 33 34 |  | 
| 34 | 
            -
            **Note that Rack::Component  | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 38 | 
            -
            to discuss how to enable escaping by default. If you have ideas or opinions, I'd
         | 
| 39 | 
            -
            love to hear about them there.
         | 
| 35 | 
            +
            **Note that Rack::Component does not escape strings by default**. To escape
         | 
| 36 | 
            +
            strings, you can either use the `#h` helper like in the example above, or you
         | 
| 37 | 
            +
            can configure your components to render a template that escapes automatically.
         | 
| 38 | 
            +
            See the [Recipes](#recipes) section for details.
         | 
| 40 39 |  | 
| 41 40 | 
             
            ## Table of Contents
         | 
| 42 41 |  | 
| 43 42 | 
             
            * [Getting Started](#getting-started)
         | 
| 44 43 | 
             
              * [Components as plain functions](#components-as-plain-functions)
         | 
| 45 44 | 
             
              * [Components as Rack::Components](#components-as-rackcomponents)
         | 
| 46 | 
            -
             | 
| 45 | 
            +
                * [Components if you hate inheritance](#components-if-you-hate-inheritance)
         | 
| 47 46 | 
             
            * [Recipes](#recipes)
         | 
| 48 47 | 
             
              * [Render one component inside another](#render-one-component-inside-another)
         | 
| 49 | 
            -
              * [ | 
| 50 | 
            -
              * [Memoize an expensive component until its content changes](#memoize-an-expensive-component-until-its-content-changes)
         | 
| 48 | 
            +
              * [Render a template that escapes output by default via Tilt](#render-a-template-that-escapes-output-by-default-via-tilt)
         | 
| 51 49 | 
             
              * [Render an HTML list from an array](#render-an-html-list-from-an-array)
         | 
| 52 50 | 
             
              * [Render a Rack::Component from a Rails controller](#render-a-rackcomponent-from-a-rails-controller)
         | 
| 53 51 | 
             
              * [Mount a Rack::Component as a Rack app](#mount-a-rackcomponent-as-a-rack-app)
         | 
| 54 52 | 
             
              * [Build an entire App out of Rack::Components](#build-an-entire-app-out-of-rackcomponents)
         | 
| 53 | 
            +
              * [Define `#render` at the instance level instead of via `render do`](#define-render-at-the-instance-level-instead-of-via-render-do)
         | 
| 55 54 | 
             
            * [API Reference](#api-reference)
         | 
| 56 55 | 
             
            * [Performance](#performance)
         | 
| 57 56 | 
             
            * [Compatibility](#compatibility)
         | 
| @@ -77,57 +76,38 @@ Greeter.call(name: 'Mina') #=> '<h1>Hi, Mina.</h1>' | |
| 77 76 |  | 
| 78 77 | 
             
            ### Components as Rack::Components
         | 
| 79 78 |  | 
| 80 | 
            -
             | 
| 81 | 
            -
            state:
         | 
| 79 | 
            +
            Upgrade your lambda to a `Rack::Component` when it needs HTML escaping, instance
         | 
| 80 | 
            +
            methods, or state:
         | 
| 82 81 |  | 
| 83 82 | 
             
            ```ruby
         | 
| 84 83 | 
             
            require 'rack/component'
         | 
| 85 84 | 
             
            class FormalGreeter < Rack::Component
         | 
| 86 85 | 
             
              render do |env|
         | 
| 87 | 
            -
                "<h1>Hi, #{title} #{env[:name]}.</h1>"
         | 
| 86 | 
            +
                "<h1>Hi, #{h title} #{h env[:name]}.</h1>"
         | 
| 88 87 | 
             
              end
         | 
| 89 88 |  | 
| 89 | 
            +
              # +env+ is available in instance methods too
         | 
| 90 90 | 
             
              def title
         | 
| 91 | 
            -
                 | 
| 92 | 
            -
                env[:title] || "President"
         | 
| 91 | 
            +
                env[:title] || "Queen"
         | 
| 93 92 | 
             
              end
         | 
| 94 93 | 
             
            end
         | 
| 95 94 |  | 
| 96 | 
            -
            FormalGreeter.call(name: ' | 
| 97 | 
            -
            FormalGreeter.call( | 
| 95 | 
            +
            FormalGreeter.call(name: 'Franklin') #=> "<h1>Hi, Queen Franklin.</h1>"
         | 
| 96 | 
            +
            FormalGreeter.call(
         | 
| 97 | 
            +
              title: 'Captain',
         | 
| 98 | 
            +
              name: 'Kirk <kirk@starfleet.gov>'
         | 
| 99 | 
            +
            ) #=> <h1>Hi, Captain Kirk <kirk@starfleet.gov>.</h1>
         | 
| 98 100 | 
             
            ```
         | 
| 99 101 |  | 
| 100 | 
            -
             | 
| 102 | 
            +
            #### Components if you hate inheritance
         | 
| 101 103 |  | 
| 102 | 
            -
             | 
| 104 | 
            +
            Instead of inheriting from `Rack::Component`, you can `extend` its methods:
         | 
| 103 105 |  | 
| 104 106 | 
             
            ```ruby
         | 
| 105 | 
            -
             | 
| 106 | 
            -
             | 
| 107 | 
            -
             | 
| 108 | 
            -
              render do |env|
         | 
| 109 | 
            -
                "Hi, #{get_job_title_from_api} #{env[:name]}."
         | 
| 110 | 
            -
              end
         | 
| 111 | 
            -
             | 
| 112 | 
            -
              def get_job_title_from_api
         | 
| 113 | 
            -
                endpoint = URI("http://api.heads-of-state.gov/")
         | 
| 114 | 
            -
                Net::HTTP.get("#{endpoint}?q=#{env[:name]}")
         | 
| 115 | 
            -
              end
         | 
| 107 | 
            +
            class SoloComponent
         | 
| 108 | 
            +
              extend Rack::Component::Methods
         | 
| 109 | 
            +
              render { "Family is complicated" }
         | 
| 116 110 | 
             
            end
         | 
| 117 | 
            -
             | 
| 118 | 
            -
            NetworkGreeter.memoized(name: 'Macron')
         | 
| 119 | 
            -
            # ...after a slow network call to our fictional Heads Of State API
         | 
| 120 | 
            -
            #=> "Hi, President Macron."
         | 
| 121 | 
            -
             | 
| 122 | 
            -
            NetworkGreeter.memoized(name: 'Macron') # subsequent calls with the same env are instant.
         | 
| 123 | 
            -
            #=> "Hi, President Macron."
         | 
| 124 | 
            -
             | 
| 125 | 
            -
            NetworkGreeter.memoized(name: 'Merkel')
         | 
| 126 | 
            -
            # ...this env is new, so NetworkGreeter makes another network call
         | 
| 127 | 
            -
            #=> "Hi, Chancellor Merkel."
         | 
| 128 | 
            -
             | 
| 129 | 
            -
            NetworkGreeter.memoized(name: 'Merkel') #=> instant! "Hi, Chancellor Merkel."
         | 
| 130 | 
            -
            NetworkGreeter.memoized(name: 'Macron') #=> instant! "Hi, President Macron."
         | 
| 131 111 | 
             
            ```
         | 
| 132 112 |  | 
| 133 113 | 
             
            ## Recipes
         | 
| @@ -138,7 +118,9 @@ You can nest Rack::Components as if they were [React Children][jsx children] by | |
| 138 118 | 
             
            calling them with a block.
         | 
| 139 119 |  | 
| 140 120 | 
             
            ```ruby
         | 
| 141 | 
            -
            Layout.call(title: 'Home')  | 
| 121 | 
            +
            Layout.call(title: 'Home') do
         | 
| 122 | 
            +
              Content.call
         | 
| 123 | 
            +
            end
         | 
| 142 124 | 
             
            ```
         | 
| 143 125 |  | 
| 144 126 | 
             
            Here's a more fully fleshed example:
         | 
| @@ -151,11 +133,12 @@ get '/posts/:id' do | |
| 151 133 | 
             
              PostPage.call(id: params[:id])
         | 
| 152 134 | 
             
            end
         | 
| 153 135 |  | 
| 154 | 
            -
            #  | 
| 136 | 
            +
            # Fetch a post from the database and render it inside a Layout
         | 
| 155 137 | 
             
            class PostPage < Rack::Component
         | 
| 156 138 | 
             
              render do |env|
         | 
| 157 | 
            -
                post = Post.find | 
| 158 | 
            -
                # Nest a PostContent instance inside a Layout instance, | 
| 139 | 
            +
                post = Post.find env[:id]
         | 
| 140 | 
            +
                # Nest a PostContent instance inside a Layout instance,
         | 
| 141 | 
            +
                # with some arbitrary HTML too
         | 
| 159 142 | 
             
                Layout.call(title: post.title) do
         | 
| 160 143 | 
             
                  <<~HTML
         | 
| 161 144 | 
             
                    <main>
         | 
| @@ -169,80 +152,122 @@ class PostPage < Rack::Component | |
| 169 152 | 
             
              end
         | 
| 170 153 | 
             
            end
         | 
| 171 154 |  | 
| 172 | 
            -
            class PostContent < Rack::Component
         | 
| 173 | 
            -
              render do |env|
         | 
| 174 | 
            -
                <<~HTML
         | 
| 175 | 
            -
                  <article>
         | 
| 176 | 
            -
                    <h1>#{env[:title]}</h1>
         | 
| 177 | 
            -
                    #{env[:body]}
         | 
| 178 | 
            -
                  </article>
         | 
| 179 | 
            -
                HTML
         | 
| 180 | 
            -
              end
         | 
| 181 | 
            -
            end
         | 
| 182 | 
            -
             | 
| 183 155 | 
             
            class Layout < Rack::Component
         | 
| 184 | 
            -
              render  | 
| 185 | 
            -
             | 
| 156 | 
            +
              # The +render+ macro supports Ruby's keyword arguments, and, like any other
         | 
| 157 | 
            +
              # Ruby function, can accept a block via the & operator.
         | 
| 158 | 
            +
              # Here, :title is a required key in +env+, and &child is just a regular Ruby
         | 
| 159 | 
            +
              # block that could be named anything.
         | 
| 160 | 
            +
              render do |title:, **, &child|
         | 
| 186 161 | 
             
                <<~HTML
         | 
| 187 162 | 
             
                  <!DOCTYPE html>
         | 
| 188 163 | 
             
                  <html>
         | 
| 189 164 | 
             
                    <head>
         | 
| 190 | 
            -
                      <title>#{ | 
| 165 | 
            +
                      <title>#{h title}</title>
         | 
| 191 166 | 
             
                    </head>
         | 
| 192 167 | 
             
                    <body>
         | 
| 193 | 
            -
             | 
| 168 | 
            +
                    #{child.call}
         | 
| 194 169 | 
             
                    </body>
         | 
| 195 170 | 
             
                  </html>
         | 
| 196 171 | 
             
                HTML
         | 
| 197 172 | 
             
              end
         | 
| 198 173 | 
             
            end
         | 
| 174 | 
            +
             | 
| 175 | 
            +
            class PostContent < Rack::Component
         | 
| 176 | 
            +
              render do |title:, body:, **|
         | 
| 177 | 
            +
                <<~HTML
         | 
| 178 | 
            +
                  <article>
         | 
| 179 | 
            +
                    <h1>#{h title}</h1>
         | 
| 180 | 
            +
                    #{h body}
         | 
| 181 | 
            +
                  </article>
         | 
| 182 | 
            +
                HTML
         | 
| 183 | 
            +
              end
         | 
| 184 | 
            +
            end
         | 
| 199 185 | 
             
            ```
         | 
| 200 186 |  | 
| 201 | 
            -
            ###  | 
| 187 | 
            +
            ### Render a template that escapes output by default via Tilt
         | 
| 202 188 |  | 
| 203 | 
            -
             | 
| 189 | 
            +
            If you add [Tilt][tilt] and `erubi` to your Gemfile, you can use the `render`
         | 
| 190 | 
            +
            macro with an automatically-escaped template instead of a block.
         | 
| 204 191 |  | 
| 205 192 | 
             
            ```ruby
         | 
| 206 | 
            -
             | 
| 193 | 
            +
            # Gemfile
         | 
| 194 | 
            +
            gem 'tilt'
         | 
| 195 | 
            +
            gem 'erubi'
         | 
| 196 | 
            +
            gem 'rack-component'
         | 
| 197 | 
            +
             | 
| 198 | 
            +
            # my_component.rb
         | 
| 199 | 
            +
            class TemplateComponent < Rack::Component
         | 
| 200 | 
            +
              render erb: <<~ERB
         | 
| 201 | 
            +
                <h1>Hello, <%= name %></h1>
         | 
| 202 | 
            +
              ERB
         | 
| 207 203 |  | 
| 208 | 
            -
             | 
| 209 | 
            -
             | 
| 210 | 
            -
               | 
| 204 | 
            +
              def name
         | 
| 205 | 
            +
                env[:name] || 'Someone'
         | 
| 206 | 
            +
              end
         | 
| 211 207 | 
             
            end
         | 
| 212 208 |  | 
| 213 | 
            -
             | 
| 214 | 
            -
             | 
| 209 | 
            +
            TemplateComponent.call #=> <h1>Hello, Someone</h1>
         | 
| 210 | 
            +
            TemplateComponent.call(name: 'Spock<>') #=> <h1>Hello, Spock<></h1>
         | 
| 211 | 
            +
            ```
         | 
| 212 | 
            +
             | 
| 213 | 
            +
            Rack::Component passes `{ escape_html: true }` to Tilt by default, which enables
         | 
| 214 | 
            +
            automatic escaping in ERB (via erubi) Haml, and Markdown. To disable automatic
         | 
| 215 | 
            +
            escaping, or to pass other tilt options, use an `opts: {}` key in `render`:
         | 
| 216 | 
            +
             | 
| 217 | 
            +
            ```ruby
         | 
| 218 | 
            +
            class OptionsComponent < Rack::Component
         | 
| 219 | 
            +
              render opts: { escape_html: false, trim: false }, erb: <<~ERB
         | 
| 220 | 
            +
                <article>
         | 
| 221 | 
            +
                  Hi there, <%= {env[:name] %>
         | 
| 222 | 
            +
                  <%== yield %>
         | 
| 223 | 
            +
                </article>
         | 
| 224 | 
            +
              ERB
         | 
| 225 | 
            +
            end
         | 
| 215 226 | 
             
            ```
         | 
| 216 227 |  | 
| 217 | 
            -
             | 
| 228 | 
            +
            Template components support using the `yield` keyword to render child
         | 
| 229 | 
            +
            components, but note the double-equals `<%==` in the example above. If your
         | 
| 230 | 
            +
            component escapes HTML, and you're yielding to a component that renders HTML,
         | 
| 231 | 
            +
            you probably want to disable escaping via `==`, just for the `<%== yield %>`
         | 
| 232 | 
            +
            call. This is safe, as long as the component you're yielding to uses escaping.
         | 
| 218 233 |  | 
| 219 | 
            -
             | 
| 220 | 
            -
             | 
| 234 | 
            +
            Using `erb` as a key for the inline template is a shorthand, which also works
         | 
| 235 | 
            +
            with `haml` and `markdown`. But you can also specify `engine` and `template`
         | 
| 236 | 
            +
            explicitly.
         | 
| 221 237 |  | 
| 222 238 | 
             
            ```ruby
         | 
| 223 | 
            -
            require ' | 
| 224 | 
            -
            class  | 
| 225 | 
            -
               | 
| 226 | 
            -
             | 
| 227 | 
            -
             | 
| 228 | 
            -
             | 
| 229 | 
            -
             | 
| 230 | 
            -
             | 
| 231 | 
            -
             | 
| 239 | 
            +
            require 'haml'
         | 
| 240 | 
            +
            class HamlComponent < Rack::Component
         | 
| 241 | 
            +
              # Note the special HEREDOC syntax for inline Haml templates! Without the
         | 
| 242 | 
            +
              # single-quotes, Ruby will interpret #{strings} before Haml does.
         | 
| 243 | 
            +
              render engine: 'haml', template: <<~'HAML'
         | 
| 244 | 
            +
                %h1 Hi #{env[:name]}.
         | 
| 245 | 
            +
              HAML
         | 
| 246 | 
            +
            end
         | 
| 247 | 
            +
            ```
         | 
| 232 248 |  | 
| 233 | 
            -
             | 
| 234 | 
            -
             | 
| 249 | 
            +
            Using a template instead of raw string interpolation is a safer default, but it
         | 
| 250 | 
            +
            can make it less convenient to do logic while rendering. Feel free to override
         | 
| 251 | 
            +
            your Component's `#initialize` method and do logic there:
         | 
| 252 | 
            +
             | 
| 253 | 
            +
            ```ruby
         | 
| 254 | 
            +
            class EscapedPostView < Rack::Component
         | 
| 255 | 
            +
              def initialize(env)
         | 
| 256 | 
            +
                @post = Post.find(env[:id])
         | 
| 257 | 
            +
                # calling `super` will populate the instance-level `env` hash, making
         | 
| 258 | 
            +
                # `env` available outside this method. But it's fine to skip it.
         | 
| 259 | 
            +
                super
         | 
| 235 260 | 
             
              end
         | 
| 236 | 
            -
            end
         | 
| 237 261 |  | 
| 238 | 
            -
             | 
| 239 | 
            -
             | 
| 262 | 
            +
              render erb: <<~ERB
         | 
| 263 | 
            +
                <article>
         | 
| 264 | 
            +
                  <h1><%= @post.title %></h1>
         | 
| 265 | 
            +
                  <%= @post.body %>
         | 
| 266 | 
            +
                </article>
         | 
| 267 | 
            +
              ERB
         | 
| 268 | 
            +
            end
         | 
| 240 269 | 
             
            ```
         | 
| 241 270 |  | 
| 242 | 
            -
            This recipe works with any Ruby object that implements a `#hash` method based
         | 
| 243 | 
            -
            on the object's content, including instances of `ActiveRecord::Base` and
         | 
| 244 | 
            -
            `Sequel::Model`.
         | 
| 245 | 
            -
             | 
| 246 271 | 
             
            ### Render an HTML list from an array
         | 
| 247 272 |  | 
| 248 273 | 
             
            [JSX Lists][jsx lists] use JavaScript's `map` function. Rack::Component does
         | 
| @@ -251,7 +276,7 @@ likewise, only you need to call `join` on the array: | |
| 251 276 | 
             
            ```ruby
         | 
| 252 277 | 
             
            require 'rack/component'
         | 
| 253 278 | 
             
            class PostsList < Rack::Component
         | 
| 254 | 
            -
              render do | 
| 279 | 
            +
              render do
         | 
| 255 280 | 
             
                <<~HTML
         | 
| 256 281 | 
             
                  <h1>This is a list of posts</h1>
         | 
| 257 282 | 
             
                  <ul>
         | 
| @@ -264,12 +289,12 @@ class PostsList < Rack::Component | |
| 264 289 | 
             
                env[:posts].map { |post|
         | 
| 265 290 | 
             
                  <<~HTML
         | 
| 266 291 | 
             
                    <li class="item">
         | 
| 267 | 
            -
                      <a href=" | 
| 292 | 
            +
                      <a href="/posts/#{post[:id]}">
         | 
| 268 293 | 
             
                        #{post[:name]}
         | 
| 269 294 | 
             
                      </a>
         | 
| 270 295 | 
             
                    </li>
         | 
| 271 296 | 
             
                  HTML
         | 
| 272 | 
            -
                }.join #unlike JSX, you need to call `join` on your array
         | 
| 297 | 
            +
                }.join # unlike JSX, you need to call `join` on your array
         | 
| 273 298 | 
             
              end
         | 
| 274 299 | 
             
            end
         | 
| 275 300 |  | 
| @@ -289,26 +314,24 @@ end | |
| 289 314 |  | 
| 290 315 | 
             
            # app/components/posts_list.rb
         | 
| 291 316 | 
             
            class PostsList < Rack::Component
         | 
| 292 | 
            -
              render | 
| 293 | 
            -
             | 
| 294 | 
            -
              def posts
         | 
| 295 | 
            -
                Post.magically_filter_via_params(env)
         | 
| 317 | 
            +
              def render
         | 
| 318 | 
            +
                Post.magically_filter_via_params(env).to_json
         | 
| 296 319 | 
             
              end
         | 
| 297 320 | 
             
            end
         | 
| 298 321 | 
             
            ```
         | 
| 299 322 |  | 
| 300 323 | 
             
            ### Mount a Rack::Component as a Rack app
         | 
| 301 324 |  | 
| 302 | 
            -
            Because Rack::Components  | 
| 303 | 
            -
            anywhere you can mount a Rack app. It's up to you to return a valid rack
         | 
| 304 | 
            -
             | 
| 325 | 
            +
            Because Rack::Components have the same signature as Rack app, you can mount them
         | 
| 326 | 
            +
            anywhere you can mount a Rack app. It's up to you to return a valid rack tuple,
         | 
| 327 | 
            +
            though.
         | 
| 305 328 |  | 
| 306 329 | 
             
            ```ruby
         | 
| 307 330 | 
             
            # config.ru
         | 
| 308 331 | 
             
            require 'rack/component'
         | 
| 309 332 |  | 
| 310 333 | 
             
            class Posts < Rack::Component
         | 
| 311 | 
            -
              render | 
| 334 | 
            +
              def render
         | 
| 312 335 | 
             
                [status, headers, [body]]
         | 
| 313 336 | 
             
              end
         | 
| 314 337 |  | 
| @@ -335,66 +358,102 @@ Rack::Component instead of Controllers, Views, and templates. But to see an | |
| 335 358 | 
             
            entire app built only out of Rack::Components, see
         | 
| 336 359 | 
             
            [the example spec](https://github.com/chrisfrank/rack-component/blob/master/spec/raw_rack_example_spec.rb).
         | 
| 337 360 |  | 
| 361 | 
            +
            ### Define `#render` at the instance level instead of via `render do`
         | 
| 362 | 
            +
             | 
| 363 | 
            +
            The class-level `render` macro exists to make using templates easy, and to lean
         | 
| 364 | 
            +
            on Ruby's keyword arguments as a limited imitation of React's `defaultProps` and
         | 
| 365 | 
            +
            `PropTypes`. But you can define render at the instance level instead.
         | 
| 366 | 
            +
             | 
| 367 | 
            +
            ```ruby
         | 
| 368 | 
            +
            # these two components render identical output
         | 
| 369 | 
            +
             | 
| 370 | 
            +
            class MacroComponent < Rack::Component
         | 
| 371 | 
            +
              render do |name:, dept: 'Engineering'|
         | 
| 372 | 
            +
                "#{name} - #{dept}"
         | 
| 373 | 
            +
              end
         | 
| 374 | 
            +
            end
         | 
| 375 | 
            +
             | 
| 376 | 
            +
            class ExplicitComponent < Rack::Component
         | 
| 377 | 
            +
              def initialize(name:, dept: 'Engineering')
         | 
| 378 | 
            +
                @name = name
         | 
| 379 | 
            +
                @dept = dept
         | 
| 380 | 
            +
                # calling `super` will populate the instance-level `env` hash, making
         | 
| 381 | 
            +
                # `env` available outside this method. But it's fine to skip it.
         | 
| 382 | 
            +
                super
         | 
| 383 | 
            +
              end
         | 
| 384 | 
            +
             | 
| 385 | 
            +
              def render
         | 
| 386 | 
            +
                "#{@name} - #{@dept}"
         | 
| 387 | 
            +
              end
         | 
| 388 | 
            +
            end
         | 
| 389 | 
            +
            ```
         | 
| 390 | 
            +
             | 
| 338 391 | 
             
            ## API Reference
         | 
| 339 392 |  | 
| 340 393 | 
             
            The full API reference is available here:
         | 
| 341 394 |  | 
| 342 395 | 
             
            https://www.rubydoc.info/gems/rack-component
         | 
| 343 396 |  | 
| 344 | 
            -
            For info on how to clear or change the size of the memoziation cache, please see
         | 
| 345 | 
            -
            [the spec][spec].
         | 
| 346 | 
            -
             | 
| 347 397 | 
             
            ## Performance
         | 
| 348 398 |  | 
| 349 | 
            -
             | 
| 350 | 
            -
             | 
| 351 | 
            -
            library. Run `ruby spec/benchmarks.rb` to see what to expect in your env.
         | 
| 399 | 
            +
            Run `ruby spec/benchmarks.rb` to see what to expect in your environment. These
         | 
| 400 | 
            +
            results are from a 2015 iMac:
         | 
| 352 401 |  | 
| 353 402 | 
             
            ```
         | 
| 354 403 | 
             
            $ ruby spec/benchmarks.rb
         | 
| 355 404 | 
             
            Warming up --------------------------------------
         | 
| 356 | 
            -
             | 
| 357 | 
            -
             | 
| 358 | 
            -
             | 
| 359 | 
            -
             | 
| 360 | 
            -
             | 
| 405 | 
            +
                      stdlib ERB     2.682k i/100ms
         | 
| 406 | 
            +
                        Tilt ERB    15.958k i/100ms
         | 
| 407 | 
            +
                     Bare lambda    77.124k i/100ms
         | 
| 408 | 
            +
                 RC [def render]    64.905k i/100ms
         | 
| 409 | 
            +
                  RC [render do]    57.725k i/100ms
         | 
| 410 | 
            +
                RC [render erb:]    15.595k i/100ms
         | 
| 361 411 | 
             
            Calculating -------------------------------------
         | 
| 362 | 
            -
             | 
| 363 | 
            -
             | 
| 364 | 
            -
             | 
| 365 | 
            -
             | 
| 366 | 
            -
             | 
| 412 | 
            +
                      stdlib ERB     27.423k (± 1.8%) i/s -    139.464k in   5.087391s
         | 
| 413 | 
            +
                        Tilt ERB    169.351k (± 2.2%) i/s -    861.732k in   5.090920s
         | 
| 414 | 
            +
                     Bare lambda    929.473k (± 3.0%) i/s -      4.705M in   5.065991s
         | 
| 415 | 
            +
                 RC [def render]    775.176k (± 1.1%) i/s -      3.894M in   5.024347s
         | 
| 416 | 
            +
                  RC [render do]    686.653k (± 2.3%) i/s -      3.464M in   5.046728s
         | 
| 417 | 
            +
                RC [render erb:]    165.113k (± 1.7%) i/s -    826.535k in   5.007444s
         | 
| 367 418 | 
             
            ```
         | 
| 368 419 |  | 
| 369 | 
            -
             | 
| 370 | 
            -
             | 
| 371 | 
            -
             | 
| 372 | 
            -
             | 
| 373 | 
            -
            probably fastest not to memoize. For components that do I/O, using `#memoize`
         | 
| 374 | 
            -
            can speed things up by several orders of magnitude.
         | 
| 420 | 
            +
            Every component in the benchmark is configured to escape HTML when rendering.
         | 
| 421 | 
            +
            When rendering via a block, Rack::Component is about 25x faster than ERB and 4x
         | 
| 422 | 
            +
            faster than Tilt. When rendering a template via Tilt, it (unsurprisingly)
         | 
| 423 | 
            +
            performs roughly at tilt-speed.
         | 
| 375 424 |  | 
| 376 425 | 
             
            ## Compatibility
         | 
| 377 426 |  | 
| 378 | 
            -
            Rack::Component has zero dependencies, | 
| 379 | 
            -
             | 
| 380 | 
            -
             | 
| 381 | 
            -
            specification, and because that's where I | 
| 427 | 
            +
            When not rendering Tilt templates, Rack::Component has zero dependencies,
         | 
| 428 | 
            +
            and will work in any Rack app. It should even work _outside_ a Rack app, because
         | 
| 429 | 
            +
            it's not actually dependent on Rack. I packaged it under the Rack namespace
         | 
| 430 | 
            +
            because it follows the Rack `call` specification, and because that's where I
         | 
| 431 | 
            +
            use and test it.
         | 
| 432 | 
            +
             | 
| 433 | 
            +
            When using Tilt templates, you will need `tilt` and a templating gem in your
         | 
| 434 | 
            +
            `Gemfile`:
         | 
| 435 | 
            +
             | 
| 436 | 
            +
            ```ruby
         | 
| 437 | 
            +
            gem 'tilt'
         | 
| 438 | 
            +
            gem 'erubi' # or gem 'haml', etc
         | 
| 439 | 
            +
            gem 'rack-component'
         | 
| 440 | 
            +
            ```
         | 
| 382 441 |  | 
| 383 442 | 
             
            ## Anybody using this in production?
         | 
| 384 443 |  | 
| 385 444 | 
             
            Aye:
         | 
| 386 445 |  | 
| 387 | 
            -
             | 
| 388 | 
            -
             | 
| 446 | 
            +
            * [future.com](https://www.future.com/)
         | 
| 447 | 
            +
            * [Seattle & King County Homelessness Response System](https://hrs.kc.future.com/)
         | 
| 389 448 |  | 
| 390 449 | 
             
            ## Ruby reference
         | 
| 391 450 |  | 
| 392 451 | 
             
            Where React uses [JSX] to make components more ergonomic, Rack::Component leans
         | 
| 393 452 | 
             
            heavily on some features built into the Ruby language, specifically:
         | 
| 394 453 |  | 
| 395 | 
            -
             | 
| 396 | 
            -
             | 
| 397 | 
            -
             | 
| 454 | 
            +
            * [Heredocs]
         | 
| 455 | 
            +
            * [String Interpolation]
         | 
| 456 | 
            +
            * [Calling methods with a block][ruby blocks]
         | 
| 398 457 |  | 
| 399 458 | 
             
            ## Development
         | 
| 400 459 |  | 
| @@ -426,3 +485,4 @@ MIT | |
| 426 485 | 
             
            [ruby blocks]: https://mixandgo.com/learn/mastering-ruby-blocks-in-less-than-5-minutes
         | 
| 427 486 | 
             
            [roda]: http://roda.jeremyevans.net
         | 
| 428 487 | 
             
            [sinatra]: http://sinatrarb.com
         | 
| 488 | 
            +
            [tilt]: https://github.com/rtomayko/tilt
         | 
    
        data/lib/rack/component.rb
    CHANGED
    
    | @@ -1,131 +1,86 @@ | |
| 1 1 | 
             
            require_relative 'component/version'
         | 
| 2 | 
            -
            require_relative 'component/ | 
| 2 | 
            +
            require_relative 'component/renderer'
         | 
| 3 3 | 
             
            require 'cgi'
         | 
| 4 4 |  | 
| 5 5 | 
             
            module Rack
         | 
| 6 6 | 
             
              # Subclass Rack::Component to compose functional, declarative responses to
         | 
| 7 7 | 
             
              # HTTP requests.
         | 
| 8 | 
            +
              # @example Subclass Rack::Component to compose functional, declarative
         | 
| 9 | 
            +
              # responses to HTTP requests.
         | 
| 10 | 
            +
              #   class Greeter < Rack::Component
         | 
| 11 | 
            +
              #     render { "Hi, #{env[:name]" }
         | 
| 12 | 
            +
              #   end
         | 
| 8 13 | 
             
              class Component
         | 
| 9 | 
            -
                 | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
                   | 
| 17 | 
            -
             | 
| 18 | 
            -
                  #             <title>#{env[:title]}</title>
         | 
| 19 | 
            -
                  #           </head>
         | 
| 20 | 
            -
                  #           <body>#{child.call}</body>
         | 
| 21 | 
            -
                  #         </html>
         | 
| 22 | 
            -
                  #       HTML
         | 
| 23 | 
            -
                  #     end
         | 
| 24 | 
            -
                  #   end
         | 
| 25 | 
            -
                  #
         | 
| 26 | 
            -
                  #   Layout.call(title: 'Hello') { "<h1>Hello from Rack::Component" } #=>
         | 
| 27 | 
            -
                  #   # <!DOCTYPE html>
         | 
| 28 | 
            -
                  #   # <html>
         | 
| 29 | 
            -
                  #   #   <head>
         | 
| 30 | 
            -
                  #   #     <title>Hello</title>
         | 
| 31 | 
            -
                  #   #   </head>
         | 
| 32 | 
            -
                  #   #   <body><h1>Hello from Rack::Component</h1></body>
         | 
| 33 | 
            -
                  #   # </html>
         | 
| 34 | 
            -
                  def call(env = {}, &child)
         | 
| 35 | 
            -
                    new(env).call env, &child
         | 
| 14 | 
            +
                # @example If you don't want to subclass, you can extend
         | 
| 15 | 
            +
                # Rack::Component::Methods instead.
         | 
| 16 | 
            +
                #   class POROGreeter
         | 
| 17 | 
            +
                #     extend Rack::Component::Methods
         | 
| 18 | 
            +
                #     render { "Hi, #{env[:name]" }
         | 
| 19 | 
            +
                #   end
         | 
| 20 | 
            +
                module Methods
         | 
| 21 | 
            +
                  def self.extended(base)
         | 
| 22 | 
            +
                    base.include(InstanceMethods)
         | 
| 36 23 | 
             
                  end
         | 
| 37 24 |  | 
| 38 | 
            -
                   | 
| 39 | 
            -
             | 
| 40 | 
            -
                  # will be read from a threadsafe in-memory cache, not computed.
         | 
| 41 | 
            -
                  # @example Cache a slow network call
         | 
| 42 | 
            -
                  #   class Fetcher < Rack::Component
         | 
| 43 | 
            -
                  #     render do |env|
         | 
| 44 | 
            -
                  #       Net::HTTP.get(env[:uri]).to_json
         | 
| 45 | 
            -
                  #     end
         | 
| 46 | 
            -
                  #   end
         | 
| 47 | 
            -
                  #
         | 
| 48 | 
            -
                  #   Fetcher.memoized(uri: '/slow/api.json')
         | 
| 49 | 
            -
                  #   # ...
         | 
| 50 | 
            -
                  #   # many seconds later...
         | 
| 51 | 
            -
                  #   # => { some: "data" }
         | 
| 52 | 
            -
                  #
         | 
| 53 | 
            -
                  #   Fetcher.memoized(uri: '/slow/api.json') #=> instant! { some: "data" }
         | 
| 54 | 
            -
                  #   Fetcher.memoized(uri: '/other/source.json') #=> slow again!
         | 
| 55 | 
            -
                  def memoized(env = {}, &child)
         | 
| 56 | 
            -
                    cache.fetch(env.hash) { call(env, &child) }
         | 
| 25 | 
            +
                  def render(opts = {})
         | 
| 26 | 
            +
                    block_given? ? configure_block(Proc.new) : configure_template(opts)
         | 
| 57 27 | 
             
                  end
         | 
| 58 28 |  | 
| 59 | 
            -
                   | 
| 60 | 
            -
             | 
| 61 | 
            -
                    cache.flush
         | 
| 29 | 
            +
                  def call(env = {}, &children)
         | 
| 30 | 
            +
                    new(env).render(&children)
         | 
| 62 31 | 
             
                  end
         | 
| 63 32 |  | 
| 64 | 
            -
                  #  | 
| 65 | 
            -
                  #  | 
| 66 | 
            -
                   | 
| 67 | 
            -
             | 
| 68 | 
            -
             | 
| 69 | 
            -
             | 
| 70 | 
            -
             | 
| 71 | 
            -
             | 
| 72 | 
            -
             | 
| 73 | 
            -
             | 
| 74 | 
            -
             | 
| 75 | 
            -
             | 
| 76 | 
            -
             | 
| 33 | 
            +
                  # Instances of Rack::Component come with these methods.
         | 
| 34 | 
            +
                  # :reek:ModuleInitialize
         | 
| 35 | 
            +
                  module InstanceMethods
         | 
| 36 | 
            +
                    # +env+ is Rack::Component's version of React's +props+ hash.
         | 
| 37 | 
            +
                    def initialize(env)
         | 
| 38 | 
            +
                      @env = env
         | 
| 39 | 
            +
                    end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                    # +env+ can be an empty hash, but cannot be nil
         | 
| 42 | 
            +
                    # @return [Hash]
         | 
| 43 | 
            +
                    def env
         | 
| 44 | 
            +
                      @env || {}
         | 
| 45 | 
            +
                    end
         | 
| 77 46 |  | 
| 78 | 
            -
             | 
| 79 | 
            -
             | 
| 80 | 
            -
             | 
| 81 | 
            -
             | 
| 82 | 
            -
             | 
| 83 | 
            -
                  #     cache { MemoryCache.new(length: 2000) }
         | 
| 84 | 
            -
                  #   end
         | 
| 85 | 
            -
                  def cache
         | 
| 86 | 
            -
                    @cache ||= (block_given? ? yield : MemoryCache.new(length: 100))
         | 
| 47 | 
            +
                    # +h+ removes HTML characters from strings via +CGI.escapeHTML+.
         | 
| 48 | 
            +
                    # @return [String]
         | 
| 49 | 
            +
                    def h(obj)
         | 
| 50 | 
            +
                      CGI.escapeHTML(obj.to_s)
         | 
| 51 | 
            +
                    end
         | 
| 87 52 | 
             
                  end
         | 
| 88 | 
            -
                end
         | 
| 89 53 |  | 
| 90 | 
            -
             | 
| 91 | 
            -
                  @env = env
         | 
| 92 | 
            -
                end
         | 
| 54 | 
            +
                  private
         | 
| 93 55 |  | 
| 94 | 
            -
             | 
| 95 | 
            -
             | 
| 96 | 
            -
             | 
| 97 | 
            -
             | 
| 98 | 
            -
             | 
| 99 | 
            -
             | 
| 100 | 
            -
             | 
| 101 | 
            -
                #   Useless = Class.new(Rack::Component)
         | 
| 102 | 
            -
                #   Useless.call(number: 1) #=> { number: 1 }
         | 
| 103 | 
            -
                #   Useless.call(number: 2) #=> { number: 2 }
         | 
| 104 | 
            -
                #   Useless.call(number: 2) { |env| "the number was #{env[:number]" }
         | 
| 105 | 
            -
                #   #=> 'the number was 2'
         | 
| 106 | 
            -
                #
         | 
| 107 | 
            -
                # @example a useful component
         | 
| 108 | 
            -
                #   class Greeter < Rack::Component
         | 
| 109 | 
            -
                #     render do |env|
         | 
| 110 | 
            -
                #       "Hi, #{env[:name]}"
         | 
| 111 | 
            -
                #     end
         | 
| 112 | 
            -
                #   end
         | 
| 113 | 
            -
                #
         | 
| 114 | 
            -
                #   Greeter.call(name: 'Jim') #=> 'Hi, Jim'
         | 
| 115 | 
            -
                #   Greeter.call(name: 'Bones') #=> 'Hi, Bones'
         | 
| 116 | 
            -
                def call(*)
         | 
| 117 | 
            -
                  block_given? ? yield(env) : env
         | 
| 118 | 
            -
                end
         | 
| 56 | 
            +
                  # :reek:TooManyStatements
         | 
| 57 | 
            +
                  # :reek:DuplicateMethodCall
         | 
| 58 | 
            +
                  def configure_block(block)
         | 
| 59 | 
            +
                    # Convert the block to an instance method, because instance_exec
         | 
| 60 | 
            +
                    # doesn't allow passing an &child param, and because it's faster.
         | 
| 61 | 
            +
                    define_method :_rc_render, &block
         | 
| 62 | 
            +
                    private :_rc_render
         | 
| 119 63 |  | 
| 120 | 
            -
             | 
| 64 | 
            +
                    # Now that the block is a method, it must be called with the correct
         | 
| 65 | 
            +
                    # number of arguments. Ruby's +arity+ method is unreliable when keyword
         | 
| 66 | 
            +
                    # args are involved, so we count arity by hand.
         | 
| 67 | 
            +
                    arity = block.parameters.reject { |type, _| type == :block }.length
         | 
| 121 68 |  | 
| 122 | 
            -
             | 
| 123 | 
            -
             | 
| 124 | 
            -
             | 
| 125 | 
            -
             | 
| 126 | 
            -
             | 
| 127 | 
            -
             | 
| 128 | 
            -
             | 
| 69 | 
            +
                    # Reek hates this DuplicateMethodCall, but fixing it would mean checking
         | 
| 70 | 
            +
                    # arity at runtime, rather than when the render macro is called.
         | 
| 71 | 
            +
                    if arity.zero?
         | 
| 72 | 
            +
                      define_method(:render) { |&child| _rc_render(&child) }
         | 
| 73 | 
            +
                    else
         | 
| 74 | 
            +
                      define_method(:render) { |&child| _rc_render(env, &child) }
         | 
| 75 | 
            +
                    end
         | 
| 76 | 
            +
                  end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                  def configure_template(options)
         | 
| 79 | 
            +
                    renderer = Renderer.new(options)
         | 
| 80 | 
            +
                    define_method(:render) { |&child| renderer.call(self, &child) }
         | 
| 81 | 
            +
                  end
         | 
| 129 82 | 
             
                end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                extend Methods
         | 
| 130 85 | 
             
              end
         | 
| 131 86 | 
             
            end
         | 
| @@ -0,0 +1,31 @@ | |
| 1 | 
            +
            module Rack
         | 
| 2 | 
            +
              class Component
         | 
| 3 | 
            +
                # Compile a Tilt template, which a component will render
         | 
| 4 | 
            +
                class Renderer
         | 
| 5 | 
            +
                  DEFAULT_TILT_OPTIONS = { escape_html: true }.freeze
         | 
| 6 | 
            +
                  FORMATS = %i[erb rhtml erubis haml liquid markdown md mkd].freeze
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def initialize(options = {})
         | 
| 9 | 
            +
                    require 'tilt'
         | 
| 10 | 
            +
                    engine, template, @config = OptionParser.call(options)
         | 
| 11 | 
            +
                    require 'erubi' if engine == 'erb' && @config[:escape_html]
         | 
| 12 | 
            +
                    @template = Tilt[engine].new(@config) { template }
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  def call(scope, &child)
         | 
| 16 | 
            +
                    @template.render(scope, &child)
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  OptionParser = lambda do |opts|
         | 
| 20 | 
            +
                    tilt_options = DEFAULT_TILT_OPTIONS.merge(opts.delete(:opts) || {})
         | 
| 21 | 
            +
                    engine, template =
         | 
| 22 | 
            +
                      opts.find { |key, _| FORMATS.include?(key) } ||
         | 
| 23 | 
            +
                      [opts[:engine], opts[:template]]
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                    [engine.to_s, template, tilt_options]
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  private_constant :OptionParser
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
              end
         | 
| 31 | 
            +
            end
         | 
    
        data/rack-component.gemspec
    CHANGED
    
    | @@ -30,10 +30,13 @@ Gem::Specification.new do |spec| | |
| 30 30 | 
             
              spec.executables   = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
         | 
| 31 31 | 
             
              spec.require_paths = ['lib']
         | 
| 32 32 |  | 
| 33 | 
            -
              spec.required_ruby_version = '>= 2. | 
| 33 | 
            +
              spec.required_ruby_version = '>= 2.3'
         | 
| 34 34 |  | 
| 35 35 | 
             
              spec.add_development_dependency 'benchmark-ips', '~> 2.7'
         | 
| 36 | 
            -
              spec.add_development_dependency 'bundler', '~>  | 
| 36 | 
            +
              spec.add_development_dependency 'bundler', '~> 2'
         | 
| 37 | 
            +
              spec.add_development_dependency 'erubi', '~> 1.8'
         | 
| 38 | 
            +
              spec.add_development_dependency 'haml', '~> 5'
         | 
| 39 | 
            +
              spec.add_development_dependency 'liquid', '~> 4'
         | 
| 37 40 | 
             
              spec.add_development_dependency 'pry', '~> 0.11'
         | 
| 38 41 | 
             
              spec.add_development_dependency 'rack', '~> 2.0.6'
         | 
| 39 42 | 
             
              spec.add_development_dependency 'rack-test', '~> 0'
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: rack-component
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.5.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Chris Frank
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2019-01- | 
| 11 | 
            +
            date: 2019-01-14 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: benchmark-ips
         | 
| @@ -30,14 +30,56 @@ dependencies: | |
| 30 30 | 
             
                requirements:
         | 
| 31 31 | 
             
                - - "~>"
         | 
| 32 32 | 
             
                  - !ruby/object:Gem::Version
         | 
| 33 | 
            -
                    version: ' | 
| 33 | 
            +
                    version: '2'
         | 
| 34 | 
            +
              type: :development
         | 
| 35 | 
            +
              prerelease: false
         | 
| 36 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 37 | 
            +
                requirements:
         | 
| 38 | 
            +
                - - "~>"
         | 
| 39 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 40 | 
            +
                    version: '2'
         | 
| 41 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 42 | 
            +
              name: erubi
         | 
| 43 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 44 | 
            +
                requirements:
         | 
| 45 | 
            +
                - - "~>"
         | 
| 46 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 47 | 
            +
                    version: '1.8'
         | 
| 48 | 
            +
              type: :development
         | 
| 49 | 
            +
              prerelease: false
         | 
| 50 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 51 | 
            +
                requirements:
         | 
| 52 | 
            +
                - - "~>"
         | 
| 53 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 54 | 
            +
                    version: '1.8'
         | 
| 55 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 56 | 
            +
              name: haml
         | 
| 57 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 58 | 
            +
                requirements:
         | 
| 59 | 
            +
                - - "~>"
         | 
| 60 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 61 | 
            +
                    version: '5'
         | 
| 62 | 
            +
              type: :development
         | 
| 63 | 
            +
              prerelease: false
         | 
| 64 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 65 | 
            +
                requirements:
         | 
| 66 | 
            +
                - - "~>"
         | 
| 67 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 68 | 
            +
                    version: '5'
         | 
| 69 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 70 | 
            +
              name: liquid
         | 
| 71 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 72 | 
            +
                requirements:
         | 
| 73 | 
            +
                - - "~>"
         | 
| 74 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 75 | 
            +
                    version: '4'
         | 
| 34 76 | 
             
              type: :development
         | 
| 35 77 | 
             
              prerelease: false
         | 
| 36 78 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 37 79 | 
             
                requirements:
         | 
| 38 80 | 
             
                - - "~>"
         | 
| 39 81 | 
             
                  - !ruby/object:Gem::Version
         | 
| 40 | 
            -
                    version: ' | 
| 82 | 
            +
                    version: '4'
         | 
| 41 83 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 42 84 | 
             
              name: pry
         | 
| 43 85 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -176,6 +218,7 @@ files: | |
| 176 218 | 
             
            - ".rspec"
         | 
| 177 219 | 
             
            - ".rubocop.yml"
         | 
| 178 220 | 
             
            - ".travis.yml"
         | 
| 221 | 
            +
            - CHANGELOG.md
         | 
| 179 222 | 
             
            - Gemfile
         | 
| 180 223 | 
             
            - Gemfile.lock
         | 
| 181 224 | 
             
            - README.md
         | 
| @@ -184,6 +227,7 @@ files: | |
| 184 227 | 
             
            - bin/setup
         | 
| 185 228 | 
             
            - lib/rack/component.rb
         | 
| 186 229 | 
             
            - lib/rack/component/memory_cache.rb
         | 
| 230 | 
            +
            - lib/rack/component/renderer.rb
         | 
| 187 231 | 
             
            - lib/rack/component/version.rb
         | 
| 188 232 | 
             
            - rack-component.gemspec
         | 
| 189 233 | 
             
            homepage: https://www.github.com/chrisfrank/rack-component
         | 
| @@ -199,7 +243,7 @@ required_ruby_version: !ruby/object:Gem::Requirement | |
| 199 243 | 
             
              requirements:
         | 
| 200 244 | 
             
              - - ">="
         | 
| 201 245 | 
             
                - !ruby/object:Gem::Version
         | 
| 202 | 
            -
                  version: '2. | 
| 246 | 
            +
                  version: '2.3'
         | 
| 203 247 | 
             
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 204 248 | 
             
              requirements:
         | 
| 205 249 | 
             
              - - ">="
         |