motel-activerecord 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
 - data/README.md +225 -0
 - data/VERSION +1 -0
 - data/lib/motel-activerecord.rb +10 -0
 - data/lib/motel/connection_adapters.rb +3 -0
 - data/lib/motel/connection_adapters/connection_handler.rb +96 -0
 - data/lib/motel/connection_adapters/connection_specification.rb +2 -0
 - data/lib/motel/connection_adapters/connection_specification/resolver.rb +92 -0
 - data/lib/motel/errors.rb +28 -0
 - data/lib/motel/lobby.rb +47 -0
 - data/lib/motel/manager.rb +69 -0
 - data/lib/motel/multi_tenant.rb +53 -0
 - data/lib/motel/railtie.rb +63 -0
 - data/lib/motel/sources.rb +3 -0
 - data/lib/motel/sources/database.rb +120 -0
 - data/lib/motel/sources/default.rb +54 -0
 - data/lib/motel/sources/redis.rb +80 -0
 - data/lib/motel/version.rb +6 -0
 - data/spec/lib/motel/connection_adapters/connection_handler_spec.rb +184 -0
 - data/spec/lib/motel/connection_adapters/connection_specification/resolver_spec.rb +120 -0
 - data/spec/lib/motel/lobby_spec.rb +155 -0
 - data/spec/lib/motel/manager_spec.rb +238 -0
 - data/spec/lib/motel/multi_tenant_spec.rb +169 -0
 - data/spec/lib/motel/sources/database_spec.rb +246 -0
 - data/spec/lib/motel/sources/default_spec.rb +167 -0
 - data/spec/lib/motel/sources/redis_spec.rb +188 -0
 - data/spec/spec_helper.rb +14 -0
 - data/spec/tmp/foo.sqlite3 +0 -0
 - data/spec/tmp/tenants.sqlite3 +0 -0
 - metadata +144 -0
 
    
        data/lib/motel/errors.rb
    ADDED
    
    | 
         @@ -0,0 +1,28 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Motel
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
              class ExistingTenantError < StandardError
         
     | 
| 
      
 4 
     | 
    
         
            +
                def initialize(msg = "Existing tenant")
         
     | 
| 
      
 5 
     | 
    
         
            +
                  super(msg)
         
     | 
| 
      
 6 
     | 
    
         
            +
                end
         
     | 
| 
      
 7 
     | 
    
         
            +
              end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
              class NonexistentTenantError < StandardError
         
     | 
| 
      
 10 
     | 
    
         
            +
                def initialize(msg = "Nonexistent tenant")
         
     | 
| 
      
 11 
     | 
    
         
            +
                  super(msg)
         
     | 
| 
      
 12 
     | 
    
         
            +
                end
         
     | 
| 
      
 13 
     | 
    
         
            +
              end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
              class NoCurrentTenantError < StandardError
         
     | 
| 
      
 16 
     | 
    
         
            +
                def initialize(msg = "No current tenant")
         
     | 
| 
      
 17 
     | 
    
         
            +
                  super(msg)
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
              end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
              class AnonymousTenantError < StandardError
         
     | 
| 
      
 22 
     | 
    
         
            +
                def initialize(msg = "Anonymous tenant is not allowed")
         
     | 
| 
      
 23 
     | 
    
         
            +
                  super(msg)
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
              end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
            end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
    
        data/lib/motel/lobby.rb
    ADDED
    
    | 
         @@ -0,0 +1,47 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'rack'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Motel
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
              class Lobby
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                def initialize(app)
         
     | 
| 
      
 8 
     | 
    
         
            +
                  @app = app
         
     | 
| 
      
 9 
     | 
    
         
            +
                end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                def call(env)
         
     | 
| 
      
 12 
     | 
    
         
            +
                  request = Rack::Request.new(env)
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                  name = tenant_name(request)
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                  if name
         
     | 
| 
      
 17 
     | 
    
         
            +
                    if ActiveRecord::Base.motel.tenant?(name)
         
     | 
| 
      
 18 
     | 
    
         
            +
                      ActiveRecord::Base.motel.current_tenant = name
         
     | 
| 
      
 19 
     | 
    
         
            +
                      @app.call(env)
         
     | 
| 
      
 20 
     | 
    
         
            +
                    else
         
     | 
| 
      
 21 
     | 
    
         
            +
                      path = ActiveRecord::Base.motel.nonexistent_tenant_page
         
     | 
| 
      
 22 
     | 
    
         
            +
                      file = File.expand_path(path) if path
         
     | 
| 
      
 23 
     | 
    
         
            +
                      body = (File.exists?(file.to_s)) ? File.read(file) : "Nonexistent #{name} tenant"
         
     | 
| 
      
 24 
     | 
    
         
            +
                      [404, {"Content-Type" => "text/html", "Content-Length" => body.size.to_s}, [body]]
         
     | 
| 
      
 25 
     | 
    
         
            +
                    end
         
     | 
| 
      
 26 
     | 
    
         
            +
                  else
         
     | 
| 
      
 27 
     | 
    
         
            +
                    ActiveRecord::Base.motel.current_tenant = nil
         
     | 
| 
      
 28 
     | 
    
         
            +
                    @app.call(env)
         
     | 
| 
      
 29 
     | 
    
         
            +
                  end
         
     | 
| 
      
 30 
     | 
    
         
            +
                end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                private
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                  def tenant_name(request)
         
     | 
| 
      
 35 
     | 
    
         
            +
                    if ActiveRecord::Base.motel.admission_criteria
         
     | 
| 
      
 36 
     | 
    
         
            +
                      regex = Regexp.new(ActiveRecord::Base.motel.admission_criteria)
         
     | 
| 
      
 37 
     | 
    
         
            +
                      name = request.path.match(regex)
         
     | 
| 
      
 38 
     | 
    
         
            +
                      name[1] if name
         
     | 
| 
      
 39 
     | 
    
         
            +
                    else
         
     | 
| 
      
 40 
     | 
    
         
            +
                     request.host.split('.').first
         
     | 
| 
      
 41 
     | 
    
         
            +
                    end
         
     | 
| 
      
 42 
     | 
    
         
            +
                  end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
              end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
            end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
         @@ -0,0 +1,69 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'active_support/core_ext/module/attribute_accessors'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'active_support/core_ext/string/inflections'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'active_record'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Motel
         
     | 
| 
      
 6 
     | 
    
         
            +
              module Manager
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                mattr_accessor :nonexistent_tenant_page
         
     | 
| 
      
 9 
     | 
    
         
            +
                mattr_accessor :admission_criteria
         
     | 
| 
      
 10 
     | 
    
         
            +
                mattr_accessor :default_tenant
         
     | 
| 
      
 11 
     | 
    
         
            +
                mattr_accessor :current_tenant
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                class << self
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  def tenants_source_configurations(config)
         
     | 
| 
      
 16 
     | 
    
         
            +
                    source_type = config[:source] || 'default'
         
     | 
| 
      
 17 
     | 
    
         
            +
                    source_class = "Motel::Sources::#{source_type.to_s.camelize}".constantize
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                    source_instance = source_class.new(config)
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                    ActiveRecord::Base.connection_handler.tenants_source = source_instance
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                  def tenants
         
     | 
| 
      
 25 
     | 
    
         
            +
                    tenants_source.tenants
         
     | 
| 
      
 26 
     | 
    
         
            +
                  end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                  def tenant(name)
         
     | 
| 
      
 29 
     | 
    
         
            +
                    tenants_source.tenant(name)
         
     | 
| 
      
 30 
     | 
    
         
            +
                  end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                  def tenant?(name)
         
     | 
| 
      
 33 
     | 
    
         
            +
                    active_tenants.include?(name) || tenants_source.tenant?(name)
         
     | 
| 
      
 34 
     | 
    
         
            +
                  end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                  def add_tenant(name, spec)
         
     | 
| 
      
 37 
     | 
    
         
            +
                    tenants_source.add_tenant(name, spec)
         
     | 
| 
      
 38 
     | 
    
         
            +
                    tenant?(name)
         
     | 
| 
      
 39 
     | 
    
         
            +
                  end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                  def update_tenant(name, spec)
         
     | 
| 
      
 42 
     | 
    
         
            +
                    ActiveRecord::Base.remove_connection(name)
         
     | 
| 
      
 43 
     | 
    
         
            +
                    tenants_source.update_tenant(name, spec)
         
     | 
| 
      
 44 
     | 
    
         
            +
                    tenant(name)
         
     | 
| 
      
 45 
     | 
    
         
            +
                  end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                  def delete_tenant(name)
         
     | 
| 
      
 48 
     | 
    
         
            +
                    ActiveRecord::Base.remove_connection(name)
         
     | 
| 
      
 49 
     | 
    
         
            +
                    tenants_source.delete_tenant(name)
         
     | 
| 
      
 50 
     | 
    
         
            +
                    !tenant?(name)
         
     | 
| 
      
 51 
     | 
    
         
            +
                  end
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                  def active_tenants
         
     | 
| 
      
 54 
     | 
    
         
            +
                    ActiveRecord::Base.connection_handler.active_tenants
         
     | 
| 
      
 55 
     | 
    
         
            +
                  end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                  def determines_tenant
         
     | 
| 
      
 58 
     | 
    
         
            +
                    ENV['TENANT'] || current_tenant || default_tenant
         
     | 
| 
      
 59 
     | 
    
         
            +
                  end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                  def tenants_source
         
     | 
| 
      
 62 
     | 
    
         
            +
                    ActiveRecord::Base.connection_handler.tenants_source
         
     | 
| 
      
 63 
     | 
    
         
            +
                  end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                end
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
              end
         
     | 
| 
      
 68 
     | 
    
         
            +
            end
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
         @@ -0,0 +1,53 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Motel
         
     | 
| 
      
 2 
     | 
    
         
            +
              module MultiTenant
         
     | 
| 
      
 3 
     | 
    
         
            +
                extend ActiveSupport::Concern
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                included do
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                  mattr_accessor :motel, instance_writer: false
         
     | 
| 
      
 8 
     | 
    
         
            +
                  self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new
         
     | 
| 
      
 9 
     | 
    
         
            +
                  self.motel = Manager
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                module ClassMethods
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  def establish_connection(config)
         
     | 
| 
      
 16 
     | 
    
         
            +
                    resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(motel.tenants)
         
     | 
| 
      
 17 
     | 
    
         
            +
                    spec = resolver.spec(config)
         
     | 
| 
      
 18 
     | 
    
         
            +
                    connection_handler.establish_connection (ENV['TENANT'] || self.name), spec
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                  def connection_pool
         
     | 
| 
      
 22 
     | 
    
         
            +
                    connection_handler.retrieve_connection_pool(current_tenant)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                  def retrieve_connection
         
     | 
| 
      
 26 
     | 
    
         
            +
                    connection_handler.retrieve_connection(current_tenant)
         
     | 
| 
      
 27 
     | 
    
         
            +
                  end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                  def connected?
         
     | 
| 
      
 30 
     | 
    
         
            +
                    connection_handler.connected?(current_tenant)
         
     | 
| 
      
 31 
     | 
    
         
            +
                  end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                  def remove_connection(tenant_name = current_tenant)
         
     | 
| 
      
 34 
     | 
    
         
            +
                    connection_handler.remove_connection(tenant_name)
         
     | 
| 
      
 35 
     | 
    
         
            +
                  end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                  def arel_engine
         
     | 
| 
      
 38 
     | 
    
         
            +
                    ActiveRecord::Base
         
     | 
| 
      
 39 
     | 
    
         
            +
                  end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                  def current_tenant
         
     | 
| 
      
 42 
     | 
    
         
            +
                    motel.determines_tenant or raise Motel::NoCurrentTenantError
         
     | 
| 
      
 43 
     | 
    
         
            +
                  end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                end
         
     | 
| 
      
 46 
     | 
    
         
            +
              end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
            end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
            ActiveSupport.on_load(:active_record) do
         
     | 
| 
      
 51 
     | 
    
         
            +
              include Motel::MultiTenant
         
     | 
| 
      
 52 
     | 
    
         
            +
            end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
         @@ -0,0 +1,63 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'active_support/ordered_options'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'motel/manager'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'rails'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Motel
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
              class Railtie < Rails::Railtie
         
     | 
| 
      
 8 
     | 
    
         
            +
                INIT_TO_DELETE = %w(active_record.initialize_database active_record.set_reloader_hooks)
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                config.motel = ActiveSupport::OrderedOptions.new
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                rake_tasks do
         
     | 
| 
      
 13 
     | 
    
         
            +
                  namespace :db do
         
     | 
| 
      
 14 
     | 
    
         
            +
                    task :load_config do
         
     | 
| 
      
 15 
     | 
    
         
            +
                      Motel::Manager.tenants_source_configurations(
         
     | 
| 
      
 16 
     | 
    
         
            +
                        Rails.application.config.motel.tenants_source_configurations
         
     | 
| 
      
 17 
     | 
    
         
            +
                      )
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                      Motel::Manager.current_tenant = "ActiveRecord::Base"
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                      ActiveRecord::Tasks::DatabaseTasks.database_configuration = Motel::Manager.tenants
         
     | 
| 
      
 22 
     | 
    
         
            +
                      ActiveRecord::Tasks::DatabaseTasks.env = Motel::Manager.determines_tenant
         
     | 
| 
      
 23 
     | 
    
         
            +
                    end
         
     | 
| 
      
 24 
     | 
    
         
            +
                  end
         
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                ActiveRecord::Railtie.initializers.delete_if do |i|
         
     | 
| 
      
 28 
     | 
    
         
            +
                  INIT_TO_DELETE.include?(i.name)
         
     | 
| 
      
 29 
     | 
    
         
            +
                end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                initializer "motel.general_configuration" do
         
     | 
| 
      
 32 
     | 
    
         
            +
                  motel_config = Rails.application.config.motel
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                  Motel::Manager.nonexistent_tenant_page = motel_config.nonexistent_tenant_page || 'public/404.html'
         
     | 
| 
      
 35 
     | 
    
         
            +
                  Motel::Manager.admission_criteria = motel_config.admission_criteria
         
     | 
| 
      
 36 
     | 
    
         
            +
                  Motel::Manager.default_tenant = motel_config.default_tenant
         
     | 
| 
      
 37 
     | 
    
         
            +
                  Motel::Manager.current_tenant = motel_config.current_tenant
         
     | 
| 
      
 38 
     | 
    
         
            +
                  Motel::Manager.tenants_source_configurations(motel_config.tenants_source_configurations)
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                initializer "motel.configure_middleware" do |app|
         
     | 
| 
      
 42 
     | 
    
         
            +
                  unless Rails.application.config.motel.disable_middleware
         
     | 
| 
      
 43 
     | 
    
         
            +
                    app.config.middleware.insert_before ActiveRecord::Migration::CheckPending, Lobby
         
     | 
| 
      
 44 
     | 
    
         
            +
                  end
         
     | 
| 
      
 45 
     | 
    
         
            +
                end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                initializer "active_record.set_reloader_hooks" do |app|
         
     | 
| 
      
 48 
     | 
    
         
            +
                  hook = app.config.reload_classes_only_on_change ? :to_prepare : :to_cleanup
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                  ActiveSupport.on_load(:active_record) do
         
     | 
| 
      
 51 
     | 
    
         
            +
                    ActionDispatch::Reloader.send(hook) do
         
     | 
| 
      
 52 
     | 
    
         
            +
                      if ActiveRecord::Base.motel.active_tenants.any?
         
     | 
| 
      
 53 
     | 
    
         
            +
                        ActiveRecord::Base.clear_reloadable_connections!
         
     | 
| 
      
 54 
     | 
    
         
            +
                        ActiveRecord::Base.clear_cache!
         
     | 
| 
      
 55 
     | 
    
         
            +
                      end
         
     | 
| 
      
 56 
     | 
    
         
            +
                    end
         
     | 
| 
      
 57 
     | 
    
         
            +
                  end
         
     | 
| 
      
 58 
     | 
    
         
            +
                end
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
              end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
            end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
         @@ -0,0 +1,120 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'active_record'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Motel
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Sources
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                class Database
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  attr_accessor :source_spec, :table_name
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                  def initialize(config = {})
         
     | 
| 
      
 11 
     | 
    
         
            +
                    @source_spec = config[:source_spec]
         
     | 
| 
      
 12 
     | 
    
         
            +
                    @table_name  = config[:table_name]
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  def tenants
         
     | 
| 
      
 16 
     | 
    
         
            +
                    query_result.inject({}) do |hash, tenant|
         
     | 
| 
      
 17 
     | 
    
         
            +
                      name = tenant.delete('name')
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                      tenant.each do |field, value|
         
     | 
| 
      
 20 
     | 
    
         
            +
                        if table[field].respond_to? :column
         
     | 
| 
      
 21 
     | 
    
         
            +
                          tenant[field] = table[field].column.type_cast(value)
         
     | 
| 
      
 22 
     | 
    
         
            +
                        end
         
     | 
| 
      
 23 
     | 
    
         
            +
                      end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                      hash[name] = tenant
         
     | 
| 
      
 26 
     | 
    
         
            +
                      hash
         
     | 
| 
      
 27 
     | 
    
         
            +
                    end
         
     | 
| 
      
 28 
     | 
    
         
            +
                  end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                  def tenant(name)
         
     | 
| 
      
 31 
     | 
    
         
            +
                    tenants[name]
         
     | 
| 
      
 32 
     | 
    
         
            +
                  end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                  def tenant?(name)
         
     | 
| 
      
 35 
     | 
    
         
            +
                    tenants.key?(name)
         
     | 
| 
      
 36 
     | 
    
         
            +
                  end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                  def add_tenant(name, spec)
         
     | 
| 
      
 39 
     | 
    
         
            +
                    raise ExistingTenantError if tenant?(name)
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                    spec = spec.merge(:name => name.to_s)
         
     | 
| 
      
 42 
     | 
    
         
            +
                    spec.delete_if{ |c,v| v.nil? }
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                    sql = <<-SQL
         
     | 
| 
      
 45 
     | 
    
         
            +
                      INSERT INTO #{table_name} (#{spec.keys.map{|c| "\`#{c}\`"}.join(',')})
         
     | 
| 
      
 46 
     | 
    
         
            +
                      VALUES (#{spec.values.map(&:inspect).join(',')})
         
     | 
| 
      
 47 
     | 
    
         
            +
                    SQL
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                    connection_pool.with_connection { |conn| conn.execute(sql) }
         
     | 
| 
      
 50 
     | 
    
         
            +
                  end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                  def update_tenant(name, spec)
         
     | 
| 
      
 53 
     | 
    
         
            +
                    raise NonexistentTenantError unless tenant?(name)
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                    spec = spec.merge(:name => name.to_s)
         
     | 
| 
      
 56 
     | 
    
         
            +
                    spec.delete_if{ |c,v| v.nil? }
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                    sql = <<-SQL
         
     | 
| 
      
 59 
     | 
    
         
            +
                      UPDATE #{table_name}
         
     | 
| 
      
 60 
     | 
    
         
            +
                      SET #{spec.map{|c, v| "\`#{c}\` = \"#{v}\""}.join(',')}
         
     | 
| 
      
 61 
     | 
    
         
            +
                      WHERE name = "#{name}"
         
     | 
| 
      
 62 
     | 
    
         
            +
                    SQL
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                    connection_pool.with_connection { |conn| conn.execute(sql) }
         
     | 
| 
      
 65 
     | 
    
         
            +
                  end
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                  def delete_tenant(name)
         
     | 
| 
      
 68 
     | 
    
         
            +
                    if tenant?(name)
         
     | 
| 
      
 69 
     | 
    
         
            +
                      sql = <<-SQL
         
     | 
| 
      
 70 
     | 
    
         
            +
                        DELETE FROM #{table_name} WHERE name = "#{name}"
         
     | 
| 
      
 71 
     | 
    
         
            +
                      SQL
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                      connection_pool.with_connection { |conn| conn.execute(sql) }
         
     | 
| 
      
 74 
     | 
    
         
            +
                    end
         
     | 
| 
      
 75 
     | 
    
         
            +
                  end
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                  def connection
         
     | 
| 
      
 78 
     | 
    
         
            +
                    connection_pool.connection
         
     | 
| 
      
 79 
     | 
    
         
            +
                  end
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
                  private
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
                    def table
         
     | 
| 
      
 84 
     | 
    
         
            +
                      @table ||= Arel::Table.new(table_name, self)
         
     | 
| 
      
 85 
     | 
    
         
            +
                    end
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
                    def query
         
     | 
| 
      
 88 
     | 
    
         
            +
                      @query ||= table.project('*')
         
     | 
| 
      
 89 
     | 
    
         
            +
                    end
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
                    def query_result
         
     | 
| 
      
 92 
     | 
    
         
            +
                      connection_pool.with_connection do |conn|
         
     | 
| 
      
 93 
     | 
    
         
            +
                        conn.select_all(query.to_sql)
         
     | 
| 
      
 94 
     | 
    
         
            +
                      end
         
     | 
| 
      
 95 
     | 
    
         
            +
                    end
         
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
      
 97 
     | 
    
         
            +
                    def spec
         
     | 
| 
      
 98 
     | 
    
         
            +
                      @spec ||= begin
         
     | 
| 
      
 99 
     | 
    
         
            +
                        resolver = Motel::ConnectionAdapters::ConnectionSpecification::Resolver.new
         
     | 
| 
      
 100 
     | 
    
         
            +
                        resolver.spec(source_spec)
         
     | 
| 
      
 101 
     | 
    
         
            +
                      end
         
     | 
| 
      
 102 
     | 
    
         
            +
                    end
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
                    def connection_handler
         
     | 
| 
      
 105 
     | 
    
         
            +
                      @connection_handler ||= begin
         
     | 
| 
      
 106 
     | 
    
         
            +
                        handler = ActiveRecord::ConnectionAdapters::ConnectionHandler.new
         
     | 
| 
      
 107 
     | 
    
         
            +
                        handler.establish_connection self.class, spec
         
     | 
| 
      
 108 
     | 
    
         
            +
                        handler
         
     | 
| 
      
 109 
     | 
    
         
            +
                      end
         
     | 
| 
      
 110 
     | 
    
         
            +
                    end
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
                    def connection_pool
         
     | 
| 
      
 113 
     | 
    
         
            +
                      connection_handler.retrieve_connection_pool self.class
         
     | 
| 
      
 114 
     | 
    
         
            +
                    end
         
     | 
| 
      
 115 
     | 
    
         
            +
             
     | 
| 
      
 116 
     | 
    
         
            +
                end
         
     | 
| 
      
 117 
     | 
    
         
            +
             
     | 
| 
      
 118 
     | 
    
         
            +
              end
         
     | 
| 
      
 119 
     | 
    
         
            +
            end
         
     | 
| 
      
 120 
     | 
    
         
            +
             
     | 
| 
         @@ -0,0 +1,54 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'active_record'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Motel
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Sources
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                class Default
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  attr_accessor :tenants
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                  alias :configurations= :tenants=
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                  def initialize(config = {})
         
     | 
| 
      
 13 
     | 
    
         
            +
                    @tenants = config[:configurations] || {}
         
     | 
| 
      
 14 
     | 
    
         
            +
                  end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                  def tenant(name)
         
     | 
| 
      
 17 
     | 
    
         
            +
                    tenants[name]
         
     | 
| 
      
 18 
     | 
    
         
            +
                  end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                  def tenant?(name)
         
     | 
| 
      
 21 
     | 
    
         
            +
                    tenants.key?(name)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                  def add_tenant(name, spec)
         
     | 
| 
      
 25 
     | 
    
         
            +
                    raise ExistingTenantError if tenant?(name)
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                    tenants[name] = keys_to_string(spec)
         
     | 
| 
      
 28 
     | 
    
         
            +
                  end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                  def update_tenant(name, spec)
         
     | 
| 
      
 31 
     | 
    
         
            +
                    raise NonexistentTenantError unless tenant?(name)
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                    spec = keys_to_string(spec)
         
     | 
| 
      
 34 
     | 
    
         
            +
                    tenants[name].merge!(spec)
         
     | 
| 
      
 35 
     | 
    
         
            +
                  end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                  def delete_tenant(name)
         
     | 
| 
      
 38 
     | 
    
         
            +
                    tenants.delete(name)
         
     | 
| 
      
 39 
     | 
    
         
            +
                  end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                  private
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                    def keys_to_string(hash)
         
     | 
| 
      
 44 
     | 
    
         
            +
                      hash = hash.inject({}) do |h, (k, v)|
         
     | 
| 
      
 45 
     | 
    
         
            +
                        h[k.to_s] = v
         
     | 
| 
      
 46 
     | 
    
         
            +
                        h
         
     | 
| 
      
 47 
     | 
    
         
            +
                      end
         
     | 
| 
      
 48 
     | 
    
         
            +
                    end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
              end
         
     | 
| 
      
 53 
     | 
    
         
            +
            end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     |