【问题标题】:Rails: best practice to scope queries based on subdomain?Rails:基于子域的范围查询的最佳实践?
【发布时间】:2009-10-21 19:50:32
【问题描述】:

我正在开发一个使用子域来隔离独立帐户站点的 Rails(当前为 2.3.4)应用程序。明确地说,我的意思是 foo.mysite.com 应该显示 foo 帐户的内容,而 bar.mysite.com 应该显示 bar 的内容。

确保所有模型查询都限定在当前子域范围内的最佳方法是什么?

例如,我的一个控制器看起来像:

@page = @global_organization.pages.find_by_id(params[:id])        

(注意@global_organization 是通过 subdomain-fu 在 application_controller 中设置的。) 当我更喜欢这样的事情时:

@page = Page.find_by_id(params[:id])

Page 模型查找的位置会自动限定到正确的组织。我试过使用这样的 default_scope 指令:(在 Page 模型中)

class Page < ActiveRecord::Base
  default_scope :conditions => "organization_id = #{Thread.current[:organization]}"
  # yadda yadda
end

(同样,需要注意的是,同一个 application_controller 将 Thread.current[:organization] 设置为组织的 id 以进行全局访问。)这种方法的问题是默认范围在第一个请求时设置并且永远不会更改对不同子域的后续请求。

到目前为止,三个明显的解决方案:

1 为每个子域使用单独的虚拟主机,并为每个子域运行不同的应用程序实例(使用 mod_rails)。此方法不适用于此应用。

2 使用上面的原始控制器方法。不幸的是,应用程序中有相当多的模型,其中许多模型是从组织中删除的一些连接,因此这种表示法很快变得很麻烦。更糟糕的是,这主动要求开发人员记住并应用限制或冒着重大安全问题的风险。

3 使用 before_filter 在每个请求上重置模型的默认范围。不确定此处的性能影响或如何最好地选择每个请求更新的模型。

想法?我还缺少其他解决方案吗?这似乎是一个足够普遍的问题,必须有一个最佳实践。感谢所有输入,谢谢!

【问题讨论】:

    标签: ruby-on-rails


    【解决方案1】:

    在此处使用默认范围时要小心,因为它会让您产生错误的安全感,尤其是在创建记录时。

    我一直用你的第一个例子来说明这一点:

    @page = @go.pages.find(params[:id])
    

    最大的原因是因为您还希望确保将此关联应用于新记录,因此您的新建/创建操作将如下所示,确保它们正确地作用于父关联:

    # New
    @page = @go.pages.new
    
    # Create
    @page = @go.pages.create(params[:page])
    

    【讨论】:

    • 好点,在这种情况下,由于连接结构,只有少数地方需要在创建时强制执行,但很多地方需要在读取时强制执行,所以我仍然想找到一种使用 default_scope 的方法。
    • 另外,请查看 github.com/devinterface/authlogic_subdomain_fu_startup_app,这是一个示例应用程序,用于开始使用子域范围的多租户应用程序。它处理与初始用户(也是帐户所有者)的帐户创建。
    • 在控制器中看起来像是额外的工作/代码混乱,但事实并非如此。最后,它显示了程序员的意图,这将使以后的修改变得更加容易。至于需要开发人员记住并应用限制,无论如何您都应该对每个操作进行测试,其中一项测试应确保模型正确地限定在其父级范围内。
    【解决方案2】:

    您是否尝试过将default_scope 定义为lambda?每次使用范围时,都会评估定义选项的 lambda 位。

    class Page < ActiveRecord::Base
      default_scope lambda do 
       {:conditions => "organization_id = #{Thread.current[:organization]}"}
      end
      # yadda yadda
    end
    

    它本质上是在做你的第三个选择,与你之前的过滤魔法一起工作。但它比这更具侵略性,它会在 Page 模型上使用的每一个发现中发挥作用。

    如果您希望所有模型都具有这种行为,您可以将 default_scope 添加到 ActiveRecord::Base,但您提到一些是几个连接。所以如果你走这条路,你将不得不覆盖这些模型中的默认范围来解决连接问题。

    【讨论】:

    • 感谢您的意见!您是否在实践中测试过该代码?我刚刚在应用程序中进行了尝试,虽然我可以确认 lambda 确实在每次查找时都被评估,但 default_scope 不应用返回的值。查看输出 SQL,:conditions 似乎被简单地忽略了。由于各种其他原因,我还不能启动我的调试器,但一旦它恢复运行,我就会达到顶峰。关于为什么它可能不起作用的想法?
    • 老实说,我不知道内部服务器如何使用线程,所以我假设 Thread.current[:organization] 在模型中工作。但我不知道这是否可以解释为什么忽略这些条件。因为您实际上是在定义默认范围以查找organazation_id = nil。
    • 经过进一步调查,这似乎还不起作用。有一个补丁,但是 YMMV:rails.lighthouseapp.com/projects/8994/tickets/…
    【解决方案3】:

    过去 2 年我们一直在使用 https://github.com/penguincoder/acts_as_restricted_subdomain,但它仅适用于 Rails 2.3。

    我们目前正在尝试升级(并优化)插件以与 Rails 3 一起使用。我很好奇您是如何解决问题的。

    【讨论】:

      【解决方案4】:

      您最好使用基于子域的database per account and switching the database connection

      除了上面的链接,如果您有一个模型(在您的情况下为帐户),您想使用默认数据库,只需在模型中包含 establish connection

      class Account < ActiveRecord::Base
      
        # Always use shared database
        establish_connection  "shared_#{RAILS_ENV}".to_sym
      

      【讨论】:

      • 可能是一个可行的解决方案,但会使跨帐户运行报告(分析)变得非常痛苦。还有迁移等。
      • 是的,迁移对我来说是一个痛点。我记得看到一个允许迁移的解决方案可以针对数据库列表运行,但我不记得在哪里。
      【解决方案5】:

      我发现acts_as_tenant gem [github] 非常适合此功能。它以最少的额外工作为您完成范围界定,并且还使得绕过范围界定变得困难。

      这是关于它的最初博客文章:http://www.rollcallapp.com/blog/2011/10/03/adding-multi-tenancy-to-your-rails-app-acts-as-tenant

      【讨论】:

        猜你喜欢
        • 2014-03-15
        • 2011-06-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-12-18
        • 1970-01-01
        • 2023-03-25
        • 2020-06-23
        相关资源
        最近更新 更多