diff --git a/README.md b/README.md index 69dd32c3..9c59ea8c 100644 --- a/README.md +++ b/README.md @@ -200,6 +200,93 @@ watch them for changes as well. directories %w[app config lib spec your-appname/app] ``` +## Integrating with `ember-cli-deploy` + +The EmberCLI community recently unified the various deployment techniques into a +single, core-team supported project: [ember-cli-deploy][ember-cli-deploy]. + +This project attempts to streamline the process of pushing and serving +EmberCLI-built static assets. + +To integrate with `ember-cli-deploy`'s ["Lightning Fast Deploys"][lightning] +(using the Redis adapter), instantiate an `EmberCLI::Deploy` in your controller: + +```ruby +require "ember-cli/deploy" + +class ApplicationController < ActionController::Base + def index + @deploy = EmberCLI::Deploy.new(namespace: "frontend") + + render text: @deploy.html, layout: false + end +end +``` + +`EmberCLI::Deploy` takes a `namespace` (the name of your app declared in your +initializer) and handles all interaction with the Redis instance. + +This is great for `staging` and `production` deploys, but introduces an extra +step in the feedback loop during development. + +Luckily, `EmberCLI::Deploy` also accepts an `index_html` override, which will +replace the call to the Redis instance. This allows integration with the normal +`ember-cli-rails` workflow: + +```ruby +require "ember-cli/deploy" + +class ApplicationController < ActionController::Base + def index + @deploy = EmberCLI::Deploy.new( + namespace: "frontend", + index_html: index_html, + ) + + render text: @deploy.html, layout: false + end + + private + + def index_html + if serve_with_ember_cli_rails? + render_to_string(:index) + end + end + + def serve_with_ember_cli_rails? + ! %w[production staging].include?(Rails.env) + end +end +``` + +Additionally, having access to the outbound HTML beforehand also enables +controllers to inject additional markup, such as metadata, CSRF tokens, or +analytics tags: + + +```ruby +require "ember-cli/deploy" + +class ApplicationController < ActionController::Base + def index + @deploy = EmberCLI::Deploy.new( + namespace: "frontend", + index_html: index_html, + ) + + @deploy.append_to_head(render_to_string(partial: "my_csrf_and_metadata") + @deploy.append_to_body(render_to_string(partial: "my_analytics") + + render text: @deploy.html, layout: false + end + # ... +end +``` + +[ember-cli-deploy]: https://github.com/ember-cli/ember-cli-deploy +[lightning]: https://github.com/ember-cli/ember-cli-deploy#lightning-approach-workflow + ## Heroku In order to deploy EmberCLI Rails app to Heroku: diff --git a/ember-cli-rails.gemspec b/ember-cli-rails.gemspec index 5ff054f8..2a5ca869 100644 --- a/ember-cli-rails.gemspec +++ b/ember-cli-rails.gemspec @@ -13,5 +13,10 @@ Gem::Specification.new do |spec| spec.required_ruby_version = ">= 1.9.3" spec.add_dependency "railties", ">= 3.1", "< 5" + spec.add_dependency "redis" spec.add_dependency "sprockets", ">= 2.0" + + spec.add_development_dependency "rspec", "~> 3.1.0" + spec.add_development_dependency "climate_control" + spec.add_development_dependency "fakeredis" end diff --git a/lib/ember-cli/deploy.rb b/lib/ember-cli/deploy.rb new file mode 100644 index 00000000..20fb399d --- /dev/null +++ b/lib/ember-cli/deploy.rb @@ -0,0 +1,79 @@ +require "active_support/core_ext/object/blank" +require "ember-cli/page" + +module EmberCLI + class Deploy + def initialize(namespace:, index_html: nil) + @namespace = namespace + @index_html = index_html + @body_markup = [] + @head_markup = [] + end + + def append_to_body(markup) + body_markup << markup + end + + def append_to_head(markup) + head_markup << markup + end + + def html + if index_html.present? + page = Page.new(html: index_html) + + body_markup.each do |markup| + page.append_to_body(markup) + end + + head_markup.each do |markup| + page.append_to_head(markup) + end + + page.build + else + index_html_missing! + end + end + + private + + attr_reader :body_markup, :head_markup, :namespace + + def index_html + @index_html ||= redis.get(deploy_key).presence + end + + def current_key + "#{namespace}:current" + end + + def deploy_key + redis.get(current_key).presence || deployment_not_activated! + end + + def redis + Redis.new(url: ENV.fetch("REDIS_URL")) + end + + def index_html_missing! + message = <<-FAIL + HTML for #{deploy_key} is missing. + + Did you forget to call `ember deploy`? + FAIL + + raise KeyError, message + end + + def deployment_not_activated! + message = <<-FAIL + #{current_key} is empty. + + Did you forget to call `ember deploy:activate`? + FAIL + + raise KeyError, message + end + end +end diff --git a/lib/ember-cli/page.rb b/lib/ember-cli/page.rb new file mode 100644 index 00000000..b9589700 --- /dev/null +++ b/lib/ember-cli/page.rb @@ -0,0 +1,53 @@ +module EmberCLI + class Page + def initialize(html:) + @html = html.clone + @body_markup = [] + @head_markup = [] + end + + def build + if has_head? + head_markup.each do |markup| + html.insert(head_position, markup) + end + end + + if has_body? + body_markup.each do |markup| + html.insert(body_position, markup) + end + end + + html + end + + def append_to_body(markup) + body_markup << markup + end + + def append_to_head(markup) + head_markup << markup + end + + private + + attr_reader :body_markup, :head_markup, :html + + def has_head? + html.include?("
", html.index("", html.index(" tag" do + provided_html = "" + ember_cli_deploy = build_ember_cli_deploy(index_html: provided_html) + + ember_cli_deploy.append_to_head("") + index_html = ember_cli_deploy.html + + expect(index_html).to eq("") + end + end + + describe "#append_to_body" do + it "injects the string into the tag" do + provided_html = "" + ember_cli_deploy = build_ember_cli_deploy(index_html: provided_html) + + ember_cli_deploy.append_to_body("") + index_html = ember_cli_deploy.html + + expect(index_html).to eq("") + end + end + + describe "#html" do + context "when the HTML is provided" do + it "returns the HTML" do + provided_html = "Hello World
" + ember_cli_deploy = build_ember_cli_deploy(index_html: provided_html) + + index_html = ember_cli_deploy.html + + expect(index_html).to eq(provided_html) + end + end + + context "when the keys are present" do + it "retrieves the HTML from Redis" do + stub_index_html(html: "Hello World
") + ember_cli_deploy = build_ember_cli_deploy + + index_html = ember_cli_deploy.html + + expect(index_html).to eq("Hello World
") + end + end + + context "when the current index is missing" do + it "raises a helpful exception" do + deploy_key = "#{namespace}:abc123" + stub_index_html(html: nil, deploy_key: deploy_key) + ember_cli_deploy = build_ember_cli_deploy + + expect { ember_cli_deploy.html }.to raise_error( + /HTML for #{deploy_key} is missing/ + ) + end + end + + context "when the current key is unset" do + it "raises a helpful exception" do + stub_current_key(nil) + ember_cli_deploy = build_ember_cli_deploy + + expect { ember_cli_deploy.html }.to raise_error( + /#{namespace}:current is empty/ + ) + end + end + end + + around :each do |example| + with_modified_env REDIS_URL: "redis://localhost:1234" do + example.run + end + end + + def build_ember_cli_deploy(index_html: nil) + EmberCLI::Deploy.new(namespace: namespace, index_html: index_html) + end + + def namespace + "human-health" + end + + def stub_current_key(deploy_key) + current_key = "#{namespace}:current" + + redis.set(current_key, deploy_key) + end + + def stub_index_html(deploy_key: "#{namespace}:123", html:) + stub_current_key(deploy_key) + redis.set(deploy_key, html) + end + + def redis + Redis.new(url: ENV["REDIS_URL"]) + end + + def with_modified_env(options, &block) + ClimateControl.modify(options, &block) + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 00000000..679ad690 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,2 @@ +require "fakeredis" +require "climate_control"