转载声明:http://ryanstenhouse.eu/tutorials/2010/02/07/dynamic-database-switching-rails-how-to-do-it/

If you have an application you offer as a service to others, youʼre going to run into the problem of needing to keep data separate between your customers. You will want to do this as efficiently as you can, with as little code as possible.

Wufoo is a good example of this kind of problem, where every user of their service sees the same application and uses the same tools; but by giving each user their own unique URI they are easily able to segregate data between users.

A variation of the fairly straight forward method I describe in this Tutorial has been live since 2007 in a web application developed by PCCL. It has proven to be fast enough to deal with the load of being in daily use by thousands of unique people. The original version of this code was written by my colleague Mark Somervilleand this is heavily based on his brilliant work.

The Problem & A Solution

Your application is fairly generic, it’ll work well for anyone – if only you could keep your clients' data separate. Of course, you could quite easily just deploy one instance of your application for each customer – after all, Rails only supports one production database.

The problem is then, how the heck do you manage these deployments? How do you keep them all up to date? As soon as you have more than two or three customers using your service, the cost to you in time and effort to keep the applications online and updated quickly becomes prohibitive.

But.. Since you’re needing separate URIs for each client anyway to serve the different instances to your clients, you could always just make your application clever enough to look at the incoming request’s address and then take the appropriate database-switching action.

Here’s how to do it:

  1. Sniff the URI of the request
  • request.host is there for a reason! Use it!

Connect to the Database for the URI

  • Use client-specific DB config files
  • Cache the contents of the files to reduce performance loss

Client Database Config Files

The messy part of this solution is that you need to create a file similar to your database.yml for each of the unique clients (or unique URIs) which are accessing your application. As a sensible convention, I suggest creating a config/databases directory in your RAILS_ROOT to place these files.

Inside this directory, each URI should be represented as a YML file. For this example, I’ll use my own domain name, so I’ll be creating ryanstenhouse.eu.yml. The contents of the file should be:

 1 # Contents of ryanstenhouse.eu.yml
 2
 3 database_details:
 4
 5   adapter: mysql
 6
 7   database: client_db_name
 8
 9   username: root
10
11   password:

As you can see, it’s almost exactly the same as a regular database.yml file, with the exception that instead of development: or production: keys; adatabase_details: key is used instead.

You are going to need at least one client config file, so open config/clients/localhost.yml in your favourite editor, and fill it with the following:

1 <%= { "database_details" => ActiveRecord::Base.configurations["development"] }.to_yaml %>

This will allow you to visit http://localhost:3000 when your application is running and still be able to use the development environment you configured indatabase.yml.

In your Rails application

After the horror of having to put up with multiple client-specifc database files, you’d be relieved to know that in your application, all you need to do is add a rather simple before_filter to your ApplicationController.

For neatness, the filter we’re going to use to do the database switching will live in a module. Something like:

 1 module ClientDatabaseSwitching
 2
 3   # This is prepended to the filter chain for each action and will ensure that on each
 4   # request, the User is connected to the correct database.
 5   #
 6   def choose_database_from_host
 7     unless defined? @@_client_database_details
 8       @@_client_database_details = Hash.new
 9     end
10     host = request.host
11     if @@_client_database_details[host].nil?
12       @@_client_database_details[host] = fetch_client_details_for host
13     end
14     connect_to_database_for @@_client_database_details[host]
15   end
16
17   # Looks in #{RAILS_ROOT}/config/databases for a configuration file which is called
18   # #{client_hostname}.yml, which will be a ymlified Hash of the connection details
19   # needed to establish a database connection.
20   #
21   def fetch_client_details_for(client_hostname)
22     file_path = "#{RAILS_ROOT}/config/databases/#{client_hostname}.yml"
23     if File.exists?(file_path)
24       return YAML::load(ERB.new(IO.read(file_path)).result)['database_details']
25     end
26   end
27
28   # Actually does the work of connecting to the database.
29   #
30   def connect_to_database_for(details)
31     ActiveRecord::Base.establish_connection(details)
32   end
33 end

Once that’s done, you just need to do something like the following in your ApplicationController:

1 include ClientDatabaseSwitching
2 prepend_before_filter :choose_database_from_host

So what is happening?

  1. When the request comes in to your application, the before_filter you prepended to the start of the filter chain reads request.host.
  2. The class-level Hash@@_client_database_details is checked to see if it contains the connection details for this client.
  3. If the @@_client_database_details contains the connection information for this host, the application connects to that client’s database before processing the action.
  4. If not, the contents of the client database config file for this client are loaded into the @@_client_database_details hash and the application then connects to the database before processing the action.

Final working code

A working example of this solution is available on my GitHub for your downloading and running pleasure. There’s a fully passing test suite and a more robust implementation than the overview detailed here.

Next steps

I’ve deliberately left a lot of validation and error handling out of the example above to make the illustration of the concept used as clear as possible. If you were to use this method in production, you will need to, at least, be able to deal with cases where configuration files don’t exist.

This solution is rather Rails-Heavy. The nature of its implementation means it will work with all versions of the framework from 1.2.3 to 2.3.4, however there’s much more to the world of Ruby web-apps than just one Framework.

Rails 3 Compatibility

For Rails 3 compatibility, it should just be a case of substituting RAILS_ROOT for Rails.root.to_s in the code examples.

转载于:https://www.cnblogs.com/lenolix/articles/2984451.html

[转载]Dynamic Database Switching in Rails - How to do it相关推荐

  1. [转载]dynamic的小坑--RuntimeBinderException:“object”未包含“xxx”的定义

    创建一个控制台项目和一个类库项目, 在类库中创建一个匿名对象,然后在控制台中访问它,代码如下: 控制台: namespace ConsoleApplication1 {class Program{st ...

  2. 获取错误:当试图让pgsql使用rails时,用户“postgres”的对等身份验证失败

    本文翻译自:Getting error: Peer authentication failed for user "postgres", when trying to get pg ...

  3. PHP语言的动态特性-Going dynamic with PHP

    原文链接: http://www.ibm.com/developerworks/xml/library/os-php-flexobj/ PHP5引入的面向对象的编程特性显著的提升了PHP语言的层次.不 ...

  4. react前端项目_如何使用React前端设置Ruby on Rails项目

    react前端项目 The author selected the Electronic Frontier Foundation to receive a donation as part of th ...

  5. Scott Hanselman的2006 Windows最终开发者和高级用户工具列表

    enjoyed this post, or this blog, please 喜欢这篇文章或博客,请make a secure tax-deductable donation to the Amer ...

  6. CNCF 宣布 TUF 毕业 | 云原生生态周报 Vol. 33

    作者 | 孙健波.汪萌海.陈有坤.李鹏 业界要闻 CNCF 宣布 TUF 毕业 CNCF 宣布 TUF(The update Framework)项目正式毕业,成为继 Kubernetes.Preme ...

  7. MySQL使用Amoeba作为Proxy时的注意事项

    Amoeba是一个以MySQL为底层数据存储,并对应用提供MySQL协议接口的proxy.它因配置方便,语法书写规则等特点深得广大使用者的青睐,但在实际的使用过程中也有很多注意事项,本文我们就对其加以 ...

  8. heroku_Heroku Connect的美丽:简化数据库同步

    heroku by Wilson Wang 威尔逊·王(Wilson Wang) Heroku Connect的美丽:简化数据库同步 (The Beauty of Heroku Connect: Si ...

  9. sql面试题问答题_SQL面试问答

    sql面试题问答题 SQL interview questions are asked in almost all interviews because database operations are ...

最新文章

  1. 新书上市 | 数学不好,Python不行,还能入门机器学习吗?
  2. 是什么动词_【术语攻略】什么是及物动词?
  3. 序列化的 serialVersionUID 到底有什么用?
  4. post install error,please remove node_moules before retry
  5. SQL 存储过程入门(事务)
  6. java --HashTable学习
  7. 文件上传案例的客户端
  8. python 关键字参数为什么只能出现在最后_Python笔记2——默认参数,可变参数,关键字参数,参数组合...
  9. Windows 下开发PHP扩展资源
  10. html5上传视频和预览,HTML5 上传前预览
  11. photoshop cs5快捷键的用法总结
  12. 无论你是用什么样的模式去拓展市场
  13. 小公司出身的我,是如何拿下知名独角兽公司 Offer?
  14. 【分享】如果我没有那么优秀,我研究生阶段选择机器学习方向还有出路吗?...
  15. mysql 驱动jar包下载_mysql驱动jar包
  16. 理清contactsprovider
  17. 学术会议html模板,学术会议的常用模板
  18. 当实现两个Activity之间的跳转时,发生 XXX has stoped 或者 XXX keeps stopping
  19. Vue3 扫描二维码
  20. 《淘宝技术这十年》读书笔记 (四). 分布式时代和中间件

热门文章

  1. 1.JUC锁的一些概念
  2. iOS动画 三维透视投影 m34
  3. Operation not permitted - /usr/bin/xcodeproj
  4. Android新建一个activty
  5. 从短信类到短信平台之设计篇
  6. Windows7中右键菜单无新建文本文档选项的解决办法(注册表)
  7. SQL Server 索引基础知识(1)--- 记录数据的基本格式
  8. ten sentences(1-10)
  9. echart 高度 不用 不撑满_装修干货:橱柜高度到底要多高才合适?
  10. 去中心化数据库Bluzelle公布2021年路线图,将于2月3日上线主网