roomer 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +17 -0
- data/.gitignore +9 -0
- data/.rvmrc +2 -0
- data/.travis.yml +2 -0
- data/Gemfile +31 -0
- data/MIT-LICENSE +20 -0
- data/README.md +116 -0
- data/Rakefile +16 -0
- data/TODO.md +10 -0
- data/doc/ActiveRecord/Base.html +328 -0
- data/doc/ApplicationController.html +116 -0
- data/doc/Roomer/Engine.html +116 -0
- data/doc/Roomer/Generators/InstallGenerator.html +419 -0
- data/doc/Roomer/Generators/SetupGenerator.html +209 -0
- data/doc/Roomer/Generators.html +108 -0
- data/doc/Roomer/Tools.html +702 -0
- data/doc/Roomer/VERSION.html +149 -0
- data/doc/Roomer.html +533 -0
- data/doc/RoomerCreate.html +121 -0
- data/doc/_index.html +232 -0
- data/doc/class_list.html +47 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +53 -0
- data/doc/css/style.css +320 -0
- data/doc/file.README.html +146 -0
- data/doc/file_list.html +49 -0
- data/doc/frames.html +13 -0
- data/doc/index.html +146 -0
- data/doc/js/app.js +205 -0
- data/doc/js/full_list.js +150 -0
- data/doc/js/jquery.js +16 -0
- data/doc/method_list.html +182 -0
- data/doc/top-level-namespace.html +105 -0
- data/lib/generators/roomer/install/install_generator.rb +39 -0
- data/lib/generators/roomer/install/templates/README +15 -0
- data/lib/generators/roomer/install/templates/roomer.rb +68 -0
- data/lib/generators/roomer/migration/migration_generator.rb +35 -0
- data/lib/generators/roomer/migration/templates/migration.rb +17 -0
- data/lib/generators/roomer/model/model_generator.rb +45 -0
- data/lib/generators/roomer/model/templates/migration.rb +16 -0
- data/lib/generators/roomer/setup/setup_generator.rb +38 -0
- data/lib/generators/roomer/setup/templates/global_migration.rb +20 -0
- data/lib/roomer/extensions/controller.rb +36 -0
- data/lib/roomer/extensions/model.rb +72 -0
- data/lib/roomer/helpers/generator_helper.rb +30 -0
- data/lib/roomer/helpers/migration_helper.rb +7 -0
- data/lib/roomer/helpers/postgres_helper.rb +75 -0
- data/lib/roomer/rails.rb +24 -0
- data/lib/roomer/schema.rb +63 -0
- data/lib/roomer/schema_dumper.rb +36 -0
- data/lib/roomer/tasks/migrate.rake +85 -0
- data/lib/roomer/utils.rb +68 -0
- data/lib/roomer/version.rb +14 -0
- data/lib/roomer.rb +125 -0
- data/roomer.gemspec +22 -0
- data/test/config.rb +3 -0
- data/test/config.yml +7 -0
- data/test/generators/install_generator_test.rb +13 -0
- data/test/generators/migration_generator_test.rb +33 -0
- data/test/generators/model_generator_test.rb +25 -0
- data/test/generators/setup_generator_test.rb +25 -0
- data/test/rails_app/app/controllers/application_controller.rb +3 -0
- data/test/rails_app/app/helpers/application_helper.rb +2 -0
- data/test/rails_app/app/models/tenant.rb +12 -0
- data/test/rails_app/app/views/layouts/application.html.erb +14 -0
- data/test/rails_app/config/application.rb +50 -0
- data/test/rails_app/config/boot.rb +6 -0
- data/test/rails_app/config/database.yml +2 -0
- data/test/rails_app/config/environment.rb +5 -0
- data/test/rails_app/config/environments/development.rb +26 -0
- data/test/rails_app/config/environments/production.rb +49 -0
- data/test/rails_app/config/environments/test.rb +35 -0
- data/test/rails_app/config/initializers/backtrace_silencers.rb +7 -0
- data/test/rails_app/config/initializers/inflections.rb +10 -0
- data/test/rails_app/config/initializers/mime_types.rb +5 -0
- data/test/rails_app/config/initializers/roomer.rb +68 -0
- data/test/rails_app/config/initializers/secret_token.rb +7 -0
- data/test/rails_app/config/initializers/session_store.rb +8 -0
- data/test/rails_app/config/locales/en.yml +5 -0
- data/test/rails_app/config/routes.rb +58 -0
- data/test/rails_app/config.ru +4 -0
- data/test/rails_app/db/migrate/global/20110825231409_roomer_create_tenants.rb +20 -0
- data/test/rails_app/db/seeds.rb +7 -0
- data/test/rails_app/lib/tasks/.gitkeep +0 -0
- data/test/rails_app/public/404.html +26 -0
- data/test/rails_app/public/422.html +26 -0
- data/test/rails_app/public/500.html +26 -0
- data/test/rails_app/public/favicon.ico +0 -0
- data/test/rails_app/public/images/rails.png +0 -0
- data/test/rails_app/public/index.html +239 -0
- data/test/rails_app/public/javascripts/application.js +2 -0
- data/test/rails_app/public/javascripts/controls.js +965 -0
- data/test/rails_app/public/javascripts/dragdrop.js +974 -0
- data/test/rails_app/public/javascripts/effects.js +1123 -0
- data/test/rails_app/public/javascripts/prototype.js +6001 -0
- data/test/rails_app/public/javascripts/rails.js +191 -0
- data/test/rails_app/public/robots.txt +5 -0
- data/test/rails_app/public/stylesheets/.gitkeep +0 -0
- data/test/roomer/helpers/postgres_helper_test.rb +62 -0
- data/test/roomer/roomer_test.rb +37 -0
- data/test/test_helper.rb +23 -0
- metadata +228 -0
@@ -0,0 +1,72 @@
|
|
1
|
+
module Roomer
|
2
|
+
module Extensions
|
3
|
+
module Model
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
# Sets the roomer scope for the model and changes the model's table_name_prefix
|
10
|
+
# Sets the table name prefix (schema name) to current_tenant's
|
11
|
+
# If :shared is passed, the global schema will be used as the table name prefix
|
12
|
+
# if :tenanted is pased, the current tenant's schema will be used as the table name prefix
|
13
|
+
# @return [Symbol] :shared or :tenanted
|
14
|
+
def roomer(scope)
|
15
|
+
case scope
|
16
|
+
when :shared
|
17
|
+
@roomer_scope = :shared
|
18
|
+
when :tenanted
|
19
|
+
@roomer_scope = :tenanted
|
20
|
+
else
|
21
|
+
raise "Invalid roomer model scope. Choose :shared or :tenanted"
|
22
|
+
end
|
23
|
+
roomer_set_table_name_prefix
|
24
|
+
end
|
25
|
+
|
26
|
+
# Confirms if model is shared
|
27
|
+
# @return [True,False]
|
28
|
+
def shared?
|
29
|
+
@roomer_scope == :shared
|
30
|
+
end
|
31
|
+
|
32
|
+
# Confirms if model is scoped by tenant
|
33
|
+
# @return [True,False]
|
34
|
+
def tenanted?
|
35
|
+
@roomer_scope == :tenanted
|
36
|
+
end
|
37
|
+
|
38
|
+
# Resets model's cached table_name and
|
39
|
+
# column information.
|
40
|
+
# This method needs to get called whenever
|
41
|
+
# the current tenant changes
|
42
|
+
def roomer_reset
|
43
|
+
roomer_set_table_name_prefix
|
44
|
+
reset_table_name
|
45
|
+
reset_column_information
|
46
|
+
end
|
47
|
+
|
48
|
+
protected
|
49
|
+
# Resolves the full table name prefix
|
50
|
+
def roomer_full_table_name_prefix(schema_name)
|
51
|
+
"#{schema_name.to_s}#{Roomer.schema_seperator}"
|
52
|
+
end
|
53
|
+
|
54
|
+
# Sets the model's table name prefix to the current tenant's schema name
|
55
|
+
# Defaults to public if model is marked as tenanted but tenant table
|
56
|
+
# hasn't been populated
|
57
|
+
def roomer_set_table_name_prefix
|
58
|
+
self.table_name_prefix = begin
|
59
|
+
case @roomer_scope
|
60
|
+
when :shared
|
61
|
+
roomer_full_table_name_prefix(Roomer.shared_schema_name)
|
62
|
+
when :tenanted
|
63
|
+
roomer_full_table_name_prefix(Roomer.current_tenant.try(Roomer.tenant_schema_name_column))
|
64
|
+
else
|
65
|
+
""
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Roomer
|
2
|
+
module Helpers
|
3
|
+
module GeneratorHelper
|
4
|
+
|
5
|
+
# Check to see if the model file exists, should be used in a Generator
|
6
|
+
# @return [True,False]
|
7
|
+
def model_exists?
|
8
|
+
File.exists?(File.join(destination_root, model_path))
|
9
|
+
end
|
10
|
+
|
11
|
+
# Returns the path of the model
|
12
|
+
# @return [String] model_path string representing the model location
|
13
|
+
def model_path
|
14
|
+
@model_path ||= File.join("app", "models", "#{file_path}.rb")
|
15
|
+
end
|
16
|
+
|
17
|
+
# Reads the --shared option specified when running "rails generate roomer:model"
|
18
|
+
# @return [True,False]
|
19
|
+
def shared?
|
20
|
+
@shared ||= options[:shared]
|
21
|
+
end
|
22
|
+
|
23
|
+
# Fetchs the migration directory for the migrations
|
24
|
+
def migration_dir
|
25
|
+
return Roomer.shared_migrations_directory if shared?
|
26
|
+
return Roomer.tenanted_migrations_directory
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Roomer
|
2
|
+
module Helpers
|
3
|
+
module PostgresHelper
|
4
|
+
|
5
|
+
# Creates a schema in PostgreSQL
|
6
|
+
# @param [#to_s] schema_name declaring the name of the schema
|
7
|
+
def create_schema(schema_name)
|
8
|
+
ActiveRecord::Base.connection.execute "CREATE SCHEMA \"#{schema_name.to_s}\""
|
9
|
+
end
|
10
|
+
|
11
|
+
# Drops a schema and all it's objects (Cascades)
|
12
|
+
# @param [#to_s] schema_name declaring the name of the schema to drop
|
13
|
+
def drop_schema(schema_name)
|
14
|
+
ActiveRecord::Base.connection.execute("DROP SCHEMA IF EXISTS \"#{schema_name.to_s}\" CASCADE")
|
15
|
+
end
|
16
|
+
|
17
|
+
# lists the schemas available
|
18
|
+
# @return [Array] list of schemas
|
19
|
+
def schemas
|
20
|
+
ActiveRecord::Base.connection.query("SELECT nspname FROM pg_namespace WHERE nspname !~ '^pg_.*'").flatten.map(&:to_s)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Ensures the schema and schema_migrations exist(creates them otherwise)
|
24
|
+
# and executes the code block
|
25
|
+
# @param [#to_s] schema_name declaring name to ensure
|
26
|
+
# @param [#call] &block code to execute
|
27
|
+
#
|
28
|
+
# Example:
|
29
|
+
#
|
30
|
+
# ensuring_schema(:global) do
|
31
|
+
# ActiveRecord::Migrator.migrate('/db/migrate', '20110812012536')
|
32
|
+
# end
|
33
|
+
def ensuring_schema(schema_name, &block)
|
34
|
+
raise ArgumentError.new("schema_name not present") unless schema_name
|
35
|
+
ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
|
36
|
+
create_schema(schema_name) unless schemas.include?(schema_name.to_s)
|
37
|
+
ensure_prefix(schema_name) do
|
38
|
+
ensure_schema_migrations
|
39
|
+
yield
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Ensures the same ActiveRecord::Base#table_name_prefix for all the
|
44
|
+
# models executed in the block
|
45
|
+
# @param [#to_s] A Symbol declaring the table name prefix
|
46
|
+
# @param [#call] code to execute
|
47
|
+
# @note All the Models will have the same prefix, caution is advised
|
48
|
+
#
|
49
|
+
# Example:
|
50
|
+
#
|
51
|
+
# ensure_prefix(:global) do
|
52
|
+
# Person.find(1) # => will execute "SELECT id FROM 'global.person' where 'id' = 1"
|
53
|
+
# end
|
54
|
+
def ensure_prefix(prefix, &block)
|
55
|
+
@old_prefix = ActiveRecord::Base.table_name_prefix
|
56
|
+
ActiveRecord::Base.table_name_prefix = "#{prefix.to_s}#{Roomer.schema_seperator.to_s}"
|
57
|
+
yield
|
58
|
+
ActiveRecord::Base.table_name_prefix = @old_prefix
|
59
|
+
end
|
60
|
+
|
61
|
+
# Ensures schema_migrations table exists and creates otherwise
|
62
|
+
# @see ActiveRecord::Base.connection#initialize_schema_migrations_table
|
63
|
+
def ensure_schema_migrations
|
64
|
+
ActiveRecord::Base.connection.initialize_schema_migrations_table
|
65
|
+
end
|
66
|
+
|
67
|
+
# Determine if there are any pending migrations in the shared migrations directory
|
68
|
+
# @returns true if migrations are pending
|
69
|
+
def shared_migrations_pending?
|
70
|
+
ActiveRecord::Migrator.new(:up,Roomer.shared_migrations_directory)
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/lib/roomer/rails.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module Roomer
|
2
|
+
class RoomerEngine < ::Rails::Engine
|
3
|
+
|
4
|
+
initializer 'roomer.extensions' do |app|
|
5
|
+
# load model extensions
|
6
|
+
ActiveSupport.on_load(:active_record) do
|
7
|
+
include Roomer::Extensions::Model
|
8
|
+
end
|
9
|
+
|
10
|
+
# load controller extensions
|
11
|
+
ActiveSupport.on_load(:action_controller) do
|
12
|
+
include Roomer::Extensions::Controller
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# load all the rake tasks
|
17
|
+
rake_tasks do
|
18
|
+
Dir[File.expand_path("../tasks/*.rake", __FILE__)].each do |file|
|
19
|
+
load file
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'active_support/core_ext/object/blank'
|
2
|
+
|
3
|
+
module Roomer
|
4
|
+
# Roomer::Schema extends ActiveRecord::Schema
|
5
|
+
# Roomer::Schema is currently only supported by the Postgres database adapter
|
6
|
+
|
7
|
+
class Schema < ActiveRecord::Migration
|
8
|
+
extend Roomer::Helpers::PostgresHelper
|
9
|
+
|
10
|
+
def self.migrations_path(scope=:tenanted)
|
11
|
+
case scope
|
12
|
+
when :shared
|
13
|
+
return Roomer.shared_migrations_directory
|
14
|
+
when :tenanted
|
15
|
+
return Roomer.tenanted_migrations_directory
|
16
|
+
else
|
17
|
+
raise 'Invalid scope parameter'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.dump(scope=:tenanted)
|
22
|
+
case scope
|
23
|
+
when :shared
|
24
|
+
schema_name = Roomer.shared_schema_name.to_s
|
25
|
+
filename = Roomer.shared_schema_filename
|
26
|
+
when :tenanted
|
27
|
+
tenant = Tenant.first
|
28
|
+
return if tenant.blank?
|
29
|
+
|
30
|
+
schema_name = Tenant.first.schema_name.to_s
|
31
|
+
filename = Roomer.tenanted_schema_filename
|
32
|
+
end
|
33
|
+
FileUtils.mkdir_p(Roomer.schemas_directory) unless File.exists?(Roomer.schemas_directory)
|
34
|
+
filepath = File.expand_path(File.join(Roomer.schemas_directory, filename))
|
35
|
+
ActiveRecord::Base.connection.schema_search_path = schema_name
|
36
|
+
ActiveRecord::Base.table_name_prefix = "#{schema_name}."
|
37
|
+
Roomer::SchemaDumper.dump(ActiveRecord::Base.connection, File.new(filepath, "w"))
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.load(schema_name, scope=:tenanted)
|
41
|
+
filename = begin
|
42
|
+
if scope == :shared
|
43
|
+
Roomer.shared_schema_filename
|
44
|
+
elsif scope == :tenanted
|
45
|
+
Roomer.tenanted_schema_filename
|
46
|
+
end
|
47
|
+
end
|
48
|
+
filepath = File.expand_path(File.join(Roomer.schemas_directory, filename))
|
49
|
+
return unless File.exists?(filepath)
|
50
|
+
|
51
|
+
ensuring_schema(schema_name) do
|
52
|
+
Object.load(filepath)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.define(info={}, &block)
|
57
|
+
instance_eval(&block)
|
58
|
+
unless info[:version].blank?
|
59
|
+
ActiveRecord::Base.connection.assume_migrated_upto_version(info[:version], migrations_path)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Roomer
|
2
|
+
class SchemaDumper < ActiveRecord::SchemaDumper
|
3
|
+
|
4
|
+
protected
|
5
|
+
def header(stream)
|
6
|
+
define_params = @version ? ":version => #{@version}" : ""
|
7
|
+
stream.puts <<HEADER
|
8
|
+
# It's strongly recommended to check this file into your version control system.
|
9
|
+
|
10
|
+
Roomer::Schema.define(#{define_params}) do
|
11
|
+
|
12
|
+
HEADER
|
13
|
+
end
|
14
|
+
|
15
|
+
def indexes(table, stream)
|
16
|
+
if (indexes = @connection.indexes(table)).any?
|
17
|
+
add_index_statements = indexes.map do |index|
|
18
|
+
statement_parts = [ ('add_index ' + index.table.inspect) ]
|
19
|
+
statement_parts << index.columns.inspect
|
20
|
+
statement_parts << (':name => "' + index.columns.first.to_s.split('.').last + '"')
|
21
|
+
statement_parts << ':unique => true' if index.unique
|
22
|
+
|
23
|
+
index_lengths = index.lengths.compact if index.lengths.is_a?(Array)
|
24
|
+
if index_lengths.present?
|
25
|
+
statement_parts << (':length => ' + Hash[*index.columns.zip(index.lengths).flatten].inspect)
|
26
|
+
end
|
27
|
+
|
28
|
+
' ' + statement_parts.join(', ')
|
29
|
+
end
|
30
|
+
|
31
|
+
stream.puts add_index_statements.sort.join("\n")
|
32
|
+
stream.puts
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
include Roomer::Helpers::PostgresHelper
|
2
|
+
include Roomer::Helpers::GeneratorHelper
|
3
|
+
|
4
|
+
namespace :roomer do
|
5
|
+
namespace :shared do
|
6
|
+
desc "Migrates the shared tables. Target specific version with VERSION=x"
|
7
|
+
task :migrate => :environment do
|
8
|
+
version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
|
9
|
+
ensuring_schema(Roomer.shared_schema_name) do
|
10
|
+
ActiveRecord::Migrator.migrate(Roomer.shared_migrations_directory, version)
|
11
|
+
end
|
12
|
+
Roomer::Schema.dump(:shared)
|
13
|
+
end
|
14
|
+
|
15
|
+
desc "Rolls back shared tables. Target specific version with STEP=x"
|
16
|
+
task :rollback => :environment do
|
17
|
+
step = ENV['STEP'] ? ENV['STEP'].to_i : 1
|
18
|
+
ensuring_schema(Roomer.shared_schema_name) do
|
19
|
+
ActiveRecord::Migrator.rollback(Roomer.shared_migrations_directory, step)
|
20
|
+
end
|
21
|
+
Roomer::Schema.dump(:shared)
|
22
|
+
end
|
23
|
+
|
24
|
+
namespace :schema do
|
25
|
+
desc "Load shared schema into database"
|
26
|
+
task :load => :environment do
|
27
|
+
Roomer::Schema.load(Roomer.shared_schema_name, :shared)
|
28
|
+
end
|
29
|
+
|
30
|
+
desc "Dumps the shared schema to shared/schema.rb. This file can be portably used against any DB supported by AR"
|
31
|
+
task :dump => :environment do
|
32
|
+
Roomer::Schema.dump(:shared)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
namespace :tenanted do
|
38
|
+
desc "Migrates the tenanted tables. Target specific version with VERSION=x"
|
39
|
+
task :migrate => :environment do
|
40
|
+
version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
|
41
|
+
Roomer.tenant_model.find(:all).each do |tenant|
|
42
|
+
ensuring_schema(tenant.try(Roomer.tenant_schema_name_column)) do
|
43
|
+
ActiveRecord::Migrator.migrate(Roomer.tenanted_migrations_directory, version)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
Roomer::Schema.dump(:tenanted)
|
47
|
+
end
|
48
|
+
|
49
|
+
desc "Rolls back tenanted tables. Target specific version with STEP=x"
|
50
|
+
task :rollback => :environment do
|
51
|
+
step = ENV['STEP'] ? ENV['STEP'].to_i : 1
|
52
|
+
Roomer.tenant_model.find(:all).each do |tenant|
|
53
|
+
ensuring_schema(tenant.try(Roomer.tenant_schema_name_column)) do
|
54
|
+
ActiveRecord::Migrator.rollback(Roomer.tenanted_migrations_directory, step)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
Roomer::Schema.dump(:tenanted)
|
58
|
+
end
|
59
|
+
|
60
|
+
namespace :schema do
|
61
|
+
desc "Load tenanted schema into database"
|
62
|
+
task :load => :environment do
|
63
|
+
schema_name = ENV['SCHEMA_NAME']
|
64
|
+
raise "No schema name provided. Try: env SCHEMA_NAME=" if schema_name.blank?
|
65
|
+
Roomer::Schema.load(schema_name)
|
66
|
+
end
|
67
|
+
|
68
|
+
desc "Dumps tenanted schema file to migrate/schema.rb. This file can be portably used against any DB supported by AR"
|
69
|
+
task :dump => :environment do
|
70
|
+
Roomer::Schema.dump
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
desc "Runs shared and tenanted migrations"
|
76
|
+
task :migrate do
|
77
|
+
if ENV["VERSION"].blank?
|
78
|
+
Rake::Task["roomer:shared:migrate"].invoke
|
79
|
+
Rake::Task["roomer:tenanted:migrate"].invoke
|
80
|
+
else
|
81
|
+
Rake::Task["roomer:tenanted:migrate"].invoke
|
82
|
+
Rake::Task["roomer:shared:migrate"].invoke
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/lib/roomer/utils.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
module Roomer
|
2
|
+
module Utils
|
3
|
+
|
4
|
+
# Rails DB Migrations Directory
|
5
|
+
# @return [String] full path to the current migrations directory
|
6
|
+
def migrations_directory
|
7
|
+
ActiveRecord::Migrator.migrations_path
|
8
|
+
end
|
9
|
+
|
10
|
+
# Directory where the models are stored
|
11
|
+
# @return [String] path of the directory where the models are stored
|
12
|
+
def model_directory
|
13
|
+
File.join("app","models")
|
14
|
+
end
|
15
|
+
|
16
|
+
# Consutructs the full name for the tenants table with schema
|
17
|
+
# Example: 'global.tenant'
|
18
|
+
# @return [String] full name of the tenant table
|
19
|
+
def full_tenants_table_name
|
20
|
+
"#{shared_schema_name}#{schema_seperator}#{tenants_table}"
|
21
|
+
end
|
22
|
+
|
23
|
+
# Constructs the full path to the shared schema directory
|
24
|
+
# Example: /Users/Greg/Projects/roomer/db/migrate/global
|
25
|
+
# @return [String] full path to the shared schema directory
|
26
|
+
def full_shared_schema_migration_path
|
27
|
+
"#{Rails.root}/#{shared_migrations_directory}"
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns tenant model as a constant
|
31
|
+
# Example: Tenant
|
32
|
+
# @return Object
|
33
|
+
def tenant_model
|
34
|
+
Roomer.tenants_table.to_s.classify.constantize
|
35
|
+
end
|
36
|
+
|
37
|
+
# Sets current tenant from ApplicationController into a Thread
|
38
|
+
# local variable. Works only with thread-safe Rails as long as
|
39
|
+
# it gets set on every request
|
40
|
+
# @return [Symbol] the current tenant key in the thread
|
41
|
+
def current_tenant=(val)
|
42
|
+
key = :"roomer_current_tenant"
|
43
|
+
Thread.current[key] = val
|
44
|
+
ensure_tenant_model_reset
|
45
|
+
end
|
46
|
+
|
47
|
+
# Fetches the current tenant
|
48
|
+
# @return [Symbol] the current tenant key in the thread
|
49
|
+
def current_tenant
|
50
|
+
key = :"roomer_current_tenant"
|
51
|
+
Thread.current[key]
|
52
|
+
end
|
53
|
+
|
54
|
+
# Reset current tenant
|
55
|
+
# @return [Nil]
|
56
|
+
def reset_current_tenant
|
57
|
+
key = :"roomer_current_tenant"
|
58
|
+
Thread.current[key] = nil
|
59
|
+
end
|
60
|
+
|
61
|
+
# Reset cached data in tenanted models
|
62
|
+
def ensure_tenant_model_reset
|
63
|
+
ActiveRecord::Base.descendants.each do |model|
|
64
|
+
model.roomer_reset if model.tenanted?
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/roomer.rb
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'rails'
|
2
|
+
require 'active_support'
|
3
|
+
require 'active_support/dependencies'
|
4
|
+
require 'active_record'
|
5
|
+
require 'roomer/version'
|
6
|
+
|
7
|
+
module ActiveRecord
|
8
|
+
autoload :Migration, 'active_record/migration'
|
9
|
+
end
|
10
|
+
|
11
|
+
module Roomer
|
12
|
+
autoload :Utils, 'roomer/utils'
|
13
|
+
|
14
|
+
module Helpers
|
15
|
+
autoload :GeneratorHelper, 'roomer/helpers/generator_helper'
|
16
|
+
autoload :PostgresHelper, 'roomer/helpers/postgres_helper'
|
17
|
+
autoload :MigrationHelper, 'roomer/helpers/migration_helper'
|
18
|
+
end
|
19
|
+
|
20
|
+
module Extensions
|
21
|
+
autoload :Model, 'roomer/extensions/model'
|
22
|
+
autoload :Controller, 'roomer/extensions/controller'
|
23
|
+
end
|
24
|
+
|
25
|
+
autoload :SchemaDumper, 'roomer/schema_dumper'
|
26
|
+
autoload :Schema, 'roomer/schema'
|
27
|
+
|
28
|
+
extend Utils
|
29
|
+
|
30
|
+
# The URL routing strategy. Roomer currently supports two routing strategies (:domain and :path)
|
31
|
+
# * :domain - Using domain name to identify the tenant. This could include a subdomain
|
32
|
+
# * :path - identifying the tenant by the path
|
33
|
+
# Example:
|
34
|
+
# Domain
|
35
|
+
# ------
|
36
|
+
# http://mytenant.myapp.com - If you tenant has a subdomain under your domain
|
37
|
+
# http://mytenant.com - If the tenant choose to use their own top level domain name
|
38
|
+
# http://myapp.mytenant.com If the tenant chooses to use their own subdomain under thier TLD
|
39
|
+
# Path
|
40
|
+
# ----
|
41
|
+
# http://yourapp.com/tenant
|
42
|
+
mattr_accessor :url_routing_strategy
|
43
|
+
@@url_routing_strategy = :domain
|
44
|
+
|
45
|
+
# name of the shared schema where all the shared tables are be present
|
46
|
+
mattr_accessor :shared_schema_name
|
47
|
+
@@shared_schema_name = :global
|
48
|
+
|
49
|
+
# The name of the table where the tenants are stored, this table must
|
50
|
+
# contain two required columns configured under tenant_url_identifier_column and
|
51
|
+
# tenant_schema_name_column
|
52
|
+
mattr_accessor :tenants_table
|
53
|
+
@@tenants_table = :tenants
|
54
|
+
|
55
|
+
# The column name that stores the url identfier in the tenants tables.
|
56
|
+
# A url idenfier is a unique value that identifies the tenant from the URL.
|
57
|
+
# For e.g: if you use domains and the url is http://mytenant.myapp.com,
|
58
|
+
# the url identifier would "mytenant"
|
59
|
+
mattr_accessor :tenant_url_identifier_column
|
60
|
+
@@tenant_url_identifier_column = :url_identifier
|
61
|
+
|
62
|
+
# The column name that strores the schema name in the tenants tables.
|
63
|
+
mattr_accessor :tenant_schema_name_column
|
64
|
+
@@tenant_schema_name_column = :schema_name
|
65
|
+
|
66
|
+
# The schema seperator. This is used when generating the table name.
|
67
|
+
# The default is set to "."
|
68
|
+
# Example: tenant's table by default will be global.tenants
|
69
|
+
mattr_accessor :schema_seperator
|
70
|
+
@@schema_seperator = '.'
|
71
|
+
|
72
|
+
# Directory where schema files will be stored.
|
73
|
+
mattr_accessor :schemas_directory
|
74
|
+
@@schemas_directory = File.expand_path(File.join("db", "schemas"))
|
75
|
+
|
76
|
+
# Tenanted schema filename.
|
77
|
+
mattr_accessor :tenanted_schema_filename
|
78
|
+
@@tenanted_schema_filename = "tenanted_schema.rb"
|
79
|
+
|
80
|
+
# Shared schema filename.
|
81
|
+
mattr_accessor :shared_schema_filename
|
82
|
+
@@shared_schema_filename = "shared_schema.rb"
|
83
|
+
|
84
|
+
# Use Tentant migrations directory?
|
85
|
+
# Default is set to false
|
86
|
+
mattr_accessor :use_tenanted_migrations_directory
|
87
|
+
alias_method :use_tenanted_migrations_directory?,
|
88
|
+
:use_tenanted_migrations_directory
|
89
|
+
@@use_tenanted_migrations_directory = false
|
90
|
+
|
91
|
+
# Directory where the tenanted migrations are stored.
|
92
|
+
# This will be used only if use_tenanted_migration_directory is set to
|
93
|
+
# true if not usual rails migraiton directory db/migrate will be used
|
94
|
+
mattr_writer :tenanted_migrations_directory
|
95
|
+
@@tenanted_migrations_directory = File.join(migrations_directory,tenants_table.to_s)
|
96
|
+
|
97
|
+
|
98
|
+
# Directory where shared migrations are stored.
|
99
|
+
mattr_accessor :shared_migrations_directory
|
100
|
+
@@shared_migrations_directory = File.join(migrations_directory,shared_schema_name.to_s)
|
101
|
+
|
102
|
+
# Fetches the migrations directory for Tenanted migrations.
|
103
|
+
# returns the standard rails migration directory "db/migrate" is the
|
104
|
+
# use_tenanted_migrations_directory is set to false
|
105
|
+
# @return [String] String representing the tenanted migrations
|
106
|
+
def self.tenanted_migrations_directory
|
107
|
+
if self.use_tenanted_migrations_directory
|
108
|
+
return @@tenanted_migrations_directory
|
109
|
+
end
|
110
|
+
return migrations_directory
|
111
|
+
end
|
112
|
+
|
113
|
+
# Default way to setup Roomer. Run rails generate roomer:install to create
|
114
|
+
# a fresh initializer with all configuration values.
|
115
|
+
# @example
|
116
|
+
# Roomer.setup do |config|
|
117
|
+
# config.url_routing_strategy = :domain
|
118
|
+
# end
|
119
|
+
def self.setup
|
120
|
+
yield self
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
require 'roomer/rails'
|
data/roomer.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "roomer/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "roomer"
|
7
|
+
s.version = Roomer::VERSION::STRING
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.homepage = "https://github.com/gosuri/roomer"
|
10
|
+
s.summary = "Roomer is a multitenant framework for Rails using PostgreSQL"
|
11
|
+
s.description = Roomer::VERSION::SUMMARY
|
12
|
+
|
13
|
+
s.authors = ["Greg Osuri","Daniel Ceballos"]
|
14
|
+
s.email = ["gosuri@gmail.com","dceballos@gmail.com"]
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_dependency 'rails','~> 3.0.9'
|
22
|
+
end
|
data/test/config.rb
ADDED
data/test/config.yml
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
require "generators/roomer/install/install_generator"
|
3
|
+
|
4
|
+
class InstallGeneratorTest < Rails::Generators::TestCase
|
5
|
+
tests Roomer::Generators::InstallGenerator
|
6
|
+
destination File.expand_path("../../tmp", __FILE__)
|
7
|
+
setup :prepare_destination
|
8
|
+
|
9
|
+
test "assert initializer file created" do
|
10
|
+
run_generator
|
11
|
+
assert_file "config/initializers/roomer.rb"
|
12
|
+
end
|
13
|
+
end
|