4

I know I am not suppose to do this, but I dont know how else should I do this. I want to use different database based on which user loged in. So I thought best way would be if I set up a session variable on first user login...

this is how it looks like:

class Stuff < ActiveRecord::Base
    establish_connection(
        :adapter  => "mysql2",
        :host     => "127.0.0.1",
        :username => session["dbuser"],
        :password => session["dbuserpass"],
        :database => session["dbname"])

and this of course does not work. Does anyone know how to do this? Thank you.

tereško
  • 58,060
  • 25
  • 98
  • 150
user899119
  • 549
  • 1
  • 11
  • 25
  • 2
    Rule one, do NOT give a user "root" access. Just don't. Rule two, don't base the connection on something the user can change on their machine, such as a session cookie or variable. User's are so darned ingenious there's no telling what they'll do. – the Tin Man Feb 05 '13 at 00:40
  • this is just an example, each user will have its db user premissions... – user899119 Feb 05 '13 at 00:42
  • 1
    Then please show that in your sample code, or explain it in your question. – the Tin Man Feb 05 '13 at 00:43
  • ok, I have modified it, this way I will set 3 session variables for user, and then he will connect only to that specified db... – user899119 Feb 05 '13 at 00:47
  • Instead of sessions, why not use User's record to switch the databases? I assume they have to log in anyway... This at least takes the session idea off the table. – nowk Feb 17 '13 at 03:15
  • how can I do that? the only way to know which user is logged in is through a session variable? – user899119 Feb 17 '13 at 03:32

4 Answers4

3

You can rewrite your method as:

class Stuff < ActiveRecord::Base
 def establish_connection_user(user, pass, database)
   establish_connection(
    :adapter  => "mysql2",
    :host     => "127.0.0.1",
    :username => user,
    :password => pass,
    :database => database)
  end
end

and in your controller:

  class StuffController < ApplicationController
    def login #example 
      stuff = Stuff.new
      stuff.establish_connection_user(
        session[:dbuser], 
        session[:dbuserpass],
        session[:dbname]) 
    end

This way you also encapsulate it and make the details less obvious. I suggest you also encrypt your cookie so you don't have the credentials so exposed. You can take an idea from this answer:

Storing an encrypted cookie with Rails

Community
  • 1
  • 1
1

You can select the database in your model like this:

establish_connection "db_name_#{session[:something]}"

This way your model know which database to pull/push data.

Look at this: http://m.onkey.org/how-to-access-session-cookies-params-request-in-model

Kaeros
  • 1,138
  • 7
  • 7
  • hm...I still get undefined local variable or method `session' – user899119 Feb 05 '13 at 00:49
  • Sorry, i modified the answer. It's not good to do this, but it is an valid experiment. – Kaeros Feb 05 '13 at 00:58
  • thank you, but it does not seems to work...when I access establish_connection "#{session[:dbname]}" I got You have a nil object when you didn't expect it!...but I am sure session[:dbname] is set... – user899119 Feb 05 '13 at 01:15
0

As hinted by kwon in the comments on the original question, I would approach it through using the session only to retain the identity of the user. I would then pull the desired database connection from logic in a model and a central database (the default rails DB) persisting user details and user connection information.

Start with a modification to your user model (assuming that your user has a model that is persisted in a central database)

  • add an attribute to the user representing the data to be used
  • in your application controller set the user in a before_filter, based on the session key
  • initialize your Stuff model with a user argument

You can then lookup your database connection based on the database.yml. Or if you have one database per user and you need this to be dynamic, create a second model (in the central database) representing the database connection with a foreign key onto the user model.

The following is a bunch of code that may or may not work in reality, but hopefully gives you a template for getting started.

class ApplicationController < ActionController::Base
  before_filter :set_user

  def set_user
    begin
      @user = UserProfile.find(session[:usernumber]) if session[:usernumber]        
    rescue
      logger.warn "Possible error in set_user. Resetting session: #{$!}"
      @user=nil
      session[:usernumber]=nil
      reset_session
    end
  end

end

class StuffController < ApplicationController
  def show
    @stuff = Stuff.user_get(@user, params[:id])
  end
end

class Stuff < ActiveRecord::Base
  # This would be better moved to a module to reuse across models
  def self.establish_connection_user(user)
    establish_connection(user.connection_hash)
  end
  def establish_connection_user(user)
    establish_connection(user.connection_hash)
  end

  def self.user_get user, item_id        
    establish_connection_user(user)
    find(id)
  end

  def self.user_where user, *query_args        
    establish_connection_user(user)
    where(query_args)
  end
  # Even better than replicating 'where', create model methods 
  # that are more representative of your desired functionality
end

class User  < ActiveRecord::Base
  has_one :user_connection
  def connection_hash
    uc = self.user_connection
    {:database=>uc.db, :password=>uc.pass, :user=>uc.username, :host=>uc.dbhost, :adapter=>uc.adapter}
  end
  # User probably contains other user-facing details
end
Phil
  • 2,797
  • 1
  • 24
  • 30
0

If you have the option of using PostgreSQL you can use the Schemas feature of PostgreSQL to effectively have separate table namespaces (schemas) for each user. The benefit here is you are still connected to the same database (thus avoiding hacking up the rails API), but you get the same benefits of multiple DBs in terms of database separation.

If you have a RailsCasts Pro subscription ($9/mo) Ryan Bates has an excellent video on the subject: http://railscasts.com/episodes/389-multitenancy-with-postgresql

Jerod Santo also did a great write up on his blog: http://blog.jerodsanto.net/2011/07/building-multi-tenant-rails-apps-with-postgresql-schemas/

In both examples they use subdomains to switch between tenants/schemas but you could easily link it to a user record.

Austin Lin
  • 2,545
  • 17
  • 17