cell 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/README.md +125 -49
- data/Rakefile +2 -1
- data/cell.gemspec +1 -1
- data/lib/cell/active_job.rb +32 -0
- data/lib/cell/clone_schema.rb +18 -16
- data/lib/cell/console.rb +9 -15
- data/lib/cell/context.rb +48 -27
- data/lib/cell/meta.rb +87 -0
- data/lib/cell/migration.rb +131 -44
- data/lib/cell/model_extensions.rb +85 -0
- data/lib/cell/railtie.rb +17 -3
- data/lib/cell/sanity_check.rb +21 -23
- data/lib/cell/schema.rb +2 -13
- data/lib/cell/tasks/cell.rake +18 -11
- data/lib/cell/tenant.rb +55 -0
- data/lib/cell/url_options.rb +26 -0
- data/lib/cell/version.rb +1 -1
- data/lib/cell.rb +1 -6
- metadata +9 -11
- data/lib/cell/configure.rb +0 -70
- data/lib/cell/data_directory.rb +0 -42
- data/lib/cell/model.rb +0 -37
- data/lib/cell/sidekiq.rb +0 -58
- data/lib/cell/with_schema.rb +0 -30
- data/lib/dump/tenant_routing_manager.rb +0 -25
- data/lib/dump/tenant_url_options.rb +0 -17
data/lib/cell/migration.rb
CHANGED
@@ -1,21 +1,95 @@
|
|
1
1
|
#
|
2
|
-
#
|
2
|
+
# This is the messiest part of Cell, I think by necessity.
|
3
3
|
#
|
4
|
-
# A migration has two execution modes, global and targeted
|
5
|
-
|
6
|
-
#
|
7
|
-
# "
|
4
|
+
# A migration has two execution modes, global and targeted, with correspond to "db:migrate" and
|
5
|
+
# "cell:db:migrate"
|
6
|
+
#
|
7
|
+
# "global" is ran with a "db:migrate", which means no Tenant is activated. DDL commands here are
|
8
|
+
# directed to either 'public' or 'cell_prototype'
|
9
|
+
#
|
10
|
+
# "targeted" is ran with a tenant activated (cell:db:migrate), and all DDL commands are directed to
|
11
|
+
# the current tenant's schema.
|
8
12
|
#
|
9
|
-
# In global mode:
|
10
|
-
#
|
11
|
-
#
|
13
|
+
# In 'rails db:migrate' (global mode), we actually execute each migration twice: once with a
|
14
|
+
# pass_context set to :global, where ONLY commands in a 'global {}' block are executed, and then
|
15
|
+
# with pass_context set to :prototype, where ONLY commands outside of a 'global {}' block are
|
16
|
+
# executed.
|
17
|
+
#
|
18
|
+
# ContextTracker keeps up with this.
|
12
19
|
|
20
|
+
require 'cell/meta'
|
13
21
|
require 'cell/schema'
|
14
22
|
require 'cell/clone_schema'
|
15
|
-
|
23
|
+
|
16
24
|
|
17
25
|
module Cell
|
18
26
|
module Migration
|
27
|
+
|
28
|
+
# OK, ContextTracker keeps up with:
|
29
|
+
# * The context in which this migration is being ran, e.g. the :global first pass, the
|
30
|
+
# :prototype second pass, OR the :targeted per tenant pass.
|
31
|
+
# * If it's in a global block or not, which determines if actions are actually executed
|
32
|
+
# * If force_execution is set, which is for the benefit of playing back CommandRecorder
|
33
|
+
# commands.
|
34
|
+
module ContextTracker
|
35
|
+
mattr_accessor :pass_context
|
36
|
+
mattr_accessor :global_block
|
37
|
+
mattr_accessor :force_execution
|
38
|
+
|
39
|
+
def execute_ddl?
|
40
|
+
force_execution ||
|
41
|
+
(pass_context == :global && global_block) ||
|
42
|
+
(pass_context == :prototype && !global_block) ||
|
43
|
+
(pass_context == :target && !global_block)
|
44
|
+
end
|
45
|
+
|
46
|
+
# When the CommandRecorder's commands are executed, they're rewritten to go through
|
47
|
+
# force_call, because the "effective set of commands" have already been determined by the
|
48
|
+
# CommandRecorder run.
|
49
|
+
def force_call(*args, &block)
|
50
|
+
saved, self.force_execution = self.force_execution, true
|
51
|
+
send(*args, &block)
|
52
|
+
ensure
|
53
|
+
self.force_execution = saved
|
54
|
+
end
|
55
|
+
|
56
|
+
def with_context(context, search_path, exclusive: false)
|
57
|
+
Cell::Meta::with_schema(search_path, exclusive: exclusive) do
|
58
|
+
begin
|
59
|
+
saved, self.pass_context = self.pass_context, context
|
60
|
+
yield
|
61
|
+
ensure
|
62
|
+
self.pass_context = saved
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def global
|
68
|
+
saved, self.global_block = self.global_block, true
|
69
|
+
yield
|
70
|
+
ensure
|
71
|
+
self.global_block = saved
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
# This module intercepts create_table and drop_table, and updates the list of global tables
|
77
|
+
# in ::ActiveRecord::InternalMetadata['cell.global'].
|
78
|
+
module MetadataIntercept
|
79
|
+
def create_table(name, *args, &block)
|
80
|
+
super.tap do
|
81
|
+
::Cell::Meta.add_global_table(name) if pass_context == :global
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def drop_table(name, *args, &block)
|
86
|
+
super.tap do
|
87
|
+
::Cell::Meta.remove_global_table(name) if pass_context == :global
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# We intercept every method given, and only execute it if appropriate according to execute_ddl?
|
19
93
|
def self.intercept(methods)
|
20
94
|
methods.each do |method|
|
21
95
|
define_method(method) do |*args, &block|
|
@@ -24,7 +98,8 @@ module Cell
|
|
24
98
|
end
|
25
99
|
end
|
26
100
|
|
27
|
-
# This sucks.
|
101
|
+
# This sucks. In the future, we may want to pull these from
|
102
|
+
# SchemaStatements
|
28
103
|
intercept %i(add_belongs_to add_column add_foreign_key add_index
|
29
104
|
add_index_sort_order add_reference add_timestamps
|
30
105
|
change_column change_column_default change_column_null
|
@@ -38,21 +113,12 @@ module Cell
|
|
38
113
|
%i(enable_extension disable_extension truncate) +
|
39
114
|
%i(execute)
|
40
115
|
|
41
|
-
attr_accessor :pass_context
|
42
|
-
attr_accessor :global_block
|
43
|
-
|
44
|
-
def execute_ddl?
|
45
|
-
(pass_context == :global && global_block) ||
|
46
|
-
(pass_context == :prototype && !global_block) ||
|
47
|
-
(pass_context == :target && !global_block)
|
48
|
-
end
|
49
|
-
|
50
116
|
def global_schema
|
51
|
-
Cell::
|
117
|
+
Cell::Meta.global_schema
|
52
118
|
end
|
53
119
|
|
54
120
|
def prototype_schema
|
55
|
-
Cell::
|
121
|
+
Cell::Meta.prototype_schema
|
56
122
|
end
|
57
123
|
|
58
124
|
def tenant_schema
|
@@ -60,28 +126,16 @@ module Cell
|
|
60
126
|
end
|
61
127
|
|
62
128
|
def target
|
63
|
-
Cell::
|
129
|
+
Cell::Model.current
|
64
130
|
end
|
65
131
|
|
66
132
|
def targeted?
|
67
133
|
!! target
|
68
134
|
end
|
69
135
|
|
70
|
-
|
71
|
-
Cell::WithSchema::with_schema(search_path, connection: connection) do
|
72
|
-
begin
|
73
|
-
saved_context = self.pass_context
|
74
|
-
self.pass_context = context
|
75
|
-
yield
|
76
|
-
ensure
|
77
|
-
self.pass_context = saved_context
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
|
136
|
+
# This is our super-special initialization function.
|
83
137
|
def initialize_cell!
|
84
|
-
CloneSchema.install_function!
|
138
|
+
CloneSchema.install_function!
|
85
139
|
execute "CREATE SCHEMA #{connection.quote_schema_name(prototype_schema)}"
|
86
140
|
end
|
87
141
|
|
@@ -95,20 +149,53 @@ module Cell
|
|
95
149
|
super
|
96
150
|
end
|
97
151
|
else
|
98
|
-
with_context(:target, tenant_schema) do
|
152
|
+
with_context(:target, tenant_schema, exclusive: true) do
|
99
153
|
super
|
100
154
|
end
|
101
155
|
end
|
102
156
|
end
|
103
157
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
ensure
|
109
|
-
self.global_block = saved
|
158
|
+
ActiveSupport.on_load(:active_record) do
|
159
|
+
ActiveRecord::Migration.prepend(::Cell::Migration::MetadataIntercept)
|
160
|
+
ActiveRecord::Migration.prepend(::Cell::Migration::ContextTracker)
|
161
|
+
ActiveRecord::Migration.prepend(::Cell::Migration)
|
110
162
|
end
|
111
163
|
|
112
|
-
|
164
|
+
|
165
|
+
# Patches to CommandRecorder, which let us roll back.
|
166
|
+
module CommandRecorderFilter
|
167
|
+
# This maybe should've been attr_reader in CommandRecorder
|
168
|
+
def commands=(*)
|
169
|
+
# If this is actually used, we're fucked.
|
170
|
+
fail "The problem with monkey patching is..."
|
171
|
+
end
|
172
|
+
|
173
|
+
def commands
|
174
|
+
@commands.select do |command|
|
175
|
+
command[0]
|
176
|
+
end.map do |command|
|
177
|
+
[:force_call, [command[1], *command[2]], command[3]]
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# saves the state of a recorded command with the context it was in.
|
182
|
+
def add_command(command)
|
183
|
+
@commands << [execute_ddl?, *command]
|
184
|
+
end
|
185
|
+
|
186
|
+
# We override #record to proxy through add_command
|
187
|
+
def record(*command, &block)
|
188
|
+
if @reverting
|
189
|
+
add_command inverse_of(*command, &block)
|
190
|
+
else
|
191
|
+
add_command (command << block)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
ActiveSupport.on_load(:active_record) do
|
196
|
+
::ActiveRecord::Migration::CommandRecorder.prepend(::Cell::Migration::ContextTracker)
|
197
|
+
::ActiveRecord::Migration::CommandRecorder.prepend(::Cell::Migration::CommandRecorderFilter)
|
198
|
+
end
|
199
|
+
end
|
113
200
|
end
|
114
201
|
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# These are added to ActiveRecord::Base, regardless if they're the tenant model or not.
|
2
|
+
|
3
|
+
# There are two big tricks here:
|
4
|
+
# 1. Assigning a "tenant" attribute to every model that is loaded from the db, or created while a
|
5
|
+
# tenant is active.
|
6
|
+
# 2. Making sure the object is validated and saved with the proper tenant activated, even if the
|
7
|
+
# tenant has changed since the object was created
|
8
|
+
#
|
9
|
+
# So you can load an object from "tenant_a", switch to "tenant_b", and if you save or validate the
|
10
|
+
# object, it'll do so while switched to "tenant_a", and then restore.
|
11
|
+
|
12
|
+
require 'cell/meta'
|
13
|
+
|
14
|
+
module Cell
|
15
|
+
module ModelExtensions
|
16
|
+
|
17
|
+
# Say we have a model 'u', that is from an old Tenant, loading an association like 'u.books'
|
18
|
+
# should swap into u.tenant while fetching.
|
19
|
+
module AssociationExtensions
|
20
|
+
def load_target
|
21
|
+
owner._activate_tenant do
|
22
|
+
super
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
module ClassMethods
|
29
|
+
def global_model?
|
30
|
+
::Cell::Meta.global_model?(self)
|
31
|
+
end
|
32
|
+
|
33
|
+
# When a model's schema is lazy-loaded, we want to make sure our view of the DB allows us to
|
34
|
+
# get introspection data.
|
35
|
+
def load_schema!
|
36
|
+
::Cell::Meta.with_structural_schema do
|
37
|
+
return super
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
attr_reader :tenant
|
43
|
+
|
44
|
+
def _assign_tenant
|
45
|
+
unless self.class.global_model?
|
46
|
+
@tenant = Cell::Model.current
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def _activate_tenant(&block)
|
51
|
+
if tenant && tenant != ::Cell::Model.current
|
52
|
+
tenant.use(&block)
|
53
|
+
else
|
54
|
+
yield
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# undocumented, but makes sure validations are ran in the context of the object's tenant, e.g.,
|
59
|
+
# uniqueness.
|
60
|
+
def run_validations!
|
61
|
+
_activate_tenant do
|
62
|
+
super
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.prepended(cls)
|
67
|
+
class << cls
|
68
|
+
prepend ClassMethods
|
69
|
+
end
|
70
|
+
|
71
|
+
cls.after_initialize :_assign_tenant
|
72
|
+
cls.around_save :_activate_tenant
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.install!
|
76
|
+
ActiveSupport.on_load(:active_record) do
|
77
|
+
::ActiveRecord::Base.prepend(::Cell::ModelExtensions)
|
78
|
+
::ActiveRecord::Associations::Association.prepend(::Cell::ModelExtensions::AssociationExtensions)
|
79
|
+
::ActiveRecord::Associations::CollectionAssociation.prepend(::Cell::ModelExtensions::AssociationExtensions)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
Cell::ModelExtensions.install!
|
data/lib/cell/railtie.rb
CHANGED
@@ -6,12 +6,26 @@ module Cell
|
|
6
6
|
|
7
7
|
console do
|
8
8
|
require 'cell/console'
|
9
|
-
Cell::Console.configure!
|
10
9
|
end
|
11
10
|
|
12
|
-
|
11
|
+
config.after_initialize do
|
12
|
+
require 'cell/tenant'
|
13
13
|
require 'cell/sanity_check'
|
14
|
-
|
14
|
+
require 'cell/model_extensions'
|
15
|
+
require 'cell/migration'
|
16
|
+
require 'cell/active_job'
|
15
17
|
end
|
16
18
|
end
|
19
|
+
|
20
|
+
|
21
|
+
def self.const_missing(name)
|
22
|
+
return super unless name == :Model
|
23
|
+
|
24
|
+
Rails.application.eager_load!
|
25
|
+
unless const_defined?(:Model)
|
26
|
+
fail "Eager loaded models to find model, didn't pan out." +
|
27
|
+
"Make sure one of your models has `prepend Cell::Tenant`"
|
28
|
+
end
|
29
|
+
::Cell::Model
|
30
|
+
end
|
17
31
|
end
|
data/lib/cell/sanity_check.rb
CHANGED
@@ -2,19 +2,16 @@ require 'active_record/connection_adapters/postgresql_adapter'
|
|
2
2
|
|
3
3
|
module Cell
|
4
4
|
module SanityCheck
|
5
|
-
|
6
|
-
module_function
|
7
|
-
def check_active_record_adapter!
|
5
|
+
def self.check_active_record_adapter!
|
8
6
|
pg_base_adapter = ::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
|
9
|
-
whitelist = [
|
7
|
+
whitelist = []
|
10
8
|
adapter_name = ActiveRecord::Base.connection.adapter_name
|
11
9
|
|
12
10
|
unless ActiveRecord::Base.connection.is_a?(pg_base_adapter) ||
|
13
11
|
whitelist.include?(adapter_name)
|
14
12
|
msg <<~EOD
|
15
|
-
Cell uses PostgreSQL-specific features that cannot be represented
|
16
|
-
|
17
|
-
open a pull request.
|
13
|
+
Cell uses PostgreSQL-specific features that cannot be represented with your adapter. If
|
14
|
+
your adapter is a PostgreSQL spin-off, please open a pull request.
|
18
15
|
|
19
16
|
Whitelist: #{whitelist.inspect}
|
20
17
|
ActiveRecord adapter: #{adapter_name}
|
@@ -24,13 +21,12 @@ module Cell
|
|
24
21
|
end
|
25
22
|
|
26
23
|
|
27
|
-
def check_schema_format!
|
24
|
+
def self.check_schema_format!
|
28
25
|
if Rails.application.config.active_record.schema_format != :sql
|
29
26
|
msg = <<~EOD
|
30
|
-
Cell uses PostgreSQL-specific features that cannot be represented
|
31
|
-
|
32
|
-
|
33
|
-
adding the following line to your application.rb:
|
27
|
+
Cell uses PostgreSQL-specific features that cannot be represented using a schema_format
|
28
|
+
other than :sql. You need a definititive structure.sql instead of a schema.rb. You can
|
29
|
+
configure this by adding the following line to your application.rb:
|
34
30
|
|
35
31
|
Rails.application.config.active_record.schema_format = :sql
|
36
32
|
EOD
|
@@ -38,18 +34,16 @@ module Cell
|
|
38
34
|
end
|
39
35
|
end
|
40
36
|
|
41
|
-
def check_dump_schemas!
|
37
|
+
def self.check_dump_schemas!
|
42
38
|
dump_schemas = Rails.application.config.active_record.dump_schemas
|
43
39
|
unless dump_schemas.to_s.match(/\bcell_prototype\b/)
|
44
40
|
msg = <<~EOD
|
45
|
-
Cell stores tenant templates in a PostgreSQL schema called
|
46
|
-
"cell_prototype".
|
41
|
+
Cell stores tenant templates in a PostgreSQL schema called "cell_prototype".
|
47
42
|
|
48
|
-
Rails will not dump this schema by default with `db:structure:dump`
|
49
|
-
|
43
|
+
Rails will not dump this schema by default with `db:structure:dump` without explicitly
|
44
|
+
setting `dump_schemas`.
|
50
45
|
|
51
|
-
You can configure this properly by adding a line like the following
|
52
|
-
in application.rb:
|
46
|
+
You can configure this properly by adding a line like the following in application.rb:
|
53
47
|
|
54
48
|
Rails.application.config.active_record.dump_schemas = "public,cell_prototype"
|
55
49
|
EOD
|
@@ -57,10 +51,14 @@ module Cell
|
|
57
51
|
end
|
58
52
|
end
|
59
53
|
|
60
|
-
def sanity_check!
|
61
|
-
|
62
|
-
|
63
|
-
|
54
|
+
def self.sanity_check!
|
55
|
+
ActiveSupport.on_load(:active_record) do
|
56
|
+
Cell::SanityCheck.check_active_record_adapter!
|
57
|
+
Cell::SanityCheck.check_schema_format!
|
58
|
+
Cell::SanityCheck.check_dump_schemas!
|
59
|
+
end
|
64
60
|
end
|
65
61
|
end
|
66
62
|
end
|
63
|
+
|
64
|
+
::Cell::SanityCheck.sanity_check!
|
data/lib/cell/schema.rb
CHANGED
@@ -8,16 +8,6 @@ module Cell
|
|
8
8
|
MAX_SCHEMA_NAME_LENGTH = 63 # PostgreSQL baked-in default
|
9
9
|
MAX_CELL_ID_SIZE = MAX_SCHEMA_NAME_LENGTH - SCHEMA_PREFIX.size
|
10
10
|
|
11
|
-
module_function \
|
12
|
-
def global_schema
|
13
|
-
@global_schema ||= ActiveRecord::Base.connection.schema_search_path
|
14
|
-
end
|
15
|
-
|
16
|
-
module_function \
|
17
|
-
def prototype_schema
|
18
|
-
'cell_prototype'
|
19
|
-
end
|
20
|
-
|
21
11
|
module ClassMethods
|
22
12
|
def schema_name_for_cell_id(cell_id)
|
23
13
|
SCHEMA_PREFIX + cell_id.to_s.gsub(/[^a-z0-9_]/i, '-')
|
@@ -32,9 +22,8 @@ module Cell
|
|
32
22
|
def create_schema!
|
33
23
|
con = self.class.connection
|
34
24
|
con.transaction do
|
35
|
-
Cell::CloneSchema.clone_schema(
|
36
|
-
|
37
|
-
Cell::CloneSchema.copy_schema_migrations_to(schema_name, connection: con)
|
25
|
+
Cell::CloneSchema.clone_schema(Cell::Meta.prototype_schema, schema_name)
|
26
|
+
Cell::CloneSchema.copy_schema_migrations_to(schema_name)
|
38
27
|
end
|
39
28
|
end
|
40
29
|
|
data/lib/cell/tasks/cell.rake
CHANGED
@@ -9,9 +9,8 @@ def wrappable_task?(task)
|
|
9
9
|
fail ArgumentError unless task.name.start_with?('db:')
|
10
10
|
|
11
11
|
case task.name
|
12
|
-
when /^db:test\b/, /^db:structure\b/, /^db:schema\b/, /^db:setup\b/,
|
13
|
-
/^db:
|
14
|
-
/^db:drop\b/
|
12
|
+
when /^db:test\b/, /^db:structure\b/, /^db:schema\b/, /^db:setup\b/, /^db:purge\b/,
|
13
|
+
/^db:reset\b/, /^db:load_config\b/, /^db:create\b/, /^db:drop\b/
|
15
14
|
false
|
16
15
|
else
|
17
16
|
true
|
@@ -20,32 +19,40 @@ end
|
|
20
19
|
|
21
20
|
|
22
21
|
def using_each_tenant(&block)
|
23
|
-
# We have to get the model that included Cell::Tenant
|
24
|
-
require Rails.root.join('config/environment')
|
25
|
-
|
26
22
|
tenants = if ENV['T']
|
27
23
|
ENV['T'].split(/\s*,\s*/).map do |tenant_id|
|
28
|
-
Cell::
|
24
|
+
::Cell::Model.cell_find(tenant_id).tap do |r|
|
29
25
|
fail "Unable to load tenant: #{tenant_id}" if r.nil?
|
30
26
|
end
|
31
27
|
end
|
32
28
|
else
|
33
|
-
::Cell::
|
34
|
-
: ::Cell::Tenant.each
|
29
|
+
::Cell::Model.respond_to?(:find_each) ? ::Cell::Model.find_each : ::Cell::Model.each
|
35
30
|
end
|
36
31
|
|
37
32
|
tenants.each do |t|
|
38
|
-
Cell::
|
33
|
+
Cell::Model.use(t, &block)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
namespace :cell do
|
38
|
+
task :eager => 'environment' do
|
39
|
+
Rails.application.eager_load!
|
39
40
|
end
|
40
41
|
end
|
41
42
|
|
42
43
|
|
43
44
|
db_tasks.each do |db_task|
|
44
45
|
if wrappable_task?(db_task)
|
46
|
+
# We need to have the model the user has chosen to be Cell::Model to have
|
47
|
+
# been loaded. The easy way is to punt and eager_load all rake tasks, but
|
48
|
+
# we'll try to selectively do it for certain tasks by adding the
|
49
|
+
# dependency.
|
50
|
+
task db_task => 'cell:eager'
|
51
|
+
|
45
52
|
if db_task.comment
|
46
53
|
desc "run `#{db_task.name}' for all tenants, or each in $T=t1,t2,..."
|
47
54
|
end
|
48
|
-
task "cell:#{db_task.name}" =>
|
55
|
+
task "cell:#{db_task.name}" => 'cell:eager' do |t|
|
49
56
|
Rails.application.eager_load!
|
50
57
|
using_each_tenant do |tenant|
|
51
58
|
puts "Running #{db_task.name} for tenant '#{tenant.cell_id}'"
|
data/lib/cell/tenant.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'cell/context'
|
2
|
+
require 'cell/schema'
|
3
|
+
require 'cell/url_options'
|
4
|
+
|
5
|
+
module Cell
|
6
|
+
module Tenant
|
7
|
+
class << self
|
8
|
+
attr_accessor :cls
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def cell_id_column
|
13
|
+
:id
|
14
|
+
end
|
15
|
+
|
16
|
+
def cell_find(cell_id, finder_method: :find_by)
|
17
|
+
send(finder_method, cell_id_column => cell_id)
|
18
|
+
end
|
19
|
+
|
20
|
+
def cell_find!(cell_id)
|
21
|
+
cell_find(cell_id, finder_method: :find_by!)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def cell_id
|
26
|
+
send(self.class.cell_id_column)
|
27
|
+
end
|
28
|
+
|
29
|
+
def cell_id_changed?
|
30
|
+
!! previous_changes[self.class.cell_id_column]
|
31
|
+
end
|
32
|
+
|
33
|
+
def cell_id_change_set
|
34
|
+
fail "cell_id was not changed" unless cell_id_changed?
|
35
|
+
previous_changes[self.class.cell_id_column]
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.included(model)
|
39
|
+
fail "Use `prepend` instead of `include`."
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.prepended(model)
|
43
|
+
model.extend(ClassMethods)
|
44
|
+
|
45
|
+
if ::Cell.const_defined?(:Model)
|
46
|
+
::Cell.send(:remove_const, :Model)
|
47
|
+
end
|
48
|
+
::Cell.const_set(:Model, model)
|
49
|
+
|
50
|
+
model.prepend(::Cell::Schema)
|
51
|
+
model.prepend(::Cell::Context)
|
52
|
+
model.prepend(::Cell::UrlOptions)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Cell
|
2
|
+
module UrlOptions
|
3
|
+
def self.prepended(cls)
|
4
|
+
cls.after_set :change_url_options, if: :has_cell_host?
|
5
|
+
cls.around_use :change_url_options, if: :has_cell_host?
|
6
|
+
end
|
7
|
+
|
8
|
+
def has_cell_host?
|
9
|
+
respond_to?(:cell_host)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
def change_url_options(&block)
|
14
|
+
saved = Rails.application.routes.default_url_options[:host]
|
15
|
+
Rails.application.routes.default_url_options[:host] = cell_host
|
16
|
+
|
17
|
+
if block_given?
|
18
|
+
begin
|
19
|
+
yield
|
20
|
+
ensure
|
21
|
+
Rails.application.routes.default_url_options[:host] = saved
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/cell/version.rb
CHANGED
data/lib/cell.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cell
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Owens
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-11-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -78,14 +78,14 @@ dependencies:
|
|
78
78
|
requirements:
|
79
79
|
- - "~>"
|
80
80
|
- !ruby/object:Gem::Version
|
81
|
-
version: '
|
81
|
+
version: '11.0'
|
82
82
|
type: :development
|
83
83
|
prerelease: false
|
84
84
|
version_requirements: !ruby/object:Gem::Requirement
|
85
85
|
requirements:
|
86
86
|
- - "~>"
|
87
87
|
- !ruby/object:Gem::Version
|
88
|
-
version: '
|
88
|
+
version: '11.0'
|
89
89
|
description:
|
90
90
|
email:
|
91
91
|
- mike@meter.md
|
@@ -103,22 +103,20 @@ files:
|
|
103
103
|
- bin/test
|
104
104
|
- cell.gemspec
|
105
105
|
- lib/cell.rb
|
106
|
+
- lib/cell/active_job.rb
|
106
107
|
- lib/cell/clone_schema.rb
|
107
|
-
- lib/cell/configure.rb
|
108
108
|
- lib/cell/console.rb
|
109
109
|
- lib/cell/context.rb
|
110
|
-
- lib/cell/
|
110
|
+
- lib/cell/meta.rb
|
111
111
|
- lib/cell/migration.rb
|
112
|
-
- lib/cell/
|
112
|
+
- lib/cell/model_extensions.rb
|
113
113
|
- lib/cell/railtie.rb
|
114
114
|
- lib/cell/sanity_check.rb
|
115
115
|
- lib/cell/schema.rb
|
116
|
-
- lib/cell/sidekiq.rb
|
117
116
|
- lib/cell/tasks/cell.rake
|
117
|
+
- lib/cell/tenant.rb
|
118
|
+
- lib/cell/url_options.rb
|
118
119
|
- lib/cell/version.rb
|
119
|
-
- lib/cell/with_schema.rb
|
120
|
-
- lib/dump/tenant_routing_manager.rb
|
121
|
-
- lib/dump/tenant_url_options.rb
|
122
120
|
homepage: https://github.com/metermd/cell
|
123
121
|
licenses:
|
124
122
|
- MIT
|