Ruby on Rails
HowtoProductizeYourApplication (Version #7)

Jul 25, 2005: There is now a RailsProductGenerator available which does everything in this email and more. It’s a lot simpler, too :)
canadaduane


The following is copied from REUSE – Productize your application by canadaduane to the Ruby on Rails mailing list.


I’m not sure if there’s interest in this approach to development, but I’ve put together the changes necessary to make your Rails application in to a “product”.

My use of that term needs some explanation: At \MyTechSupport, we often have clients who pay a few thousand dollars for a web site, but in order to make a profit, we need to re-sell our work to other (potential) clients with similar business needs. For example, we are currently working on an adoption site. Adoption sites, in general, have some generic needs (e.g. a “Birth Mother” login and a “Waiting Family” login), but our client has some particular needs as well.

Using the following code, you can create a single generic application which uses a separate database for each client. In addition, you will have a hierarchical application structure wherein add-ons and tweaks for specific customers are possible without affecting the core “generic” site code base (neither will you have to copy that code base for each application). In this way, you can maintain a core application code base that every client uses, while still providing flexibility for paid add-ons or site-specific needs.

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:

RAILS_ROOT/
    app/
        controllers/
        views/
        (etc.)
    public/
    sites/
        best_ever_adoptions_co_inc/
            app/
                controllers/
                views/
            public/
        yet_another_adoption_co/
            app/
                controllers/
                views/
            public/

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, just for that one client.

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 partial 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.

To accomplish this feat, the following (remarkably few) changes will be necessary:

1. Create the directory structure

Create the RAILS_ROOT/sites folder and any client directory structures within it.

2. Modify environment.rb

Modify your environment.rb file as follows:

  1. Find the section where most of the core rails libraries are ’require’d,
    e.g.:

        require 'active_support'
        require 'active_record'
        require 'action_controller'
        require 'action_mailer'
        require 'action_web_service'
… and add this to the end (note that it is important that this require occurs after the core rails libraries are required but before the next ActiveRecord::Base.configurations):

        require 'productize'
  1. Replace the following line
    <pre> ActiveRecord::Base.configurations = File.open("#{RAILS_ROOT}/config/database.yml") { |f| YAML::load(f) }


with:
<pre> ActiveRecord::Base.configurations = YAML::load(ERB.new((IO.read("#{RAILS_ROOT}/config/ database.yml"))).result)

3. Create ‘productize.rb’

Create the following ‘productize.rb’ file in your RAILS_ROOT/lib folder (you may want to copy this email as documentation in the header as I did):


# productize.rb

SITE = ENV['site'] || 'yet_another_adoption_co'
SITE_ROOT = File.join(RAILS_ROOT, 'sites', SITE)
module Dependencies
  def require_or_load(file_name)
    file_name = "#{file_name}.rb" unless ! load? || file_name [-3..-1] == '.rb'
    load? ? load(file_name) : require(file_name)
    if file_name.include? 'controller'
      file_name = File.join(SITE_ROOT, 'app', 'controllers',  File.basename(file_name))
      if File.exist? file_name
        load? ? load(file_name) : require(file_name)
      end
    end
  end
end

module ActionView
  class Base
    private
      def full_template_path(template_path, extension)
        # Check to see if the partial exists in our 'sites' folder  first
        site_specific_path = File.join(SITE_ROOT, 'app', 'views',  template_path + '.' + extension)

        if File.exist?(site_specific_path)
          site_specific_path
        else
          "#{@base_path}/#{template_path}.#{extension}"
        end
      end
  end
end

4. Modify database.yml

Modify your database.yml like so:


development:
  adapter: mysql
  database: <%= SITE %>_dev
  host: localhost
  username: root
  password:

test:
adapter: mysql
database: <%= SITE %>_test
host: localhost
username: root
password:

production:
adapter: mysql
database: <%= SITE %>
host: localhost
username: root
password:



h2. 5. Server configuration

In your Apache or Lighttpd config, create a FastCgiServer for each client application that will be using your generic application (i.e. each directory in the RAILS_ROOT/sites/ directory needs to have a VirtualHost set up to use the public/ folder in each site). The dispatcher for your FastCGI? server should be set to the generic RAILS_ROOT/public/dispatcher.fcgi script. And finally, use the appropriate set-env directive on the \FastCgiServer configuration to make the environment variable “site” equal to the client name (i.e. the client’s name is used both as the prefix for your database names (e.g. yet_another_adoption_site becomes @yet_another_adoption_site_dev@) and as the directory name in the sites/ directory).

This little bit of set-up can give you a lot of pay back!

NOTE: I haven’t done step 5 yet, so I don’t have the same level of detail in the instructions. I’ll post that later if there is interest.

Duane Johnson
(canadaduane)

How does this work in 1.0 — Mischa (2005/01/06)


category: Howto

Jul 25, 2005: There is now a RailsProductGenerator available which does everything in this email and more. It’s a lot simpler, too :)
canadaduane


The following is copied from REUSE – Productize your application by canadaduane to the Ruby on Rails mailing list.


I’m not sure if there’s interest in this approach to development, but I’ve put together the changes necessary to make your Rails application in to a “product”.

My use of that term needs some explanation: At \MyTechSupport, we often have clients who pay a few thousand dollars for a web site, but in order to make a profit, we need to re-sell our work to other (potential) clients with similar business needs. For example, we are currently working on an adoption site. Adoption sites, in general, have some generic needs (e.g. a “Birth Mother” login and a “Waiting Family” login), but our client has some particular needs as well.

Using the following code, you can create a single generic application which uses a separate database for each client. In addition, you will have a hierarchical application structure wherein add-ons and tweaks for specific customers are possible without affecting the core “generic” site code base (neither will you have to copy that code base for each application). In this way, you can maintain a core application code base that every client uses, while still providing flexibility for paid add-ons or site-specific needs.

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:

RAILS_ROOT/
    app/
        controllers/
        views/
        (etc.)
    public/
    sites/
        best_ever_adoptions_co_inc/
            app/
                controllers/
                views/
            public/
        yet_another_adoption_co/
            app/
                controllers/
                views/
            public/

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, just for that one client.

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 partial 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.

To accomplish this feat, the following (remarkably few) changes will be necessary:

1. Create the directory structure

Create the RAILS_ROOT/sites folder and any client directory structures within it.

2. Modify environment.rb

Modify your environment.rb file as follows:

  1. Find the section where most of the core rails libraries are ’require’d,
    e.g.:

        require 'active_support'
        require 'active_record'
        require 'action_controller'
        require 'action_mailer'
        require 'action_web_service'
… and add this to the end (note that it is important that this require occurs after the core rails libraries are required but before the next ActiveRecord::Base.configurations):

        require 'productize'
  1. Replace the following line
    <pre> ActiveRecord::Base.configurations = File.open("#{RAILS_ROOT}/config/database.yml") { |f| YAML::load(f) }


with:
<pre> ActiveRecord::Base.configurations = YAML::load(ERB.new((IO.read("#{RAILS_ROOT}/config/ database.yml"))).result)

3. Create ‘productize.rb’

Create the following ‘productize.rb’ file in your RAILS_ROOT/lib folder (you may want to copy this email as documentation in the header as I did):


# productize.rb

SITE = ENV['site'] || 'yet_another_adoption_co'
SITE_ROOT = File.join(RAILS_ROOT, 'sites', SITE)
module Dependencies
  def require_or_load(file_name)
    file_name = "#{file_name}.rb" unless ! load? || file_name [-3..-1] == '.rb'
    load? ? load(file_name) : require(file_name)
    if file_name.include? 'controller'
      file_name = File.join(SITE_ROOT, 'app', 'controllers',  File.basename(file_name))
      if File.exist? file_name
        load? ? load(file_name) : require(file_name)
      end
    end
  end
end

module ActionView
  class Base
    private
      def full_template_path(template_path, extension)
        # Check to see if the partial exists in our 'sites' folder  first
        site_specific_path = File.join(SITE_ROOT, 'app', 'views',  template_path + '.' + extension)

        if File.exist?(site_specific_path)
          site_specific_path
        else
          "#{@base_path}/#{template_path}.#{extension}"
        end
      end
  end
end

4. Modify database.yml

Modify your database.yml like so:


development:
  adapter: mysql
  database: <%= SITE %>_dev
  host: localhost
  username: root
  password:

test:
adapter: mysql
database: <%= SITE %>_test
host: localhost
username: root
password:

production:
adapter: mysql
database: <%= SITE %>
host: localhost
username: root
password:



h2. 5. Server configuration

In your Apache or Lighttpd config, create a FastCgiServer for each client application that will be using your generic application (i.e. each directory in the RAILS_ROOT/sites/ directory needs to have a VirtualHost set up to use the public/ folder in each site). The dispatcher for your FastCGI? server should be set to the generic RAILS_ROOT/public/dispatcher.fcgi script. And finally, use the appropriate set-env directive on the \FastCgiServer configuration to make the environment variable “site” equal to the client name (i.e. the client’s name is used both as the prefix for your database names (e.g. yet_another_adoption_site becomes @yet_another_adoption_site_dev@) and as the directory name in the sites/ directory).

This little bit of set-up can give you a lot of pay back!

NOTE: I haven’t done step 5 yet, so I don’t have the same level of detail in the instructions. I’ll post that later if there is interest.

Duane Johnson
(canadaduane)

How does this work in 1.0 — Mischa (2005/01/06)


category: Howto