airbnb / hypernova-ruby

Ruby client for Hypernova.
https://github.com/airbnb/hypernova
MIT License
141 stars 55 forks source link
:exclamation: Deprecation Notice
We want to express our sincere gratitude for your support and contributions to the Hypernova open source project. As we are no longer using this technology internally, we have come to the decision to archive the Hypernova repositories. While we won't be providing further updates or support, the existing code and resources will remain accessible for your reference. We encourage anyone interested to fork the repository and continue the project's legacy independently. Thank you for being a part of this journey and for your patience and understanding.

hypernova-ruby Build Status

A Ruby client for the Hypernova service

Getting Started

Add this line to your application’s Gemfile:

gem 'hypernova'

And then execute:

$ bundle

Or install it yourself as:

$ gem install hypernova

In Rails, create an initializer in config/initializers/hypernova.rb.

# Really basic configuration only consists of the host and the port
Hypernova.configure do |config|
  config.host = "localhost"
  config.port = 80
end

Add an :around_filter to your controller so you can opt into Hypernova rendering of view partials.

# In my_controller.rb
require 'hypernova'

class MyController < ApplicationController
  around_filter :hypernova_render_support
end

Use the following methods to render React components in your view/templates.

<%=
  render_react_component(
    'MyComponent.js',
    :name => 'Person',
    :color => 'Blue',
    :shape => 'Triangle'
  )
%>

Configuration

You can pass more configuration options to Hypernova.

Hypernova.configure do |config|
  config.http_adapter = :patron # Use any adapter supported by Faraday
  config.host = "localhost"
  config.port = 80
  config.open_timeout = 0.1
  config.scheme = :https # Valid schemes include :http and :https
  config.timeout = 0.6
end

If you do not want to use Faraday, you can configure Hypernova Ruby to use an HTTP client that responds to post and accepts a hash argument.

Hypernova.configure do |config|
  # Use your own HTTP client!
  config.http_client = SampleHTTPClient.new
end

You can access a lower-level interface to exactly specify the parameters that are sent to the Hypernova service.

<% things.each |thing| %>
  <li>
    <%=
      hypernova_batch_render(
        :name => 'your/component/thing.bundle.js',
        :data => thing
      )
    %>
  </li>
<% end %>

You can also use the batch interface if you want to create and submit batches yourself:

batch = Hypernova::Batch.new(service)

# each job in a hypernova render batch is identified by a token
# this allows retrieval of unordered jobs
token = batch.render(
  :name => 'some_bundle.bundle.js',
  :data => {foo: 1, bar: 2}
)
token2 = batch.render(
  :name => 'some_bundle.bundle.js',
  :data => {foo: 2, bar: 1}
)
# now we can submit the batch job and await its results
# this blocks, and takes a significant time in round trips, so try to only
# use it once per request!
result = batch.submit!

# ok now we can access our rendered strings.
foo1 = result[token].html_safe
foo2 = result[token2].html_safe

Plugins

Hypernova enables you to control and alter requests at different stages of the render lifecycle via a plugin system.

Example

All methods on a plugin are optional, and they are listed in the order that they are called.

initializers/hypernova.rb:

# initializers/hypernova.rb
require 'hypernova'

class HypernovaPlugin
  # get_view_data allows you to alter the data given to any individual
  # component being rendered.
  # component is the name of the component being rendered.
  # data is the data being given to the component.
  def get_view_data(component_name, data)
    phrase_hash = data[:phrases]
    data[:phrases].keys.each do |phrase_key|
      phrase_hash[phrase_key] = "test phrase"
    end
    data
  end

  # prepare_request allows you to alter the request object in any way that you
  # need.
  # Unless manipulated by another plugin, request takes the shape:
  # { 'component_name.js': { :name => 'component_name.js', :data => {} } }
  def prepare_request(current_request, original_request)
    current_request.keys.each do |key|
      phrase_hash = req[key][:data][:phrases]
      if phrase_hash.present?
        phrase_hash.keys.each do |phrase_key|
          phrase_hash[phrase_key] = phrase_hash[phrase_key].upcase
        end
      end
    end
    current_request
  end

  # send_request? allows you to determine whether a request should continue
  # on to the hypernova server.  Returning false prevents the request from
  # occurring, and results in the fallback html.
  def send_request?(request)
    true
  end

  # after_response gives you a chance to alter the response from hypernova.
  # This will be most useful for altering the resulting html field, and special
  # handling of any potential errors.
  # res is a Hash like { 'component_name.js': { html: String, err: Error? } }
  def after_response(current_response, original_response)
    current_response.keys.each do |key|
      hash = current_response[key]
      hash['html'] = '<div>hello</div>'
    end
    current_response
  end

  # NOTE: If an error happens in here, it won’t be caught.
  def on_error(error, jobs)
    puts "Oh no, error - #{error}, jobs - #{jobs}"
  end
end

Hypernova.add_plugin!(HypernovaPlugin.new)

Development

After checking out the repo, run bin/setup to install dependencies. Then, run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release to create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

  1. Fork it ( https://github.com/airbnb/hypernova-ruby/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request