feature flagging with rails engines v0.2
-
Upload
enrico-teotti -
Category
Engineering
-
view
209 -
download
4
Transcript of feature flagging with rails engines v0.2
![Page 1: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/1.jpg)
deploying portions of a Rails app with Engines
Enrico Teotti -- @agenteo
![Page 2: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/2.jpg)
admin user interface
public user interface
domain logic
![Page 3: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/3.jpg)
requirementrun the private and public portions of the app
on separate servers
![Page 4: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/4.jpg)
service oriented architecture
first proposal
http://teotti.com/rails-service-oriented-architecture-alternative-with-components/
![Page 5: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/5.jpg)
two apps sharing components via engines
second proposal
http://teotti.com/git-precommit-hooks-helping-local-ruby-gems-development/
![Page 6: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/6.jpg)
one app feature flagging engines components
third proposal
![Page 7: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/7.jpg)
public_uiadmin_ui
![Page 8: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/8.jpg)
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
![Page 9: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/9.jpg)
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
![Page 10: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/10.jpg)
● 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
![Page 11: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/11.jpg)
admin user interface
public user interface
domain logic
![Page 12: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/12.jpg)
mkdir components
![Page 13: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/13.jpg)
rails plugin new admin_ui --mountable --dummy=spec/dummy -O -T
mv admin_ui components
![Page 14: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/14.jpg)
admin user interface
public user interface
domain logic
admin_ui
![Page 15: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/15.jpg)
proceeding without automated tests
could drive you crazy
![Page 16: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/16.jpg)
shared user interface (preview)
shared domain
admin_ui
![Page 17: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/17.jpg)
Rails.application.routes.draw do
# ... public routes here
mount AdminUi::Engine => "/"
end
config/routes.rb
![Page 18: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/18.jpg)
gem 'admin_ui', path: 'components/admin_ui'
Gemfile
![Page 19: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/19.jpg)
public user interface
admin_ui
![Page 20: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/20.jpg)
public user interface
admin_ui
public_ui
![Page 21: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/21.jpg)
rails plugin new public_ui --mountable --dummy=spec/dummy -O -T
![Page 22: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/22.jpg)
gem 'admin_ui', path: 'components/admin_ui'
gem 'public_ui', path: 'components/public_ui'
Gemfile
![Page 23: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/23.jpg)
public_uiadmin_ui
![Page 24: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/24.jpg)
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
![Page 25: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/25.jpg)
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
![Page 26: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/26.jpg)
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
![Page 27: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/27.jpg)
the two engines are now glued together!
![Page 28: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/28.jpg)
admin_ui public_ui
domain_logic
![Page 29: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/29.jpg)
rails plugin new domain_logic --dummy-path=spec/dummy
--mountable -O -T
![Page 30: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/30.jpg)
admin_ui public_ui
shared_ui domain_logic
![Page 31: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/31.jpg)
rails plugin new shared_ui --dummy-path=spec/dummy
--mountable -O -T
![Page 32: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/32.jpg)
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' %>
![Page 33: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/33.jpg)
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/
![Page 34: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/34.jpg)
common pitfalls in Rails engines land
![Page 35: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/35.jpg)
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'
![Page 36: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/36.jpg)
$ 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.
![Page 37: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/37.jpg)
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
![Page 38: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/38.jpg)
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
![Page 39: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/39.jpg)
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
![Page 40: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/40.jpg)
require
![Page 41: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/41.jpg)
require 'nokogiri'
require 'faraday'
manually require
domain_logic/lib/domain_logic.rb
![Page 42: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/42.jpg)
the test dummy app
![Page 43: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/43.jpg)
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
![Page 44: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/44.jpg)
Rails.application.routes.draw do
mount AdminUi::Engine => "/admin_ui"
end
admin_ui/spec /dummy/config/routes.rb
![Page 45: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/45.jpg)
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
![Page 46: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/46.jpg)
require_dependency "admin_ui/application_controller"
module AdminUi
class CargosController < ApplicationController
namespace application controller
![Page 47: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/47.jpg)
module AdminUi
class CargosController < AdminUi::ApplicationController
namespace application controller
![Page 48: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/48.jpg)
module AdminUi
class VoyagesController < ApplicationController
namespace application controller
![Page 49: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/49.jpg)
module AdminUi
class VoyagesController < ApplicationController
namespace application controller
![Page 50: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/50.jpg)
scaffolding and other generators
![Page 51: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/51.jpg)
rails generate scaffold_controller admin_ui/cargo source destination
weight --model-name=DomainLogic::Cargo --orm=mongoid -t=''
scaffolding and other generators
![Page 52: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/52.jpg)
require_dependency "admin_ui/domain_logic/application_controller"
scaffolding and other generators
![Page 53: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/53.jpg)
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
![Page 54: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/54.jpg)
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
![Page 55: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/55.jpg)
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
![Page 56: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/56.jpg)
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
![Page 57: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/57.jpg)
![Page 58: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/58.jpg)
![Page 59: feature flagging with rails engines v0.2](https://reader034.fdocuments.us/reader034/viewer/2022042515/58ab36e01a28abb64d8b5e3d/html5/thumbnails/59.jpg)
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]