Skip to content

Commit

Permalink
Merge pull request #356 from rmosolgo/refactor-helper-lifecycle
Browse files Browse the repository at this point in the history
Refactor helper lifecycle
  • Loading branch information
Robert Mosolgo committed Sep 18, 2015
2 parents 829b4d9 + d174a3a commit 5aea60b
Show file tree
Hide file tree
Showing 12 changed files with 75 additions and 58 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

#### Bug Fixes

- Use controller lifecycle hooks for view helper (tests don't run middlewares)

## 1.3.0 (September 15, 2015)

#### Breaking Changes
Expand Down
34 changes: 17 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ rails g react:install
```

This will:
- create a `components.js` manifest file and a `app/assets/javascripts/components/` directory,
- create a `components.js` manifest file and a `app/assets/javascripts/components/` directory,
where you will put your components
- place the following in your `application.js`:

Expand All @@ -48,7 +48,7 @@ where you will put your components

### React.js builds

You can pick which React.js build (development, production, with or without [add-ons]((http://facebook.github.io/react/docs/addons.html)))
You can pick which React.js build (development, production, with or without [add-ons]((http://facebook.github.io/react/docs/addons.html)))
to serve in each environment by adding a config. Here are the defaults:

```ruby
Expand All @@ -71,10 +71,10 @@ MyApp::Application.configure do
end
```

After restarting your Rails server, `//= require react` will provide the build of React.js which
After restarting your Rails server, `//= require react` will provide the build of React.js which
was specified by the configurations.

`react-rails` offers a few other options for versions & builds of React.js.
`react-rails` offers a few other options for versions & builds of React.js.
See [VERSIONS.md](https://github.com/reactjs/react-rails/blob/master/VERSIONS.md) for more info about
using the `react-source` gem or dropping in your own copies of React.js.

Expand Down Expand Up @@ -121,7 +121,7 @@ config.react.jsx_transform_options = {

### Rendering & mounting

`react-rails` includes a view helper (`react_component`) and an unobtrusive JavaScript driver (`react_ujs`)
`react-rails` includes a view helper (`react_component`) and an unobtrusive JavaScript driver (`react_ujs`)
which work together to put React components on the page. You should require the UJS driver
in your manifest after `react` (and after `turbolinks` if you use [Turbolinks](https://github.com/rails/turbolinks)).

Expand All @@ -133,7 +133,7 @@ The __view helper__ puts a `div` on the page with the requested component class
<div data-react-class="HelloMessage" data-react-props="{&quot;name&quot;:&quot;John&quot;}"></div>
```

On page load, the __`react_ujs` driver__ will scan the page and mount components using `data-react-class`
On page load, the __`react_ujs` driver__ will scan the page and mount components using `data-react-class`
and `data-react-props`.

If Turbolinks is present components are mounted on the `page:change` event, and unmounted on `page:before-unload`.
Expand Down Expand Up @@ -169,9 +169,9 @@ _(It will be also be mounted by the UJS on page load.)_

There are some requirements for this to work:

- `react-rails` must load your code. By convention it uses `components.js`, which was created
- `react-rails` must load your code. By convention it uses `components.js`, which was created
by the install task. This file must include your components _and_ their dependencies (eg, Underscore.js).
- Your components must be accessible in the global scope.
- Your components must be accessible in the global scope.
If you are using `.js.jsx.coffee` files then the wrapper function needs to be taken into account:

```coffee
Expand All @@ -180,7 +180,7 @@ If you are using `.js.jsx.coffee` files then the wrapper function needs to be ta
render: ->
`<ExampleComponent videos={this.props.videos} />`
```
- Your code can't reference `document`. Prerender processes don't have access to `document`,
- Your code can't reference `document`. Prerender processes don't have access to `document`,
so jQuery and some other libs won't work in this environment :(

You can configure your pool of JS virtual machines and specify where it should load code:
Expand Down Expand Up @@ -222,10 +222,10 @@ By default, your current layout will be used and the component, rather than a vi

### Component generator

`react-rails` ships with a Rails generator to help you get started with a simple component scaffold.
You can run it using `rails generate react:component ComponentName (--es6)`.
The generator takes an optional list of arguments for default propTypes,
which follow the conventions set in the [Reusable Components](http://facebook.github.io/react/docs/reusable-components.html)
`react-rails` ships with a Rails generator to help you get started with a simple component scaffold.
You can run it using `rails generate react:component ComponentName (--es6)`.
The generator takes an optional list of arguments for default propTypes,
which follow the conventions set in the [Reusable Components](http://facebook.github.io/react/docs/reusable-components.html)
section of the React documentation.

For example:
Expand Down Expand Up @@ -294,7 +294,7 @@ Note that the arguments for `oneOf` and `oneOfType` must be enclosed in single q

### Jbuilder & react-rails

If you use Jbuilder to pass a JSON string to `react_component`, make sure your JSON is a stringified hash,
If you use Jbuilder to pass a JSON string to `react_component`, make sure your JSON is a stringified hash,
not an array. This is not the Rails default -- you should add the root node yourself. For example:

```ruby
Expand All @@ -313,7 +313,7 @@ end

## CoffeeScript

It is possible to use JSX with CoffeeScript. To use CoffeeScript, create files with an extension `.js.jsx.coffee`.
It is possible to use JSX with CoffeeScript. To use CoffeeScript, create files with an extension `.js.jsx.coffee`.
We also need to embed JSX code inside backticks so that CoffeeScript ignores the syntax it doesn't understand.
Here's an example:

Expand Down Expand Up @@ -348,8 +348,8 @@ Any subclass of `ExecJSRenderer` may use those hooks (for example, `SprocketsRen
`react-rails` uses a "helper implementation" class to generate the output of the `react_component` helper. The helper is initialized once per request and used for each `react_component` call during that request. You can provide a custom helper class to `config.react.view_helper_implementation`. The class must implement:

- `#react_component(name, props = {}, options = {}, &block)` to return a string to inject into the Rails view
- `#setup(rack_env)`, called when the helper is initialized at the start of the request
- `#teardown(rack_env)`, called at the end of the request
- `#setup(controller_instance)`, called when the helper is initialized at the start of the request
- `#teardown(controller_instance)`, called at the end of the request

`react-rails` provides one implementation, `React::Rails::ComponentMount`.

Expand Down
1 change: 0 additions & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ Rake::TestTask.new(:test) do |t|
t.libs << 'test'
t.pattern = ENV['TEST_PATTERN'] || 'test/**/*_test.rb'
t.verbose = ENV['TEST_VERBOSE'] == '1'
t.warning = true
end

task default: :test
2 changes: 1 addition & 1 deletion lib/react/rails.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
require 'react/rails/asset_variant'
require 'react/rails/engine'
require 'react/rails/railtie'
require 'react/rails/render_middleware'
require 'react/rails/controller_lifecycle'
require 'react/rails/version'
require 'react/rails/component_mount'
require 'react/rails/view_helper'
Expand Down
2 changes: 1 addition & 1 deletion lib/react/rails/component_mount.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class ComponentMount
include ActionView::Helpers::TextHelper
attr_accessor :output_buffer

# RenderMiddleware calls these hooks
# ControllerLifecycle calls these hooks
# You can use them in custom helper implementations
def setup(env)
end
Expand Down
24 changes: 24 additions & 0 deletions lib/react/rails/controller_lifecycle.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module React
module Rails
module ControllerLifecycle
extend ActiveSupport::Concern

included do
# use old names to support Rails 3
before_filter :setup_react_component_helper
after_filter :teardown_react_component_helper
attr_reader :__react_component_helper
end

def setup_react_component_helper
new_helper = React::Rails::ViewHelper.helper_implementation_class.new
new_helper.setup(self)
@__react_component_helper = new_helper
end

def teardown_react_component_helper
@__react_component_helper.teardown(self)
end
end
end
end
4 changes: 2 additions & 2 deletions lib/react/rails/controller_renderer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ class React::Rails::ControllerRenderer

attr_accessor :output_buffer

attr_reader :request
def initialize(options={})
@request = options[:request]
controller = options[:controller]
@__react_component_helper = controller.__react_component_helper
end

def call(name, options, &block)
Expand Down
9 changes: 7 additions & 2 deletions lib/react/rails/railtie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,26 @@ class Railtie < ::Rails::Railtie

# Include the react-rails view helper lazily
initializer "react_rails.setup_view_helpers", group: :all do |app|
app.config.middleware.use(::React::Rails::RenderMiddleware)

app.config.react.jsx_transformer_class ||= React::JSX::DEFAULT_TRANSFORMER
React::JSX.transformer_class = app.config.react.jsx_transformer_class
React::JSX.transform_options = app.config.react.jsx_transform_options

app.config.react.view_helper_implementation ||= React::Rails::ComponentMount
React::Rails::ViewHelper.helper_implementation_class = app.config.react.view_helper_implementation

ActiveSupport.on_load(:action_controller) do
include ::React::Rails::ControllerLifecycle
end

ActiveSupport.on_load(:action_view) do
include ::React::Rails::ViewHelper
end
end

initializer "react_rails.add_component_renderer", group: :all do |app|
ActionController::Renderers.add :component do |component_name, options|
renderer = ::React::Rails::ControllerRenderer.new(request: request)
renderer = ::React::Rails::ControllerRenderer.new(controller: self)
html = renderer.call(component_name, options)
render_options = options.merge(inline: html)
render(render_options)
Expand Down
19 changes: 0 additions & 19 deletions lib/react/rails/render_middleware.rb

This file was deleted.

7 changes: 3 additions & 4 deletions lib/react/rails/view_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@ module Rails
module ViewHelper
# This class will be used for inserting tags into HTML.
# It should implement:
# - #setup(env)
# - #teardown(env)
# - #setup(controller_instance)
# - #teardown(controller_instance)
# - #react_component(name, props, options &block)
# The default is {React::Rails::ComponentMount}
mattr_accessor :helper_implementation_class

def react_component(*args, &block)
impl_key = React::Rails::RenderMiddleware::HELPER_IMPLEMENTATION_KEY
helper_obj = request.env[impl_key]
helper_obj = @__react_component_helper
helper_obj.react_component(*args, &block)
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
# calls to `react_component`
class DummyHelperImplementation
attr_reader :events

def initialize
@events = []
end

def setup(env)
@events << :setup
def setup(controller)
@events << (controller.params["param_test"] || :setup)
end

def teardown(env)
Expand All @@ -21,9 +22,7 @@ def react_component(*args)
end
end

class RenderMiddlewareTest < ActionDispatch::IntegrationTest
impl_key = React::Rails::RenderMiddleware::HELPER_IMPLEMENTATION_KEY

class ControllerLifecycleTest < ActionDispatch::IntegrationTest
def setup
@previous_helper_implementation = React::Rails::ViewHelper.helper_implementation_class
React::Rails::ViewHelper.helper_implementation_class = DummyHelperImplementation
Expand All @@ -35,22 +34,22 @@ def teardown

test "it creates a helper object and puts it in the request env" do
get '/pages/1'
helper_obj = request.env[impl_key]
helper_obj = controller.__react_component_helper
assert(helper_obj.is_a?(DummyHelperImplementation), "It uses the view helper implementation class")
end

test "it calls setup and teardown methods" do
get '/pages/1'
helper_obj = request.env[impl_key]
lifecycle_steps = [:setup, :react_component, :teardown]
get '/pages/1?param_test=123'
helper_obj = controller.__react_component_helper
lifecycle_steps = ["123", :react_component, :teardown]
assert_equal(lifecycle_steps, helper_obj.events)
end

test "there's a new helper object for every request" do
get '/pages/1'
first_helper = request.env[impl_key]
first_helper = controller.__react_component_helper
get '/pages/1'
second_helper = request.env[impl_key]
second_helper = controller.__react_component_helper
assert(first_helper != second_helper, "The helper for the second request is brand new")
end
end
8 changes: 8 additions & 0 deletions test/react/rails/pages_controller_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
require 'test_helper'

class PagesControllerTest < ActionController::TestCase
test 'renders successfully' do
get :show, id: 1
assert_equal(200, response.status)
end
end

0 comments on commit 5aea60b

Please sign in to comment.