feature flagging with rails engines v0.2
-
Upload
enrico-teotti -
Category
Engineering
-
view
214 -
download
4
Transcript of feature flagging with rails engines v0.2
deploying portions of a Rails app with Engines
Enrico Teotti -- @agenteo
admin user interface
public user interface
domain logic
requirementrun the private and public portions of the app
on separate servers
service oriented architecture
first proposal
http://teotti.com/rails-service-oriented-architecture-alternative-with-components/
two apps sharing components via engines
second proposal
http://teotti.com/git-precommit-hooks-helping-local-ruby-gems-development/
one app feature flagging engines components
third proposal
public_uiadmin_ui
config/routes.rb
Rails.application.routes.draw do
case AppRunningMode.value
when :admin
mount AdminUi::Engine => "/"
when :public
mount PublicUi::Engine => "/"
else
mount AdminUi::Engine => "/"
mount PublicUi::Engine => "/"
end
end
RUNNING_MODE=public rails s
RUNNING_MODE=admin rails s
http://worldwideshipping-super-secret-domain.com/admin
http://worldwideshipping.com
rails s
http://localhost:3000
● are ruby gems● are special ruby gems that provide extra
behaviour (models, views, routes, rake tasks) to a Rails application
● they can be hosted on a gemserver or they can live inside your repository
● can be tested in isolation
rails engines
admin user interface
public user interface
domain logic
mkdir components
rails plugin new admin_ui --mountable --dummy=spec/dummy -O -T
mv admin_ui components
admin user interface
public user interface
domain logic
admin_ui
proceeding without automated tests
could drive you crazy
shared user interface (preview)
shared domain
admin_ui
Rails.application.routes.draw do
# ... public routes here
mount AdminUi::Engine => "/"
end
config/routes.rb
gem 'admin_ui', path: 'components/admin_ui'
Gemfile
public user interface
admin_ui
public user interface
admin_ui
public_ui
rails plugin new public_ui --mountable --dummy=spec/dummy -O -T
gem 'admin_ui', path: 'components/admin_ui'
gem 'public_ui', path: 'components/public_ui'
Gemfile
public_uiadmin_ui
Gem::Specification.new do |s|
# ... other fields up here
s.name = "public_ui"
s.add_dependency "rails", "~> 4.1.1"
s.add_dependency 'jquery-rails'
s.add_dependency 'mongoid'
s.add_runtime_dependency "admin_ui"
s.add_development_dependency 'byebug'
s.add_development_dependency 'database_cleaner'
s.add_development_dependency 'rspec-rails', '2.99.0'
s.add_development_dependency 'capybara'
s.add_development_dependency 'poltergeist'
end
public_ui.gemspec
Gem::Specification.new do |s|
# ... other fields up here
s.name = "public_ui"
s.add_dependency "rails", "~> 4.1.1"
s.add_dependency 'jquery-rails'
s.add_dependency 'mongoid'
s.add_runtime_dependency "admin_ui"
s.add_development_dependency 'byebug'
s.add_development_dependency 'database_cleaner'
s.add_development_dependency 'rspec-rails', '2.99.0'
s.add_development_dependency 'capybara'
s.add_development_dependency 'poltergeist'
end
public_ui.gemspec
Rails.application.routes.draw do
case AppRunningMode.value
when :admin
mount AdminUi::Engine => "/"
when :public
mount PublicUi::Engine => "/"
else
mount AdminUi::Engine => "/"
mount PublicUi::Engine => "/"
end
end
config/routes.rb
the two engines are now glued together!
admin_ui public_ui
domain_logic
rails plugin new domain_logic --dummy-path=spec/dummy
--mountable -O -T
admin_ui public_ui
shared_ui domain_logic
rails plugin new shared_ui --dummy-path=spec/dummy
--mountable -O -T
admin_ui/app/views/cargo_preview/show.html.erb
<%# admin console stuff here %>
<%= render partial: 'shared_ui/cargos/show' %>
<%# admin spaceship here %>
public_ui/app/views/cargos/show.html.erb
<%= render partial: 'shared_ui/cargos/show' %>
group :admin_app do
path 'components' do
gem 'admin_ui'
gem 'legacy_migration'
end
end
group :public_app do
path 'components' do
gem 'public_ui'
end
end
Gemfile
http://teotti.com/reduce-memory-footprint-requiring-portions-of-your-component-based-rails-applications/http://teotti.com/gemfiles-hierarchy-in-ruby-on-rails-component-based-architecture/
common pitfalls in Rails engines land
Gemfile
#gem 'domain_logic', path: 'components/domain_logic'
#gem 'shared_ui', path: 'components/shared_ui'
gem 'admin_ui', path: 'components/admin_ui'
gem 'public_ui', path: 'components/public_ui'
$ bundle
Resolving dependencies...
Could not find gem 'shared_ui (>= 0) ruby', which is required by gem
'admin_ui (>= 0) ruby', in any of the sources.
Gem::Specification.new do |s|
# ... other fields up here
s.name = "admin_ui"
s.add_dependency "rails", "~> 4.1.1"
s.add_dependency 'jquery-rails'
s.add_dependency 'mongoid'
s.add_dependency 'faraday'
s.add_dependency "domain_logic"
s.add_dependency "shared_ui"
s.add_development_dependency 'byebug'
s.add_development_dependency 'database_cleaner'
s.add_development_dependency 'rspec-rails', '2.99.0'
s.add_development_dependency 'vcr'
s.add_development_dependency 'webmock'
s.add_development_dependency 'capybara'
s.add_development_dependency 'poltergeist'
end
admin_ui.gemspec
Gem::Specification.new do |s|
# ... other fields up here
s.name = "admin_ui"
s.add_dependency "rails", "~> 4.1.1"
s.add_dependency 'jquery-rails'
s.add_dependency 'mongoid'
s.add_dependency 'faraday'
s.add_dependency "domain_logic"
s.add_dependency "shared_ui"
s.add_development_dependency 'byebug'
s.add_development_dependency 'database_cleaner'
s.add_development_dependency 'rspec-rails', '2.99.0'
s.add_development_dependency 'vcr'
s.add_development_dependency 'webmock'
s.add_development_dependency 'capybara'
s.add_development_dependency 'poltergeist'
end
admin_ui/admin_ui.gemspec
source "https://rubygems.org"
gem 'domain_logic', path: '../domain_logic'
gem 'shared_ui', path: '../shared_ui'
# Declare your gem's dependencies in admin_ui.gemspec.
# Bundler will treat runtime dependencies like base
dependencies, and
# development dependencies will be added by default to the :
development group.
gemspec
admin_ui/Gemfile
require
require 'nokogiri'
require 'faraday'
manually require
domain_logic/lib/domain_logic.rb
the test dummy app
require File.expand_path("../dummy/config/environment", __FILE__)
admin_ui/spec/rails_helper.rb
admin_ui/spec/dummy/config/boot.rb
# Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__)
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
$LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__)
Rails.application.routes.draw do
mount AdminUi::Engine => "/admin_ui"
end
admin_ui/spec /dummy/config/routes.rb
Rails.application.routes.draw do
mount AdminUi::Engine => "/admin_ui"
end
admin_ui/spec /dummy/config/routes.rb
https://github.com/shageman/the_next_big_thing/blob/master/build.sh
#!/bin/bash
unset BUNDLE_GEMFILE
result=0
if [ "$CI" == "true" ]; then
BUNDLE_PATH="$HOME/vendor/bundle"
fi
for test_script in $(find . -name test.sh); do
pushd `dirname $test_script` > /dev/null
source "$HOME/.rvm/scripts/rvm"
rvm use $(cat .ruby-version)@$(cat .ruby-gemset)
./test.sh
result+=$?
popd > /dev/null
done
if [ $result -eq 0 ]; then
echo "☺ SUCCESS"
else
echo "☹ FAILURE"
fi
exit $result
testing multiple engines
require_dependency "admin_ui/application_controller"
module AdminUi
class CargosController < ApplicationController
namespace application controller
module AdminUi
class CargosController < AdminUi::ApplicationController
namespace application controller
module AdminUi
class VoyagesController < ApplicationController
namespace application controller
module AdminUi
class VoyagesController < ApplicationController
namespace application controller
scaffolding and other generators
rails generate scaffold_controller admin_ui/cargo source destination
weight --model-name=DomainLogic::Cargo --orm=mongoid -t=''
scaffolding and other generators
require_dependency "admin_ui/domain_logic/application_controller"
scaffolding and other generators
Failures:
1) Staff booking a cargo booking a cargo fitting a pending voyage
Failure/Error: visit '/admin/cargos'
LoadError:
No such file to load -- admin_ui/domain_logic/application_controller
# ./engines/admin_ui/app/controllers/admin_ui/cargos_controller.rb:1:in `<top (required)>'
# ./spec/features/book_cargo_spec.rb:12:in `block (2 levels) in <top (required)>'
Finished in 0.03067 seconds (files took 1.85 seconds to load)
2 examples, 1 failure
scaffolding and other generators
Failures:
1) Staff booking a cargo booking a cargo fitting a pending voyage
Failure/Error: visit '/admin/cargos'
RuntimeError:
Circular dependency detected while autoloading constant AdminUi::AdminUi::CargosHelper
# ./engines/admin_ui/app/controllers/admin_ui/application_controller.rb:2:in `<module:AdminUi>'
# ./engines/admin_ui/app/controllers/admin_ui/application_controller.rb:1:in `<top (required)>'
# ./engines/admin_ui/app/controllers/admin_ui/cargos_controller.rb:1:in `<top (required)>'
# ./spec/features/book_cargo_spec.rb:12:in `block (2 levels) in <top (required)>'
scaffolding and other generators
Failures:
1) Staff booking a cargo booking a cargo fitting a pending voyage
Failure/Error: visit '/admin/cargos'
ActionView::Template::Error:
undefined local variable or method `new_domain_logic_cargo_path' for #<#<Class:0x007fb38887bb38>:0x007fb388873690>
# ./engines/admin_ui/app/views/admin_ui/cargos/index.html.erb:29:in
`_engines_admin_ui_app_views_admin_ui_cargos_index_html_erb__4343322268368929370_70204533119300'
# ./spec/features/book_cargo_spec.rb:12:in `block (2 levels) in <top (required)>'
scaffolding and other generators
fang:domain_logic agenteo$ rails generate mongoid:config
/Users/agenteo/.rvm/gems/ruby-2.1.2@worldwide_shipping/gems/mongoid-4.0.0/lib/rails/generators/mongoid/config/config_generator.rb:16:in `app_name': undefined
method `parent' for nil:NilClass (NoMethodError)
from /Users/agenteo/.rvm/gems/ruby-2.1.2@worldwide_shipping/gems/thor-0.19.1/lib/thor/command.rb:27:in `run'
from /Users/agenteo/.rvm/gems/ruby-2.1.2@worldwide_shipping/gems/thor-0.19.1/lib/thor/invocation.rb:126:in `invoke_command'
from /Users/agenteo/.rvm/gems/ruby-2.1.2@worldwide_shipping/gems/thor-0.19.1/lib/thor/invocation.rb:133:in `block in invoke_all'
from /Users/agenteo/.rvm/gems/ruby-2.1.2@worldwide_shipping/gems/thor-0.19.1/lib/thor/invocation.rb:133:in `each'
from /Users/agenteo/.rvm/gems/ruby-2.1.2@worldwide_shipping/gems/thor-0.19.1/lib/thor/invocation.rb:133:in `map'
from /Users/agenteo/.rvm/gems/ruby-2.1.2@worldwide_shipping/gems/thor-0.19.1/lib/thor/invocation.rb:133:in `invoke_all'
from /Users/agenteo/.rvm/gems/ruby-2.1.2@worldwide_shipping/gems/thor-0.19.1/lib/thor/group.rb:232:in `dispatch'
from /Users/agenteo/.rvm/gems/ruby-2.1.2@worldwide_shipping/gems/thor-0.19.1/lib/thor/base.rb:440:in `start'
from /Users/agenteo/.rvm/gems/ruby-2.1.2@worldwide_shipping/gems/railties-4.1.4/lib/rails/generators.rb:157:in `invoke'
from /Users/agenteo/.rvm/gems/ruby-2.1.2@worldwide_shipping/gems/railties-4.1.4/lib/rails/commands/generate.rb:11:in `<top (required)>'
from /Users/agenteo/.rvm/gems/ruby-2.1.2@worldwide_shipping/gems/railties-4.1.4/lib/rails/engine/commands.rb:19:in `require'
from /Users/agenteo/.rvm/gems/ruby-2.1.2@worldwide_shipping/gems/railties-4.1.4/lib/rails/engine/commands.rb:19:in `<top (required)>'
from bin/rails:12:in `require'
from bin/rails:12:in `<main>'
mongoid generators
FOLLOWUP READShttp://guides.rubyonrails.org/engines.html
https://leanpub.com/cbra
teotti.com/topics/component-based-rails-architecture
http://cbra.info/
Enrico Teotti @agenteo [email protected]