rack-component 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|