Skip to content

Commit 04d7260

Browse files
tommydangerousljharb
authored andcommitted
Initial import
1 parent df0617d commit 04d7260

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+2219
-2
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,11 @@ build/
2828

2929
# for a library or gem, you might want to ignore these files since the code is
3030
# intended to run in multiple environments; otherwise, check them in:
31-
# Gemfile.lock
31+
Gemfile.lock
3232
# .ruby-version
3333
# .ruby-gemset
3434

3535
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
3636
.rvmrc
37+
38+
mystique-ruby-*.gem

.travis.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
language: ruby
2+
rvm:
3+
- 1.9.3
4+
script:
5+
- bundle exec rspec

Gemfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
source 'https://rubygems.org'
2+
3+
# Specify your gem's dependencies in hypernova.gemspec
4+
gemspec

README.md

Lines changed: 211 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,211 @@
1-
# hypernova-ruby
1+
# hypernova-ruby [![Build Status](https://travis-ci.org/airbnb/hypernova-ruby.svg)](https://travis-ci.org/airbnb/hypernova-ruby)
2+
3+
> A Ruby client for the Hypernova service
4+
5+
## Getting Started
6+
7+
Add this line to your application’s Gemfile:
8+
9+
```ruby
10+
gem 'hypernova'
11+
```
12+
13+
And then execute:
14+
15+
$ bundle
16+
17+
Or install it yourself as:
18+
19+
$ gem install hypernova
20+
21+
22+
In Rails, create an initializer in `config/initializers/hypernova.rb`.
23+
24+
```ruby
25+
# Really basic configuration only consists of the host and the port
26+
Hypernova.configure do |config|
27+
config.host = "localhost"
28+
config.port = 80
29+
end
30+
```
31+
32+
Add an `:around_filter` to your controller so you can opt into Hypernova rendering of view partials.
33+
34+
```ruby
35+
# In my_controller.rb
36+
require 'hypernova'
37+
38+
class MyController < ApplicationController
39+
around_filter :hypernova_render_support
40+
end
41+
```
42+
43+
Use the following methods to render React components in your view/templates.
44+
45+
```erb
46+
<%=
47+
render_react_component(
48+
'MyComponent.js',
49+
:name => 'Person',
50+
:color => 'Blue',
51+
:shape => 'Triangle'
52+
)
53+
%>
54+
```
55+
56+
## Configuration
57+
58+
You can pass more configuration options to Hypernova.
59+
60+
```ruby
61+
Hypernova.configure do |config|
62+
config.http_adapter = :patron # Use any adapter supported by Faraday
63+
config.host = "localhost"
64+
config.port = 80
65+
config.open_timeout = 0.1
66+
config.scheme = :https # Valid schemes include :http and :https
67+
config.timeout = 0.6
68+
end
69+
```
70+
71+
If you do not want to use `Faraday`, you can configure Hypernova Ruby to use an HTTP client that
72+
responds to `post` and accepts a hash argument.
73+
74+
```ruby
75+
Hypernova.configure do |config|
76+
# Use your own HTTP client!
77+
config.http_client = SampleHTTPClient.new
78+
end
79+
```
80+
81+
You can access a lower-level interface to exactly specify the parameters that are sent to the
82+
Hypernova service.
83+
84+
```erb
85+
<% things.each |thing| %>
86+
<li>
87+
<%=
88+
hypernova_batch_render(
89+
:name => 'your/component/thing.bundle.js',
90+
:data => thing
91+
)
92+
%>
93+
</li>
94+
<% end %>
95+
```
96+
97+
You can also use the batch interface if you want to create and submit batches yourself:
98+
99+
```ruby
100+
batch = Hypernova::Batch.new(service)
101+
102+
# each job in a hypernova render batch is identified by a token
103+
# this allows retrieval of unordered jobs
104+
token = batch.render(
105+
:name => 'some_bundle.bundle.js',
106+
:data => {foo: 1, bar: 2}
107+
)
108+
token2 = batch.render(
109+
:name => 'some_bundle.bundle.js',
110+
:data => {foo: 2, bar: 1}
111+
)
112+
# now we can submit the batch job and await its results
113+
# this blocks, and takes a significant time in round trips, so try to only
114+
# use it once per request!
115+
result = batch.submit!
116+
117+
# ok now we can access our rendered strings.
118+
foo1 = result[token].html_safe
119+
foo2 = result[token2].html_safe
120+
```
121+
122+
## Plugins
123+
124+
Hypernova enables you to control and alter requests at different stages of
125+
the render lifecycle via a plugin system.
126+
127+
### Example
128+
129+
All methods on a plugin are optional, and they are listed in the order that
130+
they are called.
131+
132+
**initializers/hypernova.rb:**
133+
```ruby
134+
# initializers/hypernova.rb
135+
require 'hypernova'
136+
137+
class HypernovaPlugin
138+
# get_view_data allows you to alter the data given to any individual
139+
# component being rendered.
140+
# component is the name of the component being rendered.
141+
# data is the data being given to the component.
142+
def get_view_data(component_name, data)
143+
phrase_hash = data[:phrases]
144+
data[:phrases].keys.each do |phrase_key|
145+
phrase_hash[phrase_key] = "test phrase"
146+
end
147+
data
148+
end
149+
150+
# prepare_request allows you to alter the request object in any way that you
151+
# need.
152+
# Unless manipulated by another plugin, request takes the shape:
153+
# { 'component_name.js': { :name => 'component_name.js', :data => {} } }
154+
def prepare_request(current_request, original_request)
155+
current_request.keys.each do |key|
156+
phrase_hash = req[key][:data][:phrases]
157+
if phrase_hash.present?
158+
phrase_hash.keys.each do |phrase_key|
159+
phrase_hash[phrase_key] = phrase_hash[phrase_key].upcase
160+
end
161+
end
162+
end
163+
current_request
164+
end
165+
166+
# send_request? allows you to determine whether a request should continue
167+
# on to the hypernova server. Returning false prevents the request from
168+
# occurring, and results in the fallback html.
169+
def send_request?(request)
170+
true
171+
end
172+
173+
# after_response gives you a chance to alter the response from hypernova.
174+
# This will be most useful for altering the resulting html field, and special
175+
# handling of any potential errors.
176+
# res is a Hash like { 'component_name.js': { html: String, err: Error? } }
177+
def after_response(current_response, original_response)
178+
current_response.keys.each do |key|
179+
hash = current_response[key]
180+
hash['html'] = '<div>hello</div>'
181+
end
182+
current_response
183+
end
184+
185+
# NOTE: If an error happens in here, it won’t be caught.
186+
def on_error(error, jobs)
187+
puts "Oh no, error - #{error}, jobs - #{jobs}"
188+
end
189+
end
190+
191+
Hypernova.add_plugin!(HypernovaPlugin.new)
192+
```
193+
194+
## Development
195+
196+
After checking out the repo, run `bin/setup` to install dependencies. Then, run
197+
`bin/console` for an interactive prompt that will allow you to experiment.
198+
199+
To install this gem onto your local machine, run `bundle exec rake install`. To
200+
release a new version, update the version number in `version.rb`, and then run
201+
`bundle exec rake release` to create a git tag for the version, push git
202+
commits and tags, and push the `.gem` file to
203+
[rubygems.org](https://rubygems.org).
204+
205+
## Contributing
206+
207+
1. Fork it ( https://github.com/[my-github-username]/hypernova/fork )
208+
2. Create your feature branch (`git checkout -b my-new-feature`)
209+
3. Commit your changes (`git commit -am 'Add some feature'`)
210+
4. Push to the branch (`git push origin my-new-feature`)
211+
5. Create a new Pull Request

Rakefile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
require "bundler/gem_tasks"
2+
3+
begin
4+
require 'rspec/core/rake_task'
5+
RSpec::Core::RakeTask.new(:spec)
6+
rescue LoadError
7+
end

bin/console

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/usr/bin/env ruby
2+
3+
require "bundler/setup"
4+
require "hypernova"
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

bin/setup

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
IFS=$'\n\t'
4+
5+
bundle install
6+
7+
# Do any other automated setup that you need to do here

hypernova.gemspec

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# coding: utf-8
2+
lib = File.expand_path("../lib", __FILE__)
3+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4+
require "hypernova/version"
5+
6+
Gem::Specification.new do |spec|
7+
spec.authors = %w(
8+
Jake Teton-Landis
9+
Jordan Harband
10+
Ian Christian Myers
11+
Tommy Dang
12+
)
13+
spec.bindir = "exe"
14+
spec.description = "A Ruby client for the Hypernova service"
15+
spec.email = %w(
16+
17+
18+
19+
20+
)
21+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
23+
spec.homepage = 'https://github.com/airbnb/hypernova-ruby'
24+
spec.license = 'MIT'
25+
spec.name = 'hypernova'
26+
spec.require_paths = ["lib"]
27+
spec.summary = %q{Batch interface for Hypernova, the React render service.}
28+
spec.version = Hypernova::VERSION
29+
30+
if spec.respond_to?(:metadata)
31+
spec.metadata["allowed_push_host"] = 'https://rubygems.org'
32+
end
33+
34+
spec.add_development_dependency "bundler", "~> 1.9"
35+
spec.add_development_dependency "rake", "~> 10.0"
36+
spec.add_development_dependency "rspec", "~> 3.4"
37+
spec.add_development_dependency "simplecov", "~> 0.11"
38+
spec.add_development_dependency "pry", "~> 0.10"
39+
spec.add_development_dependency "webmock", "~> 2.0"
40+
41+
spec.add_runtime_dependency "faraday", "~> 0.8"
42+
end

lib/hypernova.rb

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
require "hypernova/batch"
2+
require "hypernova/configuration"
3+
require "hypernova/rails/action_controller"
4+
require "hypernova/version"
5+
6+
module Hypernova
7+
# thrown by ControllerHelper methods if you don't call hypernova_batch_before first
8+
class NilBatchError < StandardError; end
9+
10+
# thrown by Batch#render if your job doesn't have the right keys and stuff.
11+
class BadJobError < StandardError; end
12+
13+
class << self
14+
attr_accessor :configuration
15+
end
16+
17+
def self.configure
18+
self.configuration ||= Hypernova::Configuration.new
19+
yield(configuration)
20+
end
21+
22+
# TODO: more interesting token format?
23+
RENDER_TOKEN_REGEX = /__hypernova_render_token\[\w+\]__/
24+
25+
def self.render_token(batch_token)
26+
"__hypernova_render_token[#{batch_token}]__"
27+
end
28+
29+
def self.plugins
30+
@plugins ||= []
31+
end
32+
33+
def self.add_plugin!(plugin)
34+
plugins << plugin
35+
end
36+
37+
##
38+
# replace all hypernova tokens in `body` with the render results given by batch_result,
39+
# using render_token_to_batch_token to map render tokens into batch tokens
40+
# @param [String] body
41+
# @param [Hash] render_token_to_batch_token
42+
# @param respond_to(:[]) batch_result
43+
def self.replace_tokens_with_result(body, render_token_to_batch_token, batch_result)
44+
# replace all render tokens in the current response body with the
45+
# hypernova result for that render.
46+
return body.gsub(RENDER_TOKEN_REGEX) do |render_token|
47+
batch_token = render_token_to_batch_token[render_token]
48+
if batch_token.nil?
49+
next render_token
50+
end
51+
render_result = batch_result[batch_token]
52+
# replace with that render result.
53+
next render_result
54+
end
55+
end
56+
57+
##
58+
# raises a BadJobError if the job hash is not of the right shape.
59+
def self.verify_job_shape(job)
60+
[:name, :data].each do |key|
61+
if job[key].nil?
62+
raise BadJobError.new("Hypernova render jobs must have key #{key}")
63+
end
64+
end
65+
end
66+
end

0 commit comments

Comments
 (0)