rack-component 0.2.0 → 0.3.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/Gemfile.lock +1 -9
- data/README.md +69 -64
- data/bin/console +1 -1
- data/lib/rack/component.rb +42 -33
- data/lib/rack/component/refinements.rb +14 -0
- data/rack-component.gemspec +0 -1
- metadata +3 -17
- data/.overcommit.yml +0 -37
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 441fd93e02f16449c7284aa7c84b72d01bc8b87e133999c2ed4b7a637431be07
|
4
|
+
data.tar.gz: ab7f5669c4b28c991f173180597f7b94c64de8cc8158d31da18f60b3927010f8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3232586822a9c38192cdbab6aa537236a331334ec0fe5ccaefbcdb8a62be92617f9fa94c4f372dbefc6eed92d0a8ef34e4c91971c6ef8b5f211bfede3d52b470
|
7
|
+
data.tar.gz: dbe6b3663319d39bf6ebf5c5610979a5de5713ec992dfe6dc5f5f680bd7c551ed540036addf317fdd08c93756259a4deda97a49b224dbe8ec3b28fe593574c88
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
rack-component (0.
|
4
|
+
rack-component (0.3.0)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
@@ -12,8 +12,6 @@ GEM
|
|
12
12
|
ice_nine (~> 0.11.0)
|
13
13
|
thread_safe (~> 0.3, >= 0.3.1)
|
14
14
|
benchmark-ips (2.7.2)
|
15
|
-
childprocess (0.9.0)
|
16
|
-
ffi (~> 1.0, >= 1.0.11)
|
17
15
|
codeclimate-engine-rb (0.4.1)
|
18
16
|
virtus (~> 1.0)
|
19
17
|
coderay (1.1.2)
|
@@ -23,16 +21,11 @@ GEM
|
|
23
21
|
thread_safe (~> 0.3, >= 0.3.1)
|
24
22
|
diff-lcs (1.3)
|
25
23
|
equalizer (0.0.11)
|
26
|
-
ffi (1.9.25)
|
27
24
|
ice_nine (0.11.2)
|
28
|
-
iniparse (1.4.4)
|
29
25
|
jaro_winkler (1.5.1)
|
30
26
|
kwalify (0.7.2)
|
31
27
|
method_source (0.9.0)
|
32
28
|
mustermann (1.0.3)
|
33
|
-
overcommit (0.46.0)
|
34
|
-
childprocess (~> 0.6, >= 0.6.3)
|
35
|
-
iniparse (~> 1.4)
|
36
29
|
parallel (1.12.1)
|
37
30
|
parser (2.5.1.2)
|
38
31
|
ast (~> 2.4.0)
|
@@ -95,7 +88,6 @@ PLATFORMS
|
|
95
88
|
DEPENDENCIES
|
96
89
|
benchmark-ips (~> 2.7)
|
97
90
|
bundler (~> 1.16)
|
98
|
-
overcommit (~> 0)
|
99
91
|
pry (~> 0.11)
|
100
92
|
rack-component!
|
101
93
|
rack-test (~> 0)
|
data/README.md
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
# Rack::Component
|
2
2
|
|
3
|
-
Like a React.js component, a `Rack::Component` implements a `render` method that
|
3
|
+
Like a React.js component, a `Rack::Component` implements a `render` method that
|
4
|
+
takes input data and returns what to display.
|
4
5
|
|
5
|
-
You can combine Components to build complex features out of simple, easily
|
6
|
+
You can combine Components to build complex features out of simple, easily
|
7
|
+
testable units.
|
6
8
|
|
7
9
|
## Installation
|
8
10
|
|
@@ -21,99 +23,103 @@ Or install it yourself as:
|
|
21
23
|
$ gem install rack-component
|
22
24
|
|
23
25
|
## API Reference
|
24
|
-
Please see the [YARD docs on rubydoc.info](https://www.rubydoc.info/gems/rack-component)
|
25
26
|
|
26
|
-
|
27
|
+
Please see the
|
28
|
+
[YARD docs on rubydoc.info](https://www.rubydoc.info/gems/rack-component)
|
27
29
|
|
28
|
-
|
30
|
+
## Usage
|
29
31
|
|
30
|
-
|
32
|
+
Subclass `Rack::Component` and `#call` it:
|
31
33
|
|
32
34
|
```ruby
|
33
|
-
|
34
|
-
|
35
|
-
Layout.call(title: post[:title]) do
|
36
|
-
PostView.call(post)
|
37
|
-
end
|
38
|
-
end
|
35
|
+
require 'rack/component'
|
36
|
+
class Useless < Rack::Component
|
39
37
|
end
|
38
|
+
|
39
|
+
Useless.call #=> the output Useless.new.render
|
40
40
|
```
|
41
41
|
|
42
|
-
|
42
|
+
The default implementation of `#render` is to yield the component instance to
|
43
|
+
whatever block you pass to `Component.call`, like this:
|
43
44
|
|
44
45
|
```ruby
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
46
|
+
Useless.call { |instance| "Hello from #{instance}" }
|
47
|
+
#=> "Hello from #<Useless:0x00007fcaba87d138>"
|
48
|
+
|
49
|
+
Useless.call do |instance|
|
50
|
+
Useless.call do |second_instance|
|
51
|
+
<<~HTML
|
52
|
+
<h1>Hello from #{instance}</h1>
|
53
|
+
<p>And also from #{second_instance}"</p>
|
54
|
+
HTML
|
55
|
+
end
|
49
56
|
end
|
57
|
+
# =>
|
58
|
+
# <h1>Hello from #<Useless:0x00007fcaba87d138></h1>
|
59
|
+
# <p>And also from #<Useless:0x00007f8482802498></p>
|
50
60
|
```
|
51
61
|
|
52
|
-
|
62
|
+
### Implement `#render` or add instance methods to make Components do work
|
53
63
|
|
54
|
-
|
64
|
+
Peruse the [specs][specs] for examples of component chains that handle
|
65
|
+
data fetching, views, and error handling in Sinatra and raw Rack.
|
66
|
+
|
67
|
+
Here's a component chain that prints headlines from Daring Fireball’s JSON feed:
|
55
68
|
|
56
69
|
```ruby
|
57
|
-
|
58
|
-
|
59
|
-
|
70
|
+
require 'rack/component'
|
71
|
+
|
72
|
+
# Make a network request and return the response
|
73
|
+
class Fetcher < Rack::Component
|
74
|
+
require 'net/http'
|
75
|
+
def initialize(uri:)
|
76
|
+
@response = Net::HTTP.get(URI(uri))
|
77
|
+
end
|
60
78
|
|
61
|
-
# Compose a few Components to save on typing
|
62
|
-
class PostPageView < Rack::Component
|
63
79
|
def render
|
64
|
-
|
65
|
-
Layout.call(title: post[:title]) { PostView.call(post) }
|
66
|
-
end
|
80
|
+
yield @response
|
67
81
|
end
|
68
82
|
end
|
69
|
-
```
|
70
83
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
84
|
+
# Parse items from a JSON Feed document
|
85
|
+
class JSONFeedParser < Rack::Component
|
86
|
+
require 'json'
|
87
|
+
def initialize(data)
|
88
|
+
@items = JSON.parse(data).fetch('items')
|
89
|
+
end
|
75
90
|
|
76
|
-
# render an HTML page
|
77
|
-
class Layout < Rack::Component
|
78
91
|
def render
|
79
|
-
|
80
|
-
<html>
|
81
|
-
<head>
|
82
|
-
<title>#{props[:title]}</title>
|
83
|
-
</head>
|
84
|
-
<body>
|
85
|
-
#{yield}
|
86
|
-
</body>
|
87
|
-
</html>
|
88
|
-
)
|
92
|
+
yield @items
|
89
93
|
end
|
90
94
|
end
|
91
95
|
|
92
|
-
#
|
93
|
-
class
|
94
|
-
def
|
95
|
-
|
96
|
+
# Render an HTML list of posts
|
97
|
+
class PostsList < Rack::Component
|
98
|
+
def initialize(posts:, style: '')
|
99
|
+
@posts = posts
|
100
|
+
@style = style
|
96
101
|
end
|
97
102
|
|
98
|
-
def
|
99
|
-
|
103
|
+
def render
|
104
|
+
<<~HTML
|
105
|
+
<ul style="#{@style}">
|
106
|
+
#{@posts.map(&ListItem).join}"
|
107
|
+
</ul>
|
108
|
+
HTML
|
100
109
|
end
|
101
|
-
end
|
102
110
|
|
103
|
-
|
104
|
-
|
111
|
+
ListItem = ->(post) { "<li>#{post['title']}</li>" }
|
112
|
+
end
|
105
113
|
|
106
|
-
#
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
<article>
|
111
|
-
<h1>#{props[:title]}</h1>
|
112
|
-
<p>#{props[:body]}</h1>
|
113
|
-
</article>
|
114
|
-
)
|
114
|
+
# Fetch JSON Feed data from daring fireball, parse it, render a list
|
115
|
+
Fetcher.call(uri: 'https://daringfireball.net/feeds/json') do |data|
|
116
|
+
JSONFeedParser.call(data) do |items|
|
117
|
+
PostsList.call(posts: items, style: 'background-color: red')
|
115
118
|
end
|
116
119
|
end
|
120
|
+
end
|
121
|
+
#=> A <ul> full of headlines from Daring Fireball
|
122
|
+
|
117
123
|
```
|
118
124
|
|
119
125
|
## Development
|
@@ -137,5 +143,4 @@ https://github.com/chrisfrank/rack-component.
|
|
137
143
|
|
138
144
|
MIT
|
139
145
|
|
140
|
-
[
|
141
|
-
[sinatra]: https://github.com/sinatra/sinatra
|
146
|
+
[specs]: https://github.com/chrisfrank/rack-component/tree/master/spec
|
data/bin/console
CHANGED
data/lib/rack/component.rb
CHANGED
@@ -1,50 +1,54 @@
|
|
1
1
|
require_relative 'component/component_cache'
|
2
|
+
require_relative 'component/refinements'
|
2
3
|
|
3
4
|
module Rack
|
4
5
|
# Subclass Rack::Component to compose declarative, component-based responses
|
5
6
|
# to HTTP requests
|
6
7
|
class Component
|
7
|
-
VERSION = '0.
|
8
|
+
VERSION = '0.3.0'.freeze
|
8
9
|
|
9
|
-
|
10
|
-
attr_reader :props
|
11
|
-
|
12
|
-
# Initialize a new component with the given props and #render() it.
|
10
|
+
# Initialize a new component with the given args and render it.
|
13
11
|
#
|
14
|
-
# @example
|
12
|
+
# @example Render a HelloWorld component
|
15
13
|
# class HelloWorld < Rack::Component
|
16
|
-
# def
|
17
|
-
#
|
14
|
+
# def initialize(name)
|
15
|
+
# @name = name
|
18
16
|
# end
|
19
17
|
#
|
20
|
-
# def
|
21
|
-
#
|
18
|
+
# def render
|
19
|
+
# "<h1>Hello #{@name}</h1>"
|
22
20
|
# end
|
23
21
|
# end
|
24
22
|
#
|
25
23
|
# MyComponent.call(world: 'Earth') #=> '<h1>Hello Earth</h1>'
|
26
24
|
# @return [String, Object] the output of instance#render
|
27
|
-
def self.call(*
|
28
|
-
new(*
|
29
|
-
end
|
30
|
-
|
31
|
-
def initialize(props = {})
|
32
|
-
@props = props
|
25
|
+
def self.call(*args, &block)
|
26
|
+
new(*args).render(&block)
|
33
27
|
end
|
34
28
|
|
35
|
-
# Override
|
29
|
+
# Override either #render or #exposures to make your component do work.
|
30
|
+
# By default, the behavior of #render depends on whether you call the
|
31
|
+
# component with a block or not: it either returns #exposures or yields to
|
32
|
+
# the block with #exposures as arguments.
|
33
|
+
#
|
36
34
|
# @return [String, Object] usually a string, but really whatever
|
37
35
|
def render
|
38
|
-
block_given? ? yield(
|
36
|
+
block_given? ? yield(exposures) : exposures
|
37
|
+
end
|
38
|
+
|
39
|
+
# Override #exposures to keep the default yield-or-return behavior
|
40
|
+
# of #render, but change what gets yielded or returned
|
41
|
+
def exposures
|
42
|
+
self
|
39
43
|
end
|
40
44
|
|
41
45
|
# Rack::Component::Memoized is just like Component, only it
|
42
46
|
# caches its rendered output in memory and only rerenders
|
43
|
-
# when called with new
|
47
|
+
# when called with new arguments.
|
44
48
|
class Memoized < self
|
45
|
-
CACHE_SIZE = 100 # limit
|
49
|
+
CACHE_SIZE = 100 # limit to 100 keys by default to prevent leaking RAM
|
46
50
|
|
47
|
-
# instantiate a class-level cache
|
51
|
+
# Access or instantiate a class-level cache
|
48
52
|
# @return [Rack::Component::ComponentCache] a threadsafe in-memory cache
|
49
53
|
def self.cache
|
50
54
|
@cache ||= ComponentCache.new(const_get(:CACHE_SIZE))
|
@@ -52,37 +56,42 @@ module Rack
|
|
52
56
|
|
53
57
|
# @example render a Memoized Component
|
54
58
|
# class Expensive < Rack::Component::Memoized
|
59
|
+
# def initialize(id)
|
60
|
+
# @id = id
|
61
|
+
# end
|
62
|
+
#
|
55
63
|
# def work
|
56
64
|
# sleep 5
|
57
|
-
# "#{
|
65
|
+
# "#{@id}"
|
58
66
|
# end
|
59
67
|
#
|
60
|
-
# def
|
68
|
+
# def render
|
61
69
|
# %(<h1>#{work}</h1>)
|
62
70
|
# end
|
63
71
|
# end
|
64
72
|
#
|
65
73
|
# # first call takes five seconds
|
66
|
-
# Expensive.call(id: 1) #=> <h1>1
|
67
|
-
# # subsequent calls with identical
|
74
|
+
# Expensive.call(id: 1) #=> <h1>1</h1>
|
75
|
+
# # subsequent calls with identical args are instant
|
76
|
+
# Expensive.call(id: 1) #=> <h1>1</h1>, instantly!
|
68
77
|
#
|
69
|
-
# # subsequent calls with _different_
|
70
|
-
# Expensive.call(id: 2) #=> <h1>2
|
78
|
+
# # subsequent calls with _different_ args take five seconds
|
79
|
+
# Expensive.call(id: 2) #=> <h1>2</h1>
|
71
80
|
#
|
72
81
|
# @return [String, Object] the cached (or computed) output of render
|
73
|
-
def self.call(*
|
74
|
-
memoized(*
|
82
|
+
def self.call(*args, &block)
|
83
|
+
memoized(*args) { super }
|
75
84
|
end
|
76
85
|
|
77
86
|
# Check the class-level cache, set it to &miss if nil.
|
78
87
|
# @return [Object] the output of &miss.call
|
79
|
-
def self.memoized(*
|
80
|
-
cache.fetch(key(*
|
88
|
+
def self.memoized(*args, &miss)
|
89
|
+
cache.fetch(key(*args), &miss)
|
81
90
|
end
|
82
91
|
|
83
92
|
# @return [Integer] a cache key for this component
|
84
|
-
def self.key(*
|
85
|
-
|
93
|
+
def self.key(*args)
|
94
|
+
args.hash
|
86
95
|
end
|
87
96
|
|
88
97
|
# Clear the cache of each descendant class.
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Rack
|
2
|
+
class Component
|
3
|
+
# These are a few refinements to the core classes to make rendering easier
|
4
|
+
module Refinements
|
5
|
+
refine Array do
|
6
|
+
# Join arrays with line breaks, so that calling list.map(&:render)
|
7
|
+
# results in usable HTML
|
8
|
+
def to_s
|
9
|
+
join("\n")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/rack-component.gemspec
CHANGED
@@ -34,7 +34,6 @@ Gem::Specification.new do |spec|
|
|
34
34
|
|
35
35
|
spec.add_development_dependency 'benchmark-ips', '~> 2.7'
|
36
36
|
spec.add_development_dependency 'bundler', '~> 1.16'
|
37
|
-
spec.add_development_dependency 'overcommit', '~> 0'
|
38
37
|
spec.add_development_dependency 'pry', '~> 0.11'
|
39
38
|
spec.add_development_dependency 'rack-test', '~> 0'
|
40
39
|
spec.add_development_dependency 'rake', '~> 10.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.3.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: 2018-12-
|
11
|
+
date: 2018-12-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: benchmark-ips
|
@@ -38,20 +38,6 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '1.16'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: overcommit
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - "~>"
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '0'
|
48
|
-
type: :development
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - "~>"
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '0'
|
55
41
|
- !ruby/object:Gem::Dependency
|
56
42
|
name: pry
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -186,7 +172,6 @@ extensions: []
|
|
186
172
|
extra_rdoc_files: []
|
187
173
|
files:
|
188
174
|
- ".gitignore"
|
189
|
-
- ".overcommit.yml"
|
190
175
|
- ".reek.yml"
|
191
176
|
- ".rspec"
|
192
177
|
- ".rubocop.yml"
|
@@ -199,6 +184,7 @@ files:
|
|
199
184
|
- bin/setup
|
200
185
|
- lib/rack/component.rb
|
201
186
|
- lib/rack/component/component_cache.rb
|
187
|
+
- lib/rack/component/refinements.rb
|
202
188
|
- rack-component.gemspec
|
203
189
|
homepage: https://www.github.com/chrisfrank/rack-component
|
204
190
|
licenses:
|
data/.overcommit.yml
DELETED
@@ -1,37 +0,0 @@
|
|
1
|
-
# Use this file to configure the Overcommit hooks you wish to use. This will
|
2
|
-
# extend the default configuration defined in:
|
3
|
-
# https://github.com/brigade/overcommit/blob/master/config/default.yml
|
4
|
-
#
|
5
|
-
# At the topmost level of this YAML file is a key representing type of hook
|
6
|
-
# being run (e.g. pre-commit, commit-msg, etc.). Within each type you can
|
7
|
-
# customize each hook, such as whether to only run it on certain files (via
|
8
|
-
# `include`), whether to only display output if it fails (via `quiet`), etc.
|
9
|
-
#
|
10
|
-
# For a complete list of hooks, see:
|
11
|
-
# https://github.com/brigade/overcommit/tree/master/lib/overcommit/hook
|
12
|
-
#
|
13
|
-
# For a complete list of options that you can use to customize hooks, see:
|
14
|
-
# https://github.com/brigade/overcommit#configuration
|
15
|
-
#
|
16
|
-
# Uncomment the following lines to make the configuration take effect.
|
17
|
-
|
18
|
-
PreCommit:
|
19
|
-
Rake:
|
20
|
-
enabled: true
|
21
|
-
command: ['bundle', 'exec', 'rake']
|
22
|
-
|
23
|
-
# RuboCop:
|
24
|
-
# enabled: true
|
25
|
-
# on_warn: fail # Treat all warnings as failures
|
26
|
-
#
|
27
|
-
# TrailingWhitespace:
|
28
|
-
# enabled: true
|
29
|
-
# exclude:
|
30
|
-
# - '**/db/structure.sql' # Ignore trailing whitespace in generated files
|
31
|
-
#
|
32
|
-
#PostCheckout:
|
33
|
-
# ALL: # Special hook name that customizes all hooks of this type
|
34
|
-
# quiet: true # Change all post-checkout hooks to only display output on failure
|
35
|
-
#
|
36
|
-
# IndexTags:
|
37
|
-
# enabled: true # Generate a tags file with `ctags` each time HEAD changes
|