How to make a per-request variable globally available?

I’m developing a single application that responds to requests from two
domains. Various parts of my code need to work differently depending on
the request’s domain.

The typical solution would be to do something like this:

class ApplicationController < ActionController::Base
before_filter :initialize_site

makes @site available to all controllers and views

def initialize_site
@site = Site.find_by_domain(request.domain)
end
end

class Site < ActiveRecord::Base
def find_by_domain(domain)
# …
end
end

The problem is that I’d really like to have access to @site in my models
as well, because it would make my controllers much much more DRY.

In this (contrived) example, imagine two seperate store-fronts, both
selling widgets from the same manufacturer. But we want some widgets to
appear in one store, and some in the other.

class Widget < ActiveRecord::Base
belongs_to :manufacturer
belongs_to :site
end

class Site < ActiveRecord::Base
has_many :widgets
end

class Manufacturer < ActiveRecord::Base

This is bad because Manufacturer.find(1).widgets contains

widgets from both sites

has_many :widgets

I want to do the following instead, but I can’t because

@site is not available to models because it’s a controller

instance variable:

has_many :widgets, :conditions => {:site_id => @site.id}
end

So I have to do things like this instead:

class ManufacturersController < ApplicationController
def show
@manufacturer = Manufacturer.find(params[:id])

@widgets = @manufacturer.widgets.select do |widget|
  widget.site == @site
end

end
end

Having to do things like that in almost every action in my controllers
is driving me nuts. It’s horribly un-DRY. I want to take care of the
site filtering at the model level, so my controllers and views don’t
even know the difference.

So the question is, what’s the best-practice for making a variable
available to all models, views, and controllers? A (gasp!) global
variable? This variable needs to be set at the beginning of each
request.

Completely different approaches to solving this problem much appreciated
as well.

Thanks for reading!

makes @site available to all controllers and views

def initialize_site
@site = Site.find_by_domain(request.domain)
end
end

You can use Thread.current to pass data to your models. Something
like this:

def initialize_site
@site = Site.find_by_domain(request.domain)
Thread.current[:site] = @site
end

Aaron

I have been playing with the id of something similar and have come up
with this.

make a folder domains then for each domain add public, config and any
other files you need to be site specific.

then start 2 seperate fcgi proccesses

if you set it up properly you can run two domains off the same
application; and can put SITE in the enviroments folder

Aaron wrote:

You can use Thread.current to pass data to your models. Something
like this:

def initialize_site
@site = Site.find_by_domain(request.domain)
Thread.current[:site] = @site
end

Thank you, that’s exactly what I needed.

After I did that I extended ActiveRecord.Base::find to put my conditions
in at the highest level:

class Site < ActiveRecord::Base
def Site.current
Thread.current[:site]
end
end

class Widget < ActiveRecord::Base
class << self
# keep a pointer to the old find
alias :find_without_site :find

# extend find to always return widgets for the current site
def find(*args)
  self.with_scope(:find => {:conditions => {:site_id => 

Site.current.id}}) do
self.find_without_site(*args)
end
end
end
end

And it’s amazing. All unnecessary references to the site are gone from
my controllers, and even the other models. Thanks again!

Thank you, that’s exactly what I needed.

Your welcome

  end
end

end
end

And it’s amazing. All unnecessary references to the site are gone from
my controllers, and even the other models. Thanks again!

Scoped finds are great, and overriding the default find will
automatically scope your associations too. But be aware that the
find_by convenience methods (find_by_name, find_by_id, etc…) are NOT
scoped.

Aaron