rack-component 0.1.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 +7 -0
- data/.gitignore +12 -0
- data/.overcommit.yml +37 -0
- data/.reek.yml +8 -0
- data/.rspec +3 -0
- data/.rubocop.yml +28 -0
- data/.travis.yml +7 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +111 -0
- data/README.md +141 -0
- data/Rakefile +18 -0
- data/bin/console +14 -0
- data/bin/setup +9 -0
- data/lib/rack/component/component_cache.rb +45 -0
- data/lib/rack/component.rb +99 -0
- data/rack-component.gemspec +47 -0
- metadata +228 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 568c9eae25af8dd55cc2ac0b51c2b0da4de5818864c730c6f3965e3a4c92296b
|
|
4
|
+
data.tar.gz: 25f0a6e450191a830bb08c06c5194fba2ec251e12c0586bf414d6994e386b04a
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 123fdb85f98d1953993e2618369ce47a8d342e4346ab19d7a3322f4619cfbc4390145a3f39ecc46c70952b552fd61c636d98eb896926841224a0425ff2794adb
|
|
7
|
+
data.tar.gz: b89ea9607fba5165632392d6878bd8272d1b07f4425c0498b89c6a28ad4260126cbcfed3213d2fd76d764343aabe124c0f43adeceba38254b20553b015465e55
|
data/.gitignore
ADDED
data/.overcommit.yml
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
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
|
data/.reek.yml
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
TargetRubyVersion: 2.5
|
|
3
|
+
Layout/LeadingCommentSpace:
|
|
4
|
+
Enabled: false
|
|
5
|
+
Metrics/BlockLength:
|
|
6
|
+
Enabled: false
|
|
7
|
+
Metrics/MethodLength:
|
|
8
|
+
ExcludedMethods:
|
|
9
|
+
- render
|
|
10
|
+
Style/FrozenStringLiteralComment:
|
|
11
|
+
EnforcedStyle: never
|
|
12
|
+
Style/AsciiComments:
|
|
13
|
+
Enabled: false
|
|
14
|
+
StyleGuide: http://relaxed.ruby.style/#styleasciicomments
|
|
15
|
+
Style/TrailingCommaInArguments:
|
|
16
|
+
Enabled: false
|
|
17
|
+
StyleGuide: http://relaxed.ruby.style/#styletrailingcommainarguments
|
|
18
|
+
EnforcedStyleForMultiline: consistent_comma
|
|
19
|
+
Style/TrailingCommaInArrayLiteral:
|
|
20
|
+
Enabled: false
|
|
21
|
+
StyleGuide: http://relaxed.ruby.style/#styletrailingcommainliteral
|
|
22
|
+
EnforcedStyleForMultiline: consistent_comma
|
|
23
|
+
Style/TrailingCommaInHashLiteral:
|
|
24
|
+
Enabled: false
|
|
25
|
+
StyleGuide: http://relaxed.ruby.style/#styletrailingcommainliteral
|
|
26
|
+
EnforcedStyleForMultiline: consistent_comma
|
|
27
|
+
Style/Documentation:
|
|
28
|
+
Enabled: false
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
rack-component (0.1.0)
|
|
5
|
+
|
|
6
|
+
GEM
|
|
7
|
+
remote: https://rubygems.org/
|
|
8
|
+
specs:
|
|
9
|
+
ast (2.4.0)
|
|
10
|
+
axiom-types (0.1.1)
|
|
11
|
+
descendants_tracker (~> 0.0.4)
|
|
12
|
+
ice_nine (~> 0.11.0)
|
|
13
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
|
14
|
+
benchmark-ips (2.7.2)
|
|
15
|
+
childprocess (0.9.0)
|
|
16
|
+
ffi (~> 1.0, >= 1.0.11)
|
|
17
|
+
codeclimate-engine-rb (0.4.1)
|
|
18
|
+
virtus (~> 1.0)
|
|
19
|
+
coderay (1.1.2)
|
|
20
|
+
coercible (1.0.0)
|
|
21
|
+
descendants_tracker (~> 0.0.1)
|
|
22
|
+
descendants_tracker (0.0.4)
|
|
23
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
|
24
|
+
diff-lcs (1.3)
|
|
25
|
+
equalizer (0.0.11)
|
|
26
|
+
ffi (1.9.25)
|
|
27
|
+
ice_nine (0.11.2)
|
|
28
|
+
iniparse (1.4.4)
|
|
29
|
+
jaro_winkler (1.5.1)
|
|
30
|
+
kwalify (0.7.2)
|
|
31
|
+
method_source (0.9.0)
|
|
32
|
+
mustermann (1.0.3)
|
|
33
|
+
overcommit (0.46.0)
|
|
34
|
+
childprocess (~> 0.6, >= 0.6.3)
|
|
35
|
+
iniparse (~> 1.4)
|
|
36
|
+
parallel (1.12.1)
|
|
37
|
+
parser (2.5.1.2)
|
|
38
|
+
ast (~> 2.4.0)
|
|
39
|
+
powerpack (0.1.2)
|
|
40
|
+
pry (0.11.3)
|
|
41
|
+
coderay (~> 1.1.0)
|
|
42
|
+
method_source (~> 0.9.0)
|
|
43
|
+
rack (2.0.5)
|
|
44
|
+
rack-protection (2.0.4)
|
|
45
|
+
rack
|
|
46
|
+
rack-test (0.8.3)
|
|
47
|
+
rack (>= 1.0, < 3)
|
|
48
|
+
rainbow (3.0.0)
|
|
49
|
+
rake (10.5.0)
|
|
50
|
+
reek (5.2.0)
|
|
51
|
+
codeclimate-engine-rb (~> 0.4.0)
|
|
52
|
+
kwalify (~> 0.7.0)
|
|
53
|
+
parser (>= 2.5.0.0, < 2.6, != 2.5.1.1)
|
|
54
|
+
rainbow (>= 2.0, < 4.0)
|
|
55
|
+
rspec (3.8.0)
|
|
56
|
+
rspec-core (~> 3.8.0)
|
|
57
|
+
rspec-expectations (~> 3.8.0)
|
|
58
|
+
rspec-mocks (~> 3.8.0)
|
|
59
|
+
rspec-core (3.8.0)
|
|
60
|
+
rspec-support (~> 3.8.0)
|
|
61
|
+
rspec-expectations (3.8.2)
|
|
62
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
63
|
+
rspec-support (~> 3.8.0)
|
|
64
|
+
rspec-mocks (3.8.0)
|
|
65
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
66
|
+
rspec-support (~> 3.8.0)
|
|
67
|
+
rspec-support (3.8.0)
|
|
68
|
+
rubocop (0.59.2)
|
|
69
|
+
jaro_winkler (~> 1.5.1)
|
|
70
|
+
parallel (~> 1.10)
|
|
71
|
+
parser (>= 2.5, != 2.5.1.1)
|
|
72
|
+
powerpack (~> 0.1)
|
|
73
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
74
|
+
ruby-progressbar (~> 1.7)
|
|
75
|
+
unicode-display_width (~> 1.0, >= 1.0.1)
|
|
76
|
+
ruby-progressbar (1.10.0)
|
|
77
|
+
sinatra (2.0.4)
|
|
78
|
+
mustermann (~> 1.0)
|
|
79
|
+
rack (~> 2.0)
|
|
80
|
+
rack-protection (= 2.0.4)
|
|
81
|
+
tilt (~> 2.0)
|
|
82
|
+
thread_safe (0.3.6)
|
|
83
|
+
tilt (2.0.8)
|
|
84
|
+
unicode-display_width (1.4.0)
|
|
85
|
+
virtus (1.0.5)
|
|
86
|
+
axiom-types (~> 0.1)
|
|
87
|
+
coercible (~> 1.0)
|
|
88
|
+
descendants_tracker (~> 0.0, >= 0.0.3)
|
|
89
|
+
equalizer (~> 0.0, >= 0.0.9)
|
|
90
|
+
yard (0.9.16)
|
|
91
|
+
|
|
92
|
+
PLATFORMS
|
|
93
|
+
ruby
|
|
94
|
+
|
|
95
|
+
DEPENDENCIES
|
|
96
|
+
benchmark-ips (~> 2.7)
|
|
97
|
+
bundler (~> 1.16)
|
|
98
|
+
overcommit (~> 0)
|
|
99
|
+
pry (~> 0.11)
|
|
100
|
+
rack-component!
|
|
101
|
+
rack-test (~> 0)
|
|
102
|
+
rake (~> 10.0)
|
|
103
|
+
reek (~> 5)
|
|
104
|
+
rspec (~> 3.0)
|
|
105
|
+
rubocop (~> 0.59)
|
|
106
|
+
sinatra (~> 2)
|
|
107
|
+
tilt (~> 2)
|
|
108
|
+
yard (~> 0.9)
|
|
109
|
+
|
|
110
|
+
BUNDLED WITH
|
|
111
|
+
1.17.1
|
data/README.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Rack::Component
|
|
2
|
+
|
|
3
|
+
Like a React.js component, a `Rack::Component` implements a `render` method that takes input data and returns what to display.
|
|
4
|
+
|
|
5
|
+
You can combine Components to build complex features out of simple, easily testable units.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Add this line to your application's Gemfile:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
gem 'rack-component', require: 'rack/component'
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
And then execute:
|
|
16
|
+
|
|
17
|
+
$ bundle
|
|
18
|
+
|
|
19
|
+
Or install it yourself as:
|
|
20
|
+
|
|
21
|
+
$ gem install rack-component
|
|
22
|
+
|
|
23
|
+
## API Reference
|
|
24
|
+
Please see the [YARD docs on rubydoc.info](https://www.rubydoc.info/gems/rack-component)
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
You could build an entire app out of Components, but Ruby already has great HTTP routers like [Roda][roda] and [Sinatra][sinatra]. Here's an example that uses Sinatra for routing, and Components instead of views, controllers, and templates.
|
|
29
|
+
|
|
30
|
+
### With Sinatra
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
get '/posts/:id' do
|
|
34
|
+
PostFetcher.call(id: params[:id]) do |post|
|
|
35
|
+
Layout.call(title: post[:title]) do
|
|
36
|
+
PostView.call(post)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
_Why_, you may be thinking, _would I write something so ugly when I could write this instead?_
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
get '/posts/:id' do
|
|
46
|
+
@post = Post.find(params[:id])
|
|
47
|
+
@title = @post[:title]
|
|
48
|
+
erb :post
|
|
49
|
+
end
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
You'd be right that the traditional version is shorter and pretter. But the Component version’s API is more declarative -- you are describing what you want, and leaving the details of _how to get it_ up to each Component, instead of writing implementation-specific details right in your route block.
|
|
53
|
+
|
|
54
|
+
The Component version is easier to reuse, refactor, and test. And because Components are meant to be combined via composition, it's actually trivial to make a Component version that's even more concise:
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
get('/posts/:id') do
|
|
58
|
+
PostPageView.call(id: params[:id])
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Compose a few Components to save on typing
|
|
62
|
+
class PostPageView < Rack::Component
|
|
63
|
+
def render
|
|
64
|
+
PostFetcher.call(id: props[:id]) do |post|
|
|
65
|
+
Layout.call(title: post[:title]) { PostView.call(post) }
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
PostFetcher, Layout, and PostView are all simple Rack::Components. Their implementation looks like this:
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
require 'rack/component'
|
|
75
|
+
|
|
76
|
+
# render an HTML page
|
|
77
|
+
class Layout < Rack::Component
|
|
78
|
+
def render
|
|
79
|
+
%(
|
|
80
|
+
<html>
|
|
81
|
+
<head>
|
|
82
|
+
<title>#{props[:title]}</title>
|
|
83
|
+
</head>
|
|
84
|
+
<body>
|
|
85
|
+
#{yield}
|
|
86
|
+
</body>
|
|
87
|
+
</html>
|
|
88
|
+
)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Fetch a post, pass it to the next component
|
|
93
|
+
class PostFetcher < Rack::Component
|
|
94
|
+
def render
|
|
95
|
+
yield fetch
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def fetch
|
|
99
|
+
DB[:posts].fetch(props[:id].to_i)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# A fake database with a fake 'posts' table
|
|
104
|
+
DB = { posts: { 1 => { title: 'Example Title', body: 'Example body' } } }
|
|
105
|
+
|
|
106
|
+
# View a single post
|
|
107
|
+
class PostView < Rack::Component
|
|
108
|
+
def render
|
|
109
|
+
%(
|
|
110
|
+
<article>
|
|
111
|
+
<h1>#{props[:title]}</h1>
|
|
112
|
+
<p>#{props[:body]}</h1>
|
|
113
|
+
</article>
|
|
114
|
+
)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Development
|
|
120
|
+
|
|
121
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
122
|
+
`rake spec` to run the tests. You can also run `bin/console` for an interactive
|
|
123
|
+
prompt that will allow you to experiment.
|
|
124
|
+
|
|
125
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
126
|
+
release a new version, update the version number in `version.rb`, and then run
|
|
127
|
+
`bundle exec rake release`, which will create a git tag for the version, push
|
|
128
|
+
git commits and tags, and push the `.gem` file to
|
|
129
|
+
[rubygems.org](https://rubygems.org).
|
|
130
|
+
|
|
131
|
+
## Contributing
|
|
132
|
+
|
|
133
|
+
Bug reports and pull requests are welcome on GitHub at
|
|
134
|
+
https://github.com/chrisfrank/rack-component.
|
|
135
|
+
|
|
136
|
+
## License
|
|
137
|
+
|
|
138
|
+
MIT
|
|
139
|
+
|
|
140
|
+
[roda]: https://github.com/jeremyevans/roda
|
|
141
|
+
[sinatra]: https://github.com/sinatra/sinatra
|
data/Rakefile
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require 'bundler/gem_tasks'
|
|
2
|
+
require 'rspec/core/rake_task'
|
|
3
|
+
|
|
4
|
+
RSpec::Core::RakeTask.new(:spec)
|
|
5
|
+
|
|
6
|
+
task :cop do
|
|
7
|
+
system 'bundle exec rubocop lib'
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
task :reek do
|
|
11
|
+
system 'bundle exec reek lib'
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
task :doc do
|
|
15
|
+
system 'bundle exec yard doc'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
task default: %i[cop reek spec doc]
|
data/bin/console
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'bundler/setup'
|
|
4
|
+
require 'chemical'
|
|
5
|
+
|
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
8
|
+
|
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
10
|
+
# require "pry"
|
|
11
|
+
# Pry.start
|
|
12
|
+
|
|
13
|
+
require 'irb'
|
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module Rack
|
|
2
|
+
class Component
|
|
3
|
+
# Threadsafe in-memory cache
|
|
4
|
+
class ComponentCache
|
|
5
|
+
attr_reader :store
|
|
6
|
+
|
|
7
|
+
# Initialize a mutex for threadsafe reads and writes
|
|
8
|
+
LOCK = Mutex.new
|
|
9
|
+
|
|
10
|
+
# Store cache in a hash
|
|
11
|
+
def initialize(limit = 100)
|
|
12
|
+
@store = {}
|
|
13
|
+
@limit = limit
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Fetch a key from the cache, if it exists
|
|
17
|
+
# If the key doesn't exist and a block is passed, set the key
|
|
18
|
+
# @return the cached value
|
|
19
|
+
def fetch(key)
|
|
20
|
+
store.fetch(key) do
|
|
21
|
+
write(key, yield) if block_given?
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# empty the cache
|
|
26
|
+
# @return [Hash] the empty store
|
|
27
|
+
def flush
|
|
28
|
+
LOCK.synchronize { @store = {} }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
# Cache a value and return it
|
|
34
|
+
def write(key, value)
|
|
35
|
+
LOCK.synchronize do
|
|
36
|
+
store[key] = value
|
|
37
|
+
store.delete(@store.keys.first) if store.length > @limit
|
|
38
|
+
value
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private_constant :ComponentCache
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
require_relative 'component/component_cache'
|
|
2
|
+
|
|
3
|
+
module Rack
|
|
4
|
+
# Subclass Rack::Component to compose declarative, component-based responses
|
|
5
|
+
# to HTTP requests
|
|
6
|
+
class Component
|
|
7
|
+
VERSION = '0.1.0'.freeze
|
|
8
|
+
|
|
9
|
+
EMPTY = ''.freeze # components render an empty body by default
|
|
10
|
+
attr_reader :props
|
|
11
|
+
|
|
12
|
+
# Initialize a new component with the given props and #render() it.
|
|
13
|
+
#
|
|
14
|
+
# @example render a HelloWorld component
|
|
15
|
+
# class HelloWorld < Rack::Component
|
|
16
|
+
# def world
|
|
17
|
+
# props[:world]
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# def render
|
|
21
|
+
# %(<h1>Hello #{world}</h1>)
|
|
22
|
+
# end
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# MyComponent.call(world: 'Earth') #=> '<h1>Hello Earth</h1>'
|
|
26
|
+
# @return [String, Object] the rendered component instance
|
|
27
|
+
def self.call(props = {}, &block)
|
|
28
|
+
new(props).render(&block)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def initialize(props = {})
|
|
32
|
+
@props = props
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Override render to make your component do work.
|
|
36
|
+
# @return [String, Object] usually a string, but really whatever
|
|
37
|
+
def render
|
|
38
|
+
block_given? ? yield(self) : EMPTY
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Rack::Component::Memoized is just like Component, only it
|
|
42
|
+
# caches its rendered output in memory and only rerenders
|
|
43
|
+
# when called with new props or a new block.
|
|
44
|
+
class Memoized < self
|
|
45
|
+
CACHE_SIZE = 100 # limit cache to 100 keys by default so we don't leak RAM
|
|
46
|
+
|
|
47
|
+
# instantiate a class-level cache if necessary
|
|
48
|
+
# @return [Rack::Component::ComponentCache] a threadsafe in-memory cache
|
|
49
|
+
def self.cache
|
|
50
|
+
@cache ||= ComponentCache.new(const_get(:CACHE_SIZE))
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @example render a Memoized Component
|
|
54
|
+
# class Expensive < Rack::Component::Memoized
|
|
55
|
+
# def work
|
|
56
|
+
# sleep 5
|
|
57
|
+
# "#{props[:id]} was expensive"
|
|
58
|
+
# end
|
|
59
|
+
#
|
|
60
|
+
# def render
|
|
61
|
+
# %(<h1>#{work}</h1>)
|
|
62
|
+
# end
|
|
63
|
+
# end
|
|
64
|
+
#
|
|
65
|
+
# # first call takes five seconds
|
|
66
|
+
# Expensive.call(id: 1) #=> <h1>1 was expensive</h1>
|
|
67
|
+
# # subsequent calls with identical props are instant
|
|
68
|
+
#
|
|
69
|
+
# # subsequent calls with _different_ props take five seconds
|
|
70
|
+
# Expensive.call(id: 2) #=> <h1>2 was expensive</h1>
|
|
71
|
+
#
|
|
72
|
+
# @return [String, Object] the cached (or computed) output of render
|
|
73
|
+
def self.call(props = {}, &block)
|
|
74
|
+
memoized(props) { super }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Check the class-level cache, set it to &miss if nil.
|
|
78
|
+
# @return [Object] the output of &miss.call
|
|
79
|
+
def self.memoized(props, &miss)
|
|
80
|
+
cache.fetch(key(props), &miss)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# @return [Integer] a cache key for this component
|
|
84
|
+
def self.key(props)
|
|
85
|
+
props.hash
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Clear the cache of each descendant class.
|
|
89
|
+
# Generally you'll call this on Rack::Component::Memoized directly.
|
|
90
|
+
# @example Clear all caches:
|
|
91
|
+
# Rack::Component::Memoized.flush_caches
|
|
92
|
+
def self.clear_caches
|
|
93
|
+
ObjectSpace.each_object(singleton_class) do |descendant|
|
|
94
|
+
descendant.cache.flush
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
lib = File.expand_path('lib', __dir__)
|
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
3
|
+
require 'rack/component'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'rack-component'
|
|
7
|
+
spec.version = Rack::Component::VERSION
|
|
8
|
+
spec.authors = ['Chris Frank']
|
|
9
|
+
spec.email = ['chris.frank@future.com']
|
|
10
|
+
spec.licenses = ['MIT']
|
|
11
|
+
|
|
12
|
+
spec.summary = 'Compose declarative, component-based responses to HTTP requests'
|
|
13
|
+
spec.homepage = 'https://www.github.com/chrisfrank/rack-component'
|
|
14
|
+
|
|
15
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
|
16
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
|
17
|
+
if spec.respond_to?(:metadata)
|
|
18
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
|
19
|
+
else
|
|
20
|
+
raise 'RubyGems 2.0 or newer is required to protect against ' \
|
|
21
|
+
'public gem pushes.'
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Specify which files should be added to the gem when it is released.
|
|
25
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
26
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
27
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
28
|
+
end
|
|
29
|
+
spec.bindir = 'bin'
|
|
30
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
31
|
+
spec.require_paths = ['lib']
|
|
32
|
+
|
|
33
|
+
spec.required_ruby_version = '>= 2.2'
|
|
34
|
+
|
|
35
|
+
spec.add_development_dependency 'benchmark-ips', '~> 2.7'
|
|
36
|
+
spec.add_development_dependency 'bundler', '~> 1.16'
|
|
37
|
+
spec.add_development_dependency 'overcommit', '~> 0'
|
|
38
|
+
spec.add_development_dependency 'pry', '~> 0.11'
|
|
39
|
+
spec.add_development_dependency 'rack-test', '~> 0'
|
|
40
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
|
41
|
+
spec.add_development_dependency 'reek', '~> 5'
|
|
42
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
43
|
+
spec.add_development_dependency 'rubocop', '~> 0.59'
|
|
44
|
+
spec.add_development_dependency 'sinatra', '~> 2'
|
|
45
|
+
spec.add_development_dependency 'tilt', '~> 2'
|
|
46
|
+
spec.add_development_dependency 'yard', '~> 0.9'
|
|
47
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rack-component
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Chris Frank
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2018-11-12 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: benchmark-ips
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '2.7'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '2.7'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: bundler
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '1.16'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
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
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: pry
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0.11'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0.11'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: rack-test
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '0'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: rake
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '10.0'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '10.0'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: reek
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - "~>"
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '5'
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - "~>"
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '5'
|
|
111
|
+
- !ruby/object:Gem::Dependency
|
|
112
|
+
name: rspec
|
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - "~>"
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '3.0'
|
|
118
|
+
type: :development
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - "~>"
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '3.0'
|
|
125
|
+
- !ruby/object:Gem::Dependency
|
|
126
|
+
name: rubocop
|
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - "~>"
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '0.59'
|
|
132
|
+
type: :development
|
|
133
|
+
prerelease: false
|
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
135
|
+
requirements:
|
|
136
|
+
- - "~>"
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: '0.59'
|
|
139
|
+
- !ruby/object:Gem::Dependency
|
|
140
|
+
name: sinatra
|
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
|
142
|
+
requirements:
|
|
143
|
+
- - "~>"
|
|
144
|
+
- !ruby/object:Gem::Version
|
|
145
|
+
version: '2'
|
|
146
|
+
type: :development
|
|
147
|
+
prerelease: false
|
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
149
|
+
requirements:
|
|
150
|
+
- - "~>"
|
|
151
|
+
- !ruby/object:Gem::Version
|
|
152
|
+
version: '2'
|
|
153
|
+
- !ruby/object:Gem::Dependency
|
|
154
|
+
name: tilt
|
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
|
156
|
+
requirements:
|
|
157
|
+
- - "~>"
|
|
158
|
+
- !ruby/object:Gem::Version
|
|
159
|
+
version: '2'
|
|
160
|
+
type: :development
|
|
161
|
+
prerelease: false
|
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
163
|
+
requirements:
|
|
164
|
+
- - "~>"
|
|
165
|
+
- !ruby/object:Gem::Version
|
|
166
|
+
version: '2'
|
|
167
|
+
- !ruby/object:Gem::Dependency
|
|
168
|
+
name: yard
|
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
|
170
|
+
requirements:
|
|
171
|
+
- - "~>"
|
|
172
|
+
- !ruby/object:Gem::Version
|
|
173
|
+
version: '0.9'
|
|
174
|
+
type: :development
|
|
175
|
+
prerelease: false
|
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
177
|
+
requirements:
|
|
178
|
+
- - "~>"
|
|
179
|
+
- !ruby/object:Gem::Version
|
|
180
|
+
version: '0.9'
|
|
181
|
+
description:
|
|
182
|
+
email:
|
|
183
|
+
- chris.frank@future.com
|
|
184
|
+
executables: []
|
|
185
|
+
extensions: []
|
|
186
|
+
extra_rdoc_files: []
|
|
187
|
+
files:
|
|
188
|
+
- ".gitignore"
|
|
189
|
+
- ".overcommit.yml"
|
|
190
|
+
- ".reek.yml"
|
|
191
|
+
- ".rspec"
|
|
192
|
+
- ".rubocop.yml"
|
|
193
|
+
- ".travis.yml"
|
|
194
|
+
- Gemfile
|
|
195
|
+
- Gemfile.lock
|
|
196
|
+
- README.md
|
|
197
|
+
- Rakefile
|
|
198
|
+
- bin/console
|
|
199
|
+
- bin/setup
|
|
200
|
+
- lib/rack/component.rb
|
|
201
|
+
- lib/rack/component/component_cache.rb
|
|
202
|
+
- rack-component.gemspec
|
|
203
|
+
homepage: https://www.github.com/chrisfrank/rack-component
|
|
204
|
+
licenses:
|
|
205
|
+
- MIT
|
|
206
|
+
metadata:
|
|
207
|
+
allowed_push_host: https://rubygems.org
|
|
208
|
+
post_install_message:
|
|
209
|
+
rdoc_options: []
|
|
210
|
+
require_paths:
|
|
211
|
+
- lib
|
|
212
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
213
|
+
requirements:
|
|
214
|
+
- - ">="
|
|
215
|
+
- !ruby/object:Gem::Version
|
|
216
|
+
version: '2.2'
|
|
217
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
218
|
+
requirements:
|
|
219
|
+
- - ">="
|
|
220
|
+
- !ruby/object:Gem::Version
|
|
221
|
+
version: '0'
|
|
222
|
+
requirements: []
|
|
223
|
+
rubyforge_project:
|
|
224
|
+
rubygems_version: 2.7.6
|
|
225
|
+
signing_key:
|
|
226
|
+
specification_version: 4
|
|
227
|
+
summary: Compose declarative, component-based responses to HTTP requests
|
|
228
|
+
test_files: []
|