Advertise here!

Oracle patches for Rails

The code I described and provided earlier in my entry: Rails, db_structure_dump, and Oracle was submitted as a patch to Rails around the time of the 0.13.0 release. I just received an email today closing the ticket, showing that the patch was closed in favor of ticket #1798. Curious, I poked around looking at ticket 1798. It includes a number or oracle related patches for Rails, including my implementation of structure_dump.

The patches will give the Oracle adapter a number of improvements. Improvements related to the structure_dump implementation are the implementation of purging and cloning Oracle databases. Some bigger changes include the ability to use synonyms and a change which could break existing users: Using sequences on a per-table basis. Previously, Rails used a single sequence to generate the primary keys for all tables. The next release will change the behavior to create and use sequences in the form “#{table_name}_seq” which is more common practice.

The current stance is that existing Oracle users who want to retain the single, global sequence will have to modify their environment.rb (after the next Rails release) to include:

ActiveRecord::Base.set_sequence_name = "rails_sequence"

Update: As of Rails 1.0 RC3, the way to set the sequence name for all ActiveRecord's has changed from the initial proposal. Now, you'd do something like so:

# Post 0.13.1, Force the OCI Adapter to use the global sequence like it used to
class ActiveRecord::Base
  set_sequence_name "rails_sequence"
end

Posted at 7pm on 07/25/05 | Posted in , , | 1 responses | read on

Productize your Rails App

Duane Johnson posted some interesting code to the Rails mailing list last week. Duane’s been creating Rails applications for clients, but to help actually make a profit, he’ll “re-sell” the work to other clients with some custom-tailoring for each particular client. He dubs this “productizing” his application. You can read DHH’s original post highlighting this as well.

So, he was looking for a way to keep a common codebase for all clients and still have a ay to explicitly tailor site-specific items, even beyond images or stylesheets – but also to add or modify functionality. This post was particularly important to me, because this is exactly what I’ve been trying to do with my Rails app. The aim is to create a common database model and common code as much as possible but to allow for site specific controllers, actions, or assets.

I’d been setting up Apache to use the subdomains to determine which site to present to the user. Initially I had them running off one rails instance and swapping the DB underneath by getting the subdomain. I also used the subdomain to swap layouts and other cases where I wanted things to differ. The biggest problem was that FastCGI and swapping the DB per request based on subdomain didn’t play nice together. The DB connections stayed open forever and eventually it all choked and died.

Next I created two identical instances of the common code and had Apache just route to the right instance by virtual hosting. I just hard-coded the database.yml to point to the right database (and removed the swapping) for each instance. The rest of the code still looked at the subdomain to determine which layout to use, etc. Obviously my approaches left much to desire.

(Modified instructions for) "Productizing" your rails app

To anyone out there trying to follow Duane’s  approach, go read his post first, and then come back here, because some things are missing from his original post and some of what he tells there is not correct. Go ahead, read it, and come on back…

OK, welcome back. If you’re actually playing along and are trying to get this working, follow Duane’s advice first and then go through what I have to get it working properly.

Open Issues / Caveats

  • We haven’t worked out the work process of testing with this setup. (I also should note that I haven’t really tried playing with testing yet.)
  • Routing will fail if you have a controller that only exists at the site level. For now, the workaround is to create an empty version of the controller at the top common-level.
  • Some of what I give below is specific to Apache. I have not yet tried doing this with lighttpd, but I’m assuming that it would be fairly straight-forward to get this working with it. 

Apache Setup

Second, the way he told you to set up your document roots? Yeah that’s not exactly correct. Here’s what you do to get set up with Apache and FastCGI:

Make sure you set your project folders up as Duane specified:

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/

 Now, in each site’s public directory, copy over dispatch.fcgi. For each site-specific version of dispatch.fcgi, edit the line which includes environment.rb. You’ll be telling it to look up two directories higher than normal. Change the line to look like this:

require File.dirname(__FILE__) + "/../../../config/environment"

 You’ll probably be hosting all of these sites off the same server and Apache install. So here’s an example of my httpd.conf file where I use Virtual Hosts for each site. This is on Windows XP with Apache 2.x.

LoadModule fastcgi_module modules/mod_fastcgi.so

<IfModule mod_fastcgi.c>
  AddHandler fastcgi-script .fcgi
  FastCgiServer C:/projects/svn/trunk/sites/site_one/public/dispatch.fcgi -initial-env site=site_one 
-initial-env RAILS_ENV=production -initial-env PATH=C:/oracle/ora81/bin -idle-timeout 200
  FastCgiServer C:/projects/svn/trunk/sites/site_two/public/dispatch.fcgi -initial-env site=site_two 
-initial-env RAILS_ENV=production -initial-env PATH=C:/oracle/ora81/bin -idle-timeout 200
</IfModule>

NameVirtualHost *:80
<VirtualHost *:80>
    ServerName site_one.myhost.com
    DocumentRoot C:/projects/svn/trunk/sites/site_one/public
    ErrorLog C:/projects/svn/trunk/sites/site_one/log/apache.log
 
    <Directory C:/projects/svn/trunk/public/>
      Options ExecCGI FollowSymLinks
      AddHandler cgi-script .cgi
      AllowOverride all
      Order allow,deny
      Allow from all
    </Directory>
    <Directory C:/projects/svn/trunk/sites/site_one/public/>
      Options ExecCGI FollowSymLinks
      AddHandler cgi-script .cgi
      AllowOverride all
      Order allow,deny
      Allow from all
    </Directory>
  </VirtualHost>
 
<VirtualHost *:80>
    ServerName site_two.myhost.com
    DocumentRoot C:/projects/svn/trunk/sites/site_two/public
    ErrorLog C:/projects/svn/trunk/sites/site_two/log/apache.log
    
    <Directory C:/projects/svn/trunk/public/>
      Options ExecCGI FollowSymLinks
      AddHandler cgi-script .cgi
      AllowOverride all
      Order allow,deny
      Allow from all
    </Directory>
    <Directory C:/projects/svn/trunk/sites/site_two/public/>
      Options ExecCGI FollowSymLinks
      AddHandler cgi-script .cgi
      AllowOverride all
      Order allow,deny
      Allow from all
    </Directory>
  </VirtualHost>

Getting Caching working right

Next, we’re going to modify Duane’s productize.rb file. Make sure it looks like this:

# 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

ActionController::Base.page_cache_directory = "#{SITE_ROOT}/public"

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

What’s the difference? I’ve added a line which tells Rails to cache (and reload cached files) into the site specific directories. With Duane’s original code, the files would get cached at the top-level (so if the cached file was no generic enough, a site-specific version would be served up to every site).

Making the Assets Load in a Hierarchical Way

One of the initial problems I had was that the assets – images, javascripts, stylesheets – had to be copied to every site’s public directory. Obviously we want common assets to behave like the code does – try loading from the site specific directory, and then the common directory.

I posed this to Duane and he responded with the following. Please note that I haven’t tried this yet, but it looks promising:

I have an Apache solution to this problem: 

First, add an "Alias" directive inside of each VirtualHost, something like this:

FastCgiServer /Users/clifffano/Projects/premier/sites/premier_adoption/public/dispatch.fcgi 
-processes 1 -initial-env SITE=premier_adoption

<Directory "/Users/clifffano/Projects/premier/sites/premier_adoption/public/">
    AllowOverride All
</Directory>

# Remember to make sure "NameVirtualHost *:80" is set in httpd.conf so we can use VirtualHosts
<VirtualHost *:80>
    ServerName premier_adoption.localhost
    DocumentRoot /Users/clifffano/Projects/premier/sites/premier_adoption/public/

    # The following alias is important since it will allow this particular site to
    # seemlessly use the generic app's resources, e.g. images, javascripts etc. without
    # having to copy all files to the site-specific public/ folder:
    Alias /generic /Users/clifffano/Projects/premier/public/
</VirtualHost>

Then, modify the site-specific dispatch.fcgi files, like so:

# Example:
#   RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]
RewriteEngine On
RewriteRule ^$ index.html [QSA]

# If the requested file does not exist in SITE_ROOT/public...
RewriteCond %{REQUEST_FILENAME} !-f
# ... then split its full path up in to manageable pieces ...
RewriteCond %{REQUEST_FILENAME} ^(.*)/sites/.+/public/(.*)$
# ... and check to see if the file exists in the RAILS_ROOT/public folder...
RewriteCond %1/public/%2 -f
# ... if so, rewrite our requested file to be the RAILS_ROOT one
RewriteRule ^(.*)$ /generic/$1 [NS,L]

RewriteRule ^([^.]+)$ $1.html [QSA] 

RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]

This will skip Rails altogether and just send the appropriate files 
in the case that images, javascripts or html files exist in the 
generic public folder but not in the site-specific public folder.  
Posted at 3pm on 07/25/05 | Posted in , , | 3 responses | read on

Exciting Typo patches in the works

Scott Laird is doing some exciting work on patching up new features for Typo. I’m especially interested in the new sidebar plugin work he’s doing.

He’s been making it simpler to package up sidebar plugins for Typo – such as the available Flickr, Tadalist, or delicious integration. The current stable release of Typo hard-codes these into the main layout template and sticks the helper code inside Typo. He’s making it easier to just drop a plugin package in and, under the admin inetrface, drag and drop them into your sidebar. This way you can enable or disable them via WYSIWYG rather than hacking the rhtml.

Editing the rhtml to enable plugins really isn’t a big deal for techy geeks who grok Rails and HTML. But for bloggers who just want to post articles and skin a website – they won’t really want to be editing templates and copying and pasting code into controllers and helpers.

Posted at 8pm on 07/18/05 | Posted in , , | no responses | read on

Google Sitemap generation with Rails

Tristan at IP Angels, has a nice little post on generating Google sitemaps with Ruby on Rails. The post gives a great example of generating a sitemap for a blog. I can’t get access to the latest Typo snapshot to create a patch yet, but here’s what I think it would look like to add this into Typo.

First, edit the xml_controller.rb to add a new action, sitemap.

def sitemap
    @headers['Content-Type'] = 'text/xml; charset=utf-8'
    @articles = Article.find(:all, :conditions => 'published=1', :order => 'created_at DESC')
  end

Next, create a sitemap.rxml file in app/views/xml.

xml.instruct! :xml, :version=> '1.0', :encoding => 'UTF-8'
xml.urlset( :xmlns => 'http://www.google.com/schemas/sitemap/0.84') do
  # First entry is the main entry to the site
  xml.url do
    xml.loc server_url_for(:controller => "articles")
    xml.changefreq 'daily'
    xml.priority '0.9'
  end

  for entry in @articles
    xml.url do
      xml.loc article_url(entry, false)
      xml.changefreq 'weekly'
      xml.priority'0.5'
    end
  end
end

And remember, I just whipped this up without trying it.  So..

"Beware of bugs in the above code; I have only proved it correct, not tried it" - Donald E. Knuth

Posted at 3pm on 07/18/05 | Posted in , , , | 3 responses | read on

Older posts: 1 ... 5 6 7 8 9