使用PostgreSQL的模式和Rails创建多租户应用程序

47

我已经掌握的内容

我正在学习如何在Rails中创建一个多租户应用程序,该应用程序根据使用的域或子域从不同的模式中提供数据。

这里有一些我已经解决的问题:

  1. 如何让subdomain-fu也适用于域名? 这里有人问了同样的问题,它会带你到这篇博客
  2. 使用什么数据库,以及如何进行结构化? Guy Naor有一个很好的演讲,还有一个关于PostgreSQL和模式的好问题
  3. 我已经知道我的所有模式都具有相同的结构。它们将在它们所持有的数据方面有所不同。因此,如何对所有模式运行迁移?这里有一个答案

这三个要点涵盖了我需要知道的许多一般性内容。然而,在接下来的步骤中,我似乎有很多实现方法可供选择。我希望有更好、更简单的方法。

最后,我的问题是什么

当一个新用户注册时,我可以轻松地创建模式。然而,加载其余模式已经具有的结构的最佳和最简单的方法是什么?这里有一些问题/方案,可能会让你有更好的想法。

  1. 我应该将它传递给一个shell脚本,将公共模式倾倒到临时模式中,然后再将其导入回我的主要数据库中(就像Guy Naor在他的视频中所说的那样)吗?这里有一个从#postgres on freenode获取的快速摘要/脚本。虽然这可能有效,但我将不得不在Rails之外做很多工作,这让我有点不舒服..这也带来了下一个问题。
  2. 是否有一种方法可以直接从Ruby on Rails中实现这个目标?比如创建一个PostgreSQL模式,然后只需将Rails数据库模式(schema.rb - 我知道,这很令人困惑)加载到该PostgreSQL模式中。
  3. 是否有一个已经拥有这些功能的gem/plugin呢?类似于“create_pg_schema_and_load_rails_schema(the_new_schema_name)”这样的方法。如果没有,我可能会尝试去创造一个,但我对其中所有组成部分进行测试的质量感到怀疑(特别是如果我最终使用shell脚本来创建和管理新的PostgreSQL模式)。

谢谢,希望这不会太长!

3个回答

13

2011年12月5日更新

感谢Brad Robertson和他的团队,现在有了Apartment gem。它非常有用,可以完成很多繁重的工作。

然而,如果你会玩模式(schemas),我强烈建议你知道它是如何工作的。熟悉一下Jerod Santo的演示,这样你就会知道Apartment gem大致上在做什么。

2011年8月20日11:23 GMT+8更新

有人创建了一篇博客文章,并很好地介绍了整个过程。

2010年5月11日11:26 GMT+8更新

从昨晚开始,我已经能够使用一种方法来创建一个新的模式,并将schema.rb加载到其中。不确定我所做的是否正确(到目前为止似乎工作得很好),但至少这是更接近一步了。如果有更好的方法,请告诉我。

module SchemaUtils
  def self.add_schema_to_path(schema)
    conn = ActiveRecord::Base.connection
    conn.execute "SET search_path TO #{schema}, #{conn.schema_search_path}"
  end

  def self.reset_search_path
    conn = ActiveRecord::Base.connection
    conn.execute "SET search_path TO #{conn.schema_search_path}"
  end

  def self.create_and_migrate_schema(schema_name)
    conn = ActiveRecord::Base.connection

    schemas = conn.select_values("select * from pg_namespace where nspname != 'information_schema' AND nspname NOT LIKE 'pg%'")

    if schemas.include?(schema_name)
      tables = conn.tables
      Rails.logger.info "#{schema_name} exists already with these tables #{tables.inspect}"
    else
      Rails.logger.info "About to create #{schema_name}"
      conn.execute "create schema #{schema_name}"
    end

    # Save the old search path so we can set it back at the end of this method
    old_search_path = conn.schema_search_path

    # Tried to set the search path like in the methods above (from Guy Naor)
    # [METHOD 1]: conn.execute "SET search_path TO #{schema_name}"
    # But the connection itself seems to remember the old search path.
    # When Rails executes a schema it first asks if the table it will load in already exists and if :force => true. 
    # If both true, it will drop the table and then load it. 
    # The problem is that in the METHOD 1 way of setting things, ActiveRecord::Base.connection.schema_search_path still returns $user,public.
    # That means that when Rails tries to load the schema, and asks if the tables exist, it searches for these tables in the public schema.
    # See line 655 in Rails 2.3.5 activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
    # That's why I kept running into this error of the table existing when it didn't (in the newly created schema).
    # If used this way [METHOD 2], it works. ActiveRecord::Base.connection.schema_search_path returns the string we pass it.
    conn.schema_search_path = schema_name

    # Directly from databases.rake. 
    # In Rails 2.3.5 databases.rake can be found in railties/lib/tasks/databases.rake
    file = "#{Rails.root}/db/schema.rb"
    if File.exists?(file)
      Rails.logger.info "About to load the schema #{file}"
      load(file)
    else
      abort %{#{file} doesn't exist yet. It's possible that you just ran a migration!}
    end

    Rails.logger.info "About to set search path back to #{old_search_path}."
    conn.schema_search_path = old_search_path
  end
end

注意:由于我不知道原因,此内容不适用于Rails 3。请参见http://groups.google.com/group/rubyonrails-talk/browse_thread/thread/c04c033348aebf3c获取更多详细信息(但没有答案)。 - Ramon Tayag
有关此事是否有任何更新?我正在尝试实现完全相同的事情。在ActiveRecord 3.x Postgres适配器中,我所看到的是table_exists?方法似乎忽略了当前模式...因此,加载schema.rb失败,因为它试图删除一个不存在的表。 - brad
FYI,这是我在 Rails GitHub 页面上提交的一个问题:https://github.com/rails/rails/issues/1518#issuecomment-1316011 - brad
@brad,我正在重新实现这个多租户模式的东西,并决定使用最新的方法。我正在遵循Jerod Santo的博客文章并升级到Rails 3.0.10。当我将schema.rb加载到新创建的模式中时,我得到了相同的关于表存在的错误。我遵循了你的票据,看起来已经合并了。我查看了3.0.10的源代码以确保你的更改已经存在。不幸的是,错误仍然存在!你的补丁是否修复了https://groups.google.com/forum/#!topic/rubyonrails-talk/wEwDM0iuvzw中特别提到的错误? - Ramon Tayag
有趣的是......我们一直在使用我的补丁而没有任何问题。我还没有升级到3.0.10,但我会让你知道我的发现。另外,我写了一个gem来进行多租户管理,我们正在生产环境中使用我的补丁和这个gem。我希望很快就能尝试3.0.10,但在此期间,请查看我的gem并告诉我您的想法。 - brad
不错。还有另一个,但我现在想不起来名字了。它只适用于PG。你的gem很酷 - 当你的DB是PG时,它使用模式! - Ramon Tayag

3

将第38行改为:

conn.schema_search_path = "#{schema_name}, #{old_search_path}"

我猜测当加载schema.rb时,postgres试图查找现有的表名,由于您已将search_path设置为仅包含新模式,因此失败了。当然,这是假设您的数据库中仍然有公共模式。
希望能对您有所帮助。

0
有没有一个已经有这些功能的宝石/插件? pg_power 提供了这个功能来在迁移中创建/删除 PostgreSQL 模式,就像这样:
def change
  # Create schema
  create_schema 'demography'

  # Create new table in specific schema
  create_table "countries", :schema => "demography" do |t|
    # columns goes here
  end

  # Drop schema
  drop_schema 'politics'
end

同时,它还负责将模式正确地转储到schema.rb文件中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接