cell 0.1.1 → 0.1.2
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 +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
|