diff --git a/.pryrc b/.pryrc new file mode 100644 index 00000000..22e16cde --- /dev/null +++ b/.pryrc @@ -0,0 +1,3 @@ +if defined?(Rails) && Rails.env + extend Rails::ConsoleMethods +end diff --git a/README.md b/README.md index de859132..6031d40c 100644 --- a/README.md +++ b/README.md @@ -85,20 +85,19 @@ To set config options, add this to your initializer: ### Excluding models -If you have some models that should always access the 'root' database, you can specify this by configuring -Apartment using `Apartment.configure`. This will yield a config object for you. You can set excluded models like so: +If you have some models that should always access the 'root' database, you can specify this by configuring Apartment using `Apartment.configure`. This will yield a config object for you. You can set excluded models like so: config.excluded_models = ["User", "Company"] # these models will not be multi-tenanted, but remain in the global (public) namespace Note that a string representation of the model name is now the standard so that models are properly constantized when reloaded in development -### Handling Environments +### Providing a default schema -By default, when not using postgresql schemas, Apartment will prepend the environment to the database name -to ensure there is no conflict between your environments. This is mainly for the benefit of your development -and test environments. If you wish to turn this option off in production, you could do something like: +By default, ActiveRecord will use `"$user", public` as the default `schema_search_path`. This can be modified if you wish to use a different default schema be setting: - config.prepend_environment = !Rails.env.production? + config.default_schema = "some_other_schema" + +With that set, all excluded models will use this schema as the table name prefix instead of `public` and `reset` on `Apartment::Database` will return to this schema also ### Managing Migrations @@ -119,7 +118,15 @@ You can then migration your databases using the rake task: This basically invokes `Apartment::Database.migrate(#{db_name})` for each database name supplied from `Apartment.database_names` -### Delayed::Job +### Handling Environments + +By default, when not using postgresql schemas, Apartment will prepend the environment to the database name +to ensure there is no conflict between your environments. This is mainly for the benefit of your development +and test environments. If you wish to turn this option off in production, you could do something like: + + config.prepend_environment = !Rails.env.production? + +## Delayed::Job If using Rails ~> 3.2, you *must* use `delayed_job ~> 3.0`. It has better Rails 3 support plus has some major changes that affect the serialization of models. I haven't been able to get `psych` working whatsoever as the YAML parser, so to get things to work properly, you must explicitly set the parser to `syck` *before* requiring `delayed_job` diff --git a/lib/apartment.rb b/lib/apartment.rb index c76ab27e..6a8d2103 100644 --- a/lib/apartment.rb +++ b/lib/apartment.rb @@ -4,7 +4,7 @@ module Apartment class << self attr_accessor :use_postgres_schemas, :seed_after_create, :prepend_environment - attr_writer :database_names, :excluded_models + attr_writer :database_names, :excluded_models, :default_schema # configure apartment with available options def configure @@ -21,6 +21,9 @@ def excluded_models @excluded_models || [] end + def default_schema + @default_schema || "public" + end end autoload :Database, 'apartment/database' diff --git a/lib/apartment/adapters/postgresql_adapter.rb b/lib/apartment/adapters/postgresql_adapter.rb index 178df229..a12a9e00 100644 --- a/lib/apartment/adapters/postgresql_adapter.rb +++ b/lib/apartment/adapters/postgresql_adapter.rb @@ -4,7 +4,7 @@ module Database def self.postgresql_adapter(config) Apartment.use_postgres_schemas ? - Adapters::PostgresqlSchemaAdapter.new(config, :schema_search_path => ActiveRecord::Base.connection.schema_search_path) : + Adapters::PostgresqlSchemaAdapter.new(config) : Adapters::PostgresqlAdapter.new(config) end end @@ -53,7 +53,7 @@ def drop(database) end # Reset search path to default search_path - # Set the table_name to always use the public namespace for excluded models + # Set the table_name to always use the default namespace for excluded models # def process_excluded_models Apartment.excluded_models.each do |excluded_model| @@ -66,14 +66,14 @@ def process_excluded_models excluded_model.constantize.tap do |klass| # some models (such as delayed_job) seem to load and cache their column names before this, - # so would never get the public prefix, so reset first + # so would never get the default prefix, so reset first klass.reset_column_information # Ensure that if a schema *was* set, we override table_name = klass.table_name.split('.', 2).last # Not sure why, but Delayed::Job somehow ignores table_name_prefix... so we'll just manually set table name instead - klass.table_name = "public.#{table_name}" + klass.table_name = "#{Apartment.default_schema}.#{table_name}" end end end @@ -83,7 +83,7 @@ def process_excluded_models # @return {String} default schema search path # def reset - ActiveRecord::Base.connection.schema_search_path = @defaults[:schema_search_path] + ActiveRecord::Base.connection.schema_search_path = Apartment.default_schema end protected diff --git a/lib/apartment/database.rb b/lib/apartment/database.rb index da0aa5b5..e107c6af 100644 --- a/lib/apartment/database.rb +++ b/lib/apartment/database.rb @@ -3,6 +3,7 @@ module Apartment # The main entry point to Apartment functions + # module Database extend self diff --git a/spec/config/database.yml b/spec/config/database.yml index e61d4c36..f0a6bdc6 100644 --- a/spec/config/database.yml +++ b/spec/config/database.yml @@ -4,6 +4,7 @@ connections: database: apartment_postgresql_test min_messages: WARNING username: postgres + schema_search_path: public password: mysql: diff --git a/spec/database_spec.rb b/spec/database_spec.rb index 09d73752..c3a6e083 100644 --- a/spec/database_spec.rb +++ b/spec/database_spec.rb @@ -26,42 +26,42 @@ end context "using postgresql" do - + # See apartment.yml file in dummy app config - + let(:config){ Apartment::Test.config['connections']['postgresql'].symbolize_keys } let(:database){ Apartment::Test.next_db } let(:database2){ Apartment::Test.next_db } - + before do Apartment.use_postgres_schemas = true ActiveRecord::Base.establish_connection config Apartment::Test.load_schema # load the Rails schema in the public db schema subject.stub(:config).and_return config # Use postgresql database config for this test end - + describe "#adapter" do before do subject.reload! end - + it "should load postgresql adapter" do subject.adapter Apartment::Adapters::PostgresqlAdapter.should be_a(Class) end - + it "should raise exception with invalid adapter specified" do subject.stub(:config).and_return config.merge(:adapter => 'unkown') - + expect { Apartment::Database.adapter }.to raise_error end - + end - + context "with schemas" do - + before do Apartment.configure do |config| config.excluded_models = [] @@ -70,25 +70,25 @@ end subject.create database end - + after{ subject.drop database } - + describe "#create" do it "should seed data" do subject.switch database User.count.should be > 0 end end - + describe "#switch" do - + let(:x){ rand(3) } - + context "creating models" do - + before{ subject.create database2 } after{ subject.drop database2 } - + it "should create a model instance in the current schema" do subject.switch database2 db2_count = User.count + x.times{ User.create } @@ -103,20 +103,20 @@ User.count.should == db_count end end - + context "with excluded models" do - + before do Apartment.configure do |config| config.excluded_models = ["Company"] end subject.init end - + it "should create excluded models in public schema" do subject.reset # ensure we're on public schema count = Company.count + x.times{ Company.create } - + subject.switch database x.times{ Company.create } Company.count.should == count + x @@ -124,10 +124,10 @@ Company.count.should == count + x end end - + end - + end - + end end diff --git a/spec/examples/schema_adapter_examples.rb b/spec/examples/schema_adapter_examples.rb index f7096abd..7022863c 100644 --- a/spec/examples/schema_adapter_examples.rb +++ b/spec/examples/schema_adapter_examples.rb @@ -9,15 +9,26 @@ describe "#init" do - it "should process model exclusions" do + before do Apartment.configure do |config| config.excluded_models = ["Company"] end + end + it "should process model exclusions" do Apartment::Database.init Company.table_name.should == "public.companies" end + + context "with a default_schema", :default_schema => true do + + it "should set the proper table_name on excluded_models" do + Apartment::Database.init + + Company.table_name.should == "#{default_schema}.companies" + end + end end # @@ -91,7 +102,7 @@ it "should reset" do subject.process(schema1) - connection.schema_search_path.should == public_schema + connection.schema_search_path.should start_with public_schema end end @@ -99,7 +110,15 @@ it "should reset connection" do subject.switch(schema1) subject.reset - connection.schema_search_path.should == public_schema + connection.schema_search_path.should start_with public_schema + end + + context "with default_schema", :default_schema => true do + it "should reset to the default schema" do + subject.switch(schema1) + subject.reset + connection.schema_search_path.should start_with default_schema + end end end @@ -134,6 +153,20 @@ after{ subject.drop(db) } end + + describe "with default_schema specified", :default_schema => true do + before do + subject.switch(schema1) + end + + it "should switch out the default schema rather than public" do + connection.schema_search_path.should_not include default_schema + end + + it "should still switch to the switched schema" do + connection.schema_search_path.should start_with schema1 + end + end end describe "#current_database" do diff --git a/spec/support/apartment_helpers.rb b/spec/support/apartment_helpers.rb index a6727d22..771cbaea 100644 --- a/spec/support/apartment_helpers.rb +++ b/spec/support/apartment_helpers.rb @@ -1,38 +1,41 @@ module Apartment module Test - + extend self - + def reset Apartment.excluded_models = nil Apartment.use_postgres_schemas = nil Apartment.seed_after_create = nil + Apartment.default_schema = nil end - + def next_db @x ||= 0 "db_#{@x += 1}" end - + def drop_schema(schema) ActiveRecord::Base.connection.execute("DROP SCHEMA IF EXISTS #{schema} CASCADE") rescue true end - + + # Use this if you don't want to import schema.rb etc... but need the postgres schema to exist + # basically for speed purposes def create_schema(schema) ActiveRecord::Base.connection.execute("CREATE SCHEMA #{schema}") end - + def load_schema silence_stream(STDOUT){ load(Rails.root.join('db', 'schema.rb')) } end - + def migrate ActiveRecord::Migrator.migrate(Rails.root + ActiveRecord::Migrator.migrations_path) end - + def rollback ActiveRecord::Migrator.rollback(Rails.root + ActiveRecord::Migrator.migrations_path) end - + end end \ No newline at end of file diff --git a/spec/support/contexts.rb b/spec/support/contexts.rb new file mode 100644 index 00000000..c52a32af --- /dev/null +++ b/spec/support/contexts.rb @@ -0,0 +1,16 @@ +# Some shared contexts for specs + +shared_context "with default schema", :default_schema => true do + let(:default_schema){ Apartment::Test.next_db } + + before do + Apartment::Test.create_schema(default_schema) + Apartment.default_schema = default_schema + end + + after do + # resetting default_schema so we can drop and any further resets won't try to access droppped schema + Apartment.default_schema = nil + Apartment::Test.drop_schema(default_schema) + end +end \ No newline at end of file diff --git a/spec/support/requirements.rb b/spec/support/requirements.rb index 1e17c763..5cae543c 100644 --- a/spec/support/requirements.rb +++ b/spec/support/requirements.rb @@ -7,9 +7,9 @@ module Spec # # module AdapterRequirements - + extend ActiveSupport::Concern - + included do let(:db1){ Apartment::Test.next_db } let(:db2){ Apartment::Test.next_db } @@ -28,7 +28,7 @@ module AdapterRequirements # sometimes we manually drop these schemas in testing, don't care if we can't drop, hence rescue subject.drop(db1) rescue true subject.drop(db2) rescue true - + ActiveRecord::Base.clear_all_connections! Apartment::Database.reload! end @@ -39,7 +39,7 @@ module AdapterRequirements raise "You must define a `#{method}` method in your host group" end unless defined?(method) end - + end end end \ No newline at end of file