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.
@@ -1,21 +1,95 @@
1
1
  #
2
- # Alright, here's how this works:
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
- # "global" is ran with a "db:migrate", which means no Tenant is activated.
7
- # "targeted" is ran once over each tenant, via "cell:db:migrate".
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
- # Each migration is ran twice: once with no explicit schema set, then again
11
- # with "tenant_prototype" activated.
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
- require 'cell/with_schema'
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::Schema.global_schema
117
+ Cell::Meta.global_schema
52
118
  end
53
119
 
54
120
  def prototype_schema
55
- Cell::Schema.prototype_schema
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::Tenant.current
129
+ Cell::Model.current
64
130
  end
65
131
 
66
132
  def targeted?
67
133
  !! target
68
134
  end
69
135
 
70
- def with_context(context, search_path)
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!(connection)
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
- def global
105
- saved = self.global_block
106
- self.global_block = true
107
- yield
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
- ActiveRecord::Migration.prepend(self)
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
- initializer "cell.sanity_check" do
11
+ config.after_initialize do
12
+ require 'cell/tenant'
13
13
  require 'cell/sanity_check'
14
- Cell::SanityCheck.sanity_check!
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
@@ -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 = ["PostgreSQLAdapter"]
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
- with your adapter. If your adapter is a PostgreSQL spin-off, please
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
- using a schema_format other than :sql. You need a definititive
32
- structure.sql instead of a schema.rb. You can configure this by
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
- without explicitly setting `dump_schemas`.
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
- check_active_record_adapter!
62
- check_schema_format!
63
- check_dump_schemas!
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(con, Cell::Schema.prototype_schema,
36
- schema_name)
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
 
@@ -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:purge\b/, /^db:reset\b/, /^db:load_config\b/, /^db:create\b/,
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::Tenant.cell_find(tenant_id).tap do |r|
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::Tenant.respond_to?(:find_each) ? ::Cell::Tenant.find_each
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::Tenant.use(t, &block)
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}" => ['environment', 'db:load_config'] do |t|
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}'"
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Cell
2
- VERSION = '0.1.1'
2
+ VERSION = '0.1.2'
3
3
  end
data/lib/cell.rb CHANGED
@@ -1,7 +1,2 @@
1
1
  require 'cell/version'
2
- require 'cell/configure'
3
-
4
- if defined?(Rails)
5
- require 'cell/migration'
6
- require 'cell/railtie'
7
- end
2
+ require 'cell/railtie' if defined?(Rails)
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.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-10-08 00:00:00.000000000 Z
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: '10.0'
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: '10.0'
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/data_directory.rb
110
+ - lib/cell/meta.rb
111
111
  - lib/cell/migration.rb
112
- - lib/cell/model.rb
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