diff --git a/CHANGELOG.md b/CHANGELOG.md index bb2a9476..f10bb900 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index 4320d2af..6e05eeca 100644 --- a/README.md +++ b/README.md @@ -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`: @@ -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 @@ -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. @@ -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)). @@ -133,7 +133,7 @@ The __view helper__ puts a `div` on the page with the requested component class
``` -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`. @@ -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 @@ -180,7 +180,7 @@ If you are using `.js.jsx.coffee` files then the wrapper function needs to be ta render: -> `` ``` -- 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: @@ -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: @@ -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 @@ -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: @@ -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`. diff --git a/Rakefile b/Rakefile index 117ee384..2b33d706 100644 --- a/Rakefile +++ b/Rakefile @@ -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 diff --git a/lib/react/rails.rb b/lib/react/rails.rb index 788b610c..a1a98507 100644 --- a/lib/react/rails.rb +++ b/lib/react/rails.rb @@ -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' diff --git a/lib/react/rails/component_mount.rb b/lib/react/rails/component_mount.rb index 506d3391..9b8f1db4 100644 --- a/lib/react/rails/component_mount.rb +++ b/lib/react/rails/component_mount.rb @@ -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 diff --git a/lib/react/rails/controller_lifecycle.rb b/lib/react/rails/controller_lifecycle.rb new file mode 100644 index 00000000..d8f370a6 --- /dev/null +++ b/lib/react/rails/controller_lifecycle.rb @@ -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 diff --git a/lib/react/rails/controller_renderer.rb b/lib/react/rails/controller_renderer.rb index b78a87e5..aaaa2cbc 100644 --- a/lib/react/rails/controller_renderer.rb +++ b/lib/react/rails/controller_renderer.rb @@ -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) diff --git a/lib/react/rails/railtie.rb b/lib/react/rails/railtie.rb index 0605fe1a..c90d1667 100644 --- a/lib/react/rails/railtie.rb +++ b/lib/react/rails/railtie.rb @@ -24,13 +24,18 @@ 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 @@ -38,7 +43,7 @@ class Railtie < ::Rails::Railtie 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) diff --git a/lib/react/rails/render_middleware.rb b/lib/react/rails/render_middleware.rb deleted file mode 100644 index e038cc3b..00000000 --- a/lib/react/rails/render_middleware.rb +++ /dev/null @@ -1,19 +0,0 @@ -module React - module Rails - class RenderMiddleware - HELPER_IMPLEMENTATION_KEY = "react_rails.view_helper_implementation" - def initialize(app) - @app = app - end - - def call(env) - new_helper = React::Rails::ViewHelper.helper_implementation_class.new - new_helper.setup(env) - env[HELPER_IMPLEMENTATION_KEY] = new_helper - response = @app.call(env) - new_helper.teardown(env) - response - end - end - end -end diff --git a/lib/react/rails/view_helper.rb b/lib/react/rails/view_helper.rb index eeca5dc4..d3fc7dc4 100644 --- a/lib/react/rails/view_helper.rb +++ b/lib/react/rails/view_helper.rb @@ -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 diff --git a/test/react/rails/render_middleware_test.rb b/test/react/rails/controller_lifecycle_test.rb similarity index 71% rename from test/react/rails/render_middleware_test.rb rename to test/react/rails/controller_lifecycle_test.rb index 35848c6f..2b5c236f 100644 --- a/test/react/rails/render_middleware_test.rb +++ b/test/react/rails/controller_lifecycle_test.rb @@ -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) @@ -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 @@ -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 diff --git a/test/react/rails/pages_controller_test.rb b/test/react/rails/pages_controller_test.rb new file mode 100644 index 00000000..89badd4a --- /dev/null +++ b/test/react/rails/pages_controller_test.rb @@ -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