Author: DuaneJohnson (canadaduane)
License: Ruby License
A “productized” application is a two-tiered application. The first tier is a single base application that encapsulates common functionality for your product. The second tier consists of several customized applications that inherit and extend functionality from that base. It is useful for web developers who want to create a base application with some sort of generic functionality (e.g. a shopping cart) and then extend or modify that functionality for specific clients only.
The rails_product shell command (available in gem form below) generates the entire Rails directory structure with some modifications as required by the aims of a Productized Rails Application.
See the original post for more information.
NOTE: rails_product and productize are not compatible with rails 1.0.
Question: Why is this? Where do I start to bring this up to date?
To install the rails_product generator run:
$ gem install site_generator
When it asks you if you want to install its dependency, ‘rails_product’, choose Yes.
(The installation above assumes you have the prerequisites to Rails already installed, e.g. Ruby 1.8.2 2004-12-25 and the ‘gem’ shell command from rubyforge.org, as well as Rails 0.13.1 itself).
So, now that you’ve got the gems installed, here’s how you’d create your very first Rails product:
<pre>
$ cd ~/Projects
$ rails_product shopping_cart
create
create app/apis
<snip>
create sites
create Rakefile
create README
create CHANGELOG
create app/controllers/application.rb
create app/helpers/application_helper.rb
create test/test_helper.rb
create config/database.yml
create config/routes.rb
create public/.htaccess
create sites/Rakefile
<snip>
$ cd shopping_cart
$ ./script/generate site my_first_cart_client
create sites/my_first_cart_client/app/controllers
create sites/my_first_cart_client/app/helpers
create sites/my_first_cart_client/app/models
create sites/my_first_cart_client/app/views/layouts
create sites/my_first_cart_client/db/migrate
create sites/my_first_cart_client/log
create sites/my_first_cart_client/public
create sites/my_first_cart_client/config
create sites/my_first_cart_client/test/fixtures
create sites/my_first_cart_client/test/functional
create sites/my_first_cart_client/test/unit
create sites/my_first_cart_client/public/.htaccess
create sites/my_first_cart_client/public/dispatch.fcgi
create sites/my_first_cart_client/config/routes.rb
$ ./script/server -s my_first_cart_client
=> Productized Rails application started on <a href="http://0.0.0.0">http://0.0.0.0</a>:3000
...
</pre>
At this point, assuming your application is going to be using a
database, you’ll want to go make your first site’s database. Depending on how you do things, you’ll want to make one, two, or possibly all three of these databases:
The last database in that list is the production database.
The directory structure of the Generic application is just like a normal Rails application, with one additional directory: the “sites” directory. So it looks something like this:
<pre>
RAILS_ROOT/
app/
controllers/
helpers/
models/
views/
public/
sites/
best_ever_adoptions_co_inc/ # <-- Site #1
app/
controllers/
helpers/
models/
views/
config/
db/
migrate/
public/
test/
fixtures/
functional/
unit/
yet_another_adoption_co/ # <-- Site #2
app/
controllers/
helpers/
models/
views/
(etc.)
</pre>
Note that although the directory structure of each individual site is almost identical to a regular Rails application, you need not have any files in these directories. In fact, you can omit any directory (except the SITE_ROOT, e.g. sites/ best_ever_adoptions_co_inc/) if it does not contain files. So how does it work?
Well, if you put a file called, for example, welcome_controller.rb in RAILS_ROOT/app/controllers, then all of your sites will access that controller as normal UNLESS you put a file with the same name in the site-specific controllers folder. In that case, the RAILS_ROOT welcome_controller.rb will be loaded, and then the WelcomeController? class will be reopened (yay for Ruby!) by the site-specific welcome_controller.rb (say, for example, in RAILS_ROOT/sites/yet_another_adoption_co/app/controllers/) and modified as needed. For example, you could override the “index” method to do a redirect, or you could add a before_filter to make the controller require a login.
Views, layouts and partials act similarly—however, they simply override their generic counterparts. For example, if you have an index.rhtml and a title.rhtml in RAILSROOT/app/views/welcome/ and supposing this index.rhtml has a <%= render :partial => “title” %> in its code, then the title partial will be rendered in place. Now, however, let’s say that we put a _title.rhtml file in RAILSROOT/sites/yet_another_adoption_co/app/views/welcome/. In that case, our generic index.rhtml will load the site-specific _title partial in place. Hierarchical customizations in a jiffy.
If you would like to understand how all of the pieces work together to accomplish this feat, read on.
Here is what the rails_product generator will do:
—-
I had issues with this setup working if you had folder structures under (for instance) RAILS_ROOT/public/images which you wanted the sites to transparently have access to. Mod_rewrite was not matching the request and trying to pass it on to dispatch.fcgi.
Here’s a (lightly tested, YMMV) modification hack to the site-specific .htaccess file that appears to solve my problem…
# Example:
# RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]
RewriteEngine On
RewriteRule ^$ index.html [QSA]
# Hack to fix mod_rewrite bug: we catch the incoming uri as an environment variable 'DOC'
RewriteRule "(.*)" "$1" [env=DOC:$1]
# If the requested file does not exist in SITE_ROOT/public...
RewriteCond %{DOCUMENT_ROOT}%{ENV:DOC} !-f
# ... then split its full path up in to manageable pieces ...
RewriteCond %{REQUEST_FILENAME} ^(.*)/sites/.+$
# ... and check to see if the file exists in the RAILS_ROOT/public folder...
RewriteCond %1/public/%{ENV:DOC} -f
# ... if so, rewrite our requested file to be the RAILS_ROOT one
RewriteRule ^(.*)$ /generic/%{ENV:DOC} [NS,L]
RewriteRule ^([^.]+)$ $1.html [QSA]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]
— solo & canadaduane
How is this going to work in 1.0 (0.14.x)? — Mischa
Yea, I wonder how branching solutions like this get affected by the rails upgrades.
—————————————-
I am using ruby 1.8.3 and cannot get rails_product to work because of:
/usr/local/lib/ruby/gems/1.8/gems/activesupport-1.1.1/lib/active_support/clean_logger.rb:13:in
`remove_const’: constant Logger::Format not defined (NameError)
Did a google search and here is the answer: http://groups.google.com/group/rubyonrails/browse_thread/thread/a3ce774773d9b5ec
There’s a simple fix for this. Just replace line 13 of active_support/clean_logger.rb from this:
remove_const “Format”
to this:
remove_const “Format” if const_defined? “Format”
—————————————-
Also, in site_generator 0.6, when you first do:
./script/generate site my_first_cart_client
it will fail as:
./script/../config/../lib/productize.rb:1:in `join’: can’t convert nil into String (TypeError)
from ./script/../config/../lib/productize.rb:1
from /usr/local/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:21:in
`require’
from /usr/local/lib/ruby/gems/1.8/gems/activesupport-1.2.3/lib/active_su
pport/dependencies.rb:214:in `require’
from ./script/../config/environment.rb:66
from ./script/generate:2
because ENV‘SITE’ is not defined. I added this to the first line of lib/productize.rb to by pass it.
SITE=’’ if (SITE == nil)
—————————————-
I found that line 24 in productize.rb need to be changed from:
to
site_specific_path = File.join(SITE_ROOT, ‘app’, ‘views’, template_path + “.” + extension.to_s)in order to load
Author: DuaneJohnson (canadaduane)
License: Ruby License
A “productized” application is a two-tiered application. The first tier is a single base application that encapsulates common functionality for your product. The second tier consists of several customized applications that inherit and extend functionality from that base. It is useful for web developers who want to create a base application with some sort of generic functionality (e.g. a shopping cart) and then extend or modify that functionality for specific clients only.
The rails_product shell command (available in gem form below) generates the entire Rails directory structure with some modifications as required by the aims of a Productized Rails Application.
See the original post for more information.
NOTE: rails_product and productize are not compatible with rails 1.0.
Question: Why is this? Where do I start to bring this up to date?
To install the rails_product generator run:
$ gem install site_generator
When it asks you if you want to install its dependency, ‘rails_product’, choose Yes.
(The installation above assumes you have the prerequisites to Rails already installed, e.g. Ruby 1.8.2 2004-12-25 and the ‘gem’ shell command from rubyforge.org, as well as Rails 0.13.1 itself).
So, now that you’ve got the gems installed, here’s how you’d create your very first Rails product:
<pre>
$ cd ~/Projects
$ rails_product shopping_cart
create
create app/apis
<snip>
create sites
create Rakefile
create README
create CHANGELOG
create app/controllers/application.rb
create app/helpers/application_helper.rb
create test/test_helper.rb
create config/database.yml
create config/routes.rb
create public/.htaccess
create sites/Rakefile
<snip>
$ cd shopping_cart
$ ./script/generate site my_first_cart_client
create sites/my_first_cart_client/app/controllers
create sites/my_first_cart_client/app/helpers
create sites/my_first_cart_client/app/models
create sites/my_first_cart_client/app/views/layouts
create sites/my_first_cart_client/db/migrate
create sites/my_first_cart_client/log
create sites/my_first_cart_client/public
create sites/my_first_cart_client/config
create sites/my_first_cart_client/test/fixtures
create sites/my_first_cart_client/test/functional
create sites/my_first_cart_client/test/unit
create sites/my_first_cart_client/public/.htaccess
create sites/my_first_cart_client/public/dispatch.fcgi
create sites/my_first_cart_client/config/routes.rb
$ ./script/server -s my_first_cart_client
=> Productized Rails application started on <a href="http://0.0.0.0">http://0.0.0.0</a>:3000
...
</pre>
At this point, assuming your application is going to be using a
database, you’ll want to go make your first site’s database. Depending on how you do things, you’ll want to make one, two, or possibly all three of these databases:
The last database in that list is the production database.
The directory structure of the Generic application is just like a normal Rails application, with one additional directory: the “sites” directory. So it looks something like this:
<pre>
RAILS_ROOT/
app/
controllers/
helpers/
models/
views/
public/
sites/
best_ever_adoptions_co_inc/ # <-- Site #1
app/
controllers/
helpers/
models/
views/
config/
db/
migrate/
public/
test/
fixtures/
functional/
unit/
yet_another_adoption_co/ # <-- Site #2
app/
controllers/
helpers/
models/
views/
(etc.)
</pre>
Note that although the directory structure of each individual site is almost identical to a regular Rails application, you need not have any files in these directories. In fact, you can omit any directory (except the SITE_ROOT, e.g. sites/ best_ever_adoptions_co_inc/) if it does not contain files. So how does it work?
Well, if you put a file called, for example, welcome_controller.rb in RAILS_ROOT/app/controllers, then all of your sites will access that controller as normal UNLESS you put a file with the same name in the site-specific controllers folder. In that case, the RAILS_ROOT welcome_controller.rb will be loaded, and then the WelcomeController? class will be reopened (yay for Ruby!) by the site-specific welcome_controller.rb (say, for example, in RAILS_ROOT/sites/yet_another_adoption_co/app/controllers/) and modified as needed. For example, you could override the “index” method to do a redirect, or you could add a before_filter to make the controller require a login.
Views, layouts and partials act similarly—however, they simply override their generic counterparts. For example, if you have an index.rhtml and a title.rhtml in RAILSROOT/app/views/welcome/ and supposing this index.rhtml has a <%= render :partial => “title” %> in its code, then the title partial will be rendered in place. Now, however, let’s say that we put a _title.rhtml file in RAILSROOT/sites/yet_another_adoption_co/app/views/welcome/. In that case, our generic index.rhtml will load the site-specific _title partial in place. Hierarchical customizations in a jiffy.
If you would like to understand how all of the pieces work together to accomplish this feat, read on.
Here is what the rails_product generator will do:
—-
I had issues with this setup working if you had folder structures under (for instance) RAILS_ROOT/public/images which you wanted the sites to transparently have access to. Mod_rewrite was not matching the request and trying to pass it on to dispatch.fcgi.
Here’s a (lightly tested, YMMV) modification hack to the site-specific .htaccess file that appears to solve my problem…
# Example:
# RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]
RewriteEngine On
RewriteRule ^$ index.html [QSA]
# Hack to fix mod_rewrite bug: we catch the incoming uri as an environment variable 'DOC'
RewriteRule "(.*)" "$1" [env=DOC:$1]
# If the requested file does not exist in SITE_ROOT/public...
RewriteCond %{DOCUMENT_ROOT}%{ENV:DOC} !-f
# ... then split its full path up in to manageable pieces ...
RewriteCond %{REQUEST_FILENAME} ^(.*)/sites/.+$
# ... and check to see if the file exists in the RAILS_ROOT/public folder...
RewriteCond %1/public/%{ENV:DOC} -f
# ... if so, rewrite our requested file to be the RAILS_ROOT one
RewriteRule ^(.*)$ /generic/%{ENV:DOC} [NS,L]
RewriteRule ^([^.]+)$ $1.html [QSA]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]
— solo & canadaduane
How is this going to work in 1.0 (0.14.x)? — Mischa
Yea, I wonder how branching solutions like this get affected by the rails upgrades.
—————————————-
I am using ruby 1.8.3 and cannot get rails_product to work because of:
/usr/local/lib/ruby/gems/1.8/gems/activesupport-1.1.1/lib/active_support/clean_logger.rb:13:in
`remove_const’: constant Logger::Format not defined (NameError)
Did a google search and here is the answer: http://groups.google.com/group/rubyonrails/browse_thread/thread/a3ce774773d9b5ec
There’s a simple fix for this. Just replace line 13 of active_support/clean_logger.rb from this:
remove_const “Format”
to this:
remove_const “Format” if const_defined? “Format”
—————————————-
Also, in site_generator 0.6, when you first do:
./script/generate site my_first_cart_client
it will fail as:
./script/../config/../lib/productize.rb:1:in `join’: can’t convert nil into String (TypeError)
from ./script/../config/../lib/productize.rb:1
from /usr/local/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:21:in
`require’
from /usr/local/lib/ruby/gems/1.8/gems/activesupport-1.2.3/lib/active_su
pport/dependencies.rb:214:in `require’
from ./script/../config/environment.rb:66
from ./script/generate:2
because ENV‘SITE’ is not defined. I added this to the first line of lib/productize.rb to by pass it.
SITE=’’ if (SITE == nil)
—————————————-
I found that line 24 in productize.rb need to be changed from:
to
site_specific_path = File.join(SITE_ROOT, ‘app’, ‘views’, template_path + “.” + extension.to_s)in order to load