Open im-not-a-robot opened 4 years ago
Did you read the issue #610 completely? this comment https://github.com/influitive/apartment/issues/610#issuecomment-515212827 explain why your code doesn't work as expected.
In summary, when you do Content.all
the all
method returns a Relation object (not a collection of Content), but rails inspect the return value of each line you execute in the console, thus, rails execute this Content.all.inspect
instead of Content.all
So, what rails console is trully executing is the following:
contents = Apartment::Tenant.switch("domain.com") { Content.all }; 1 # whe can use the ;1 to avoid rails from inspecting the Content.all variable.
contents.inspect
It's inspecting the value of the relation outside of the switch.
Yeah im already read that issue completely So the solution is i must using Content.all.load to load from expected schema ? Or may there's another solution for best practice ?
Not really, the solution depends completely on what you want to achieve. But you shouldn't need to use Content.all.load
. Apartment let you write your code the same way you been writing it.
If you want to use "best practises", you should learn how data loading works on rails and understand why Content.all
don't bring the data until you try to access it;
contents = Content.all; 1
contents.loaded? #false: is not loaded
contents = contents.where('id < 100')
contents.loaded? #false: is not loaded
contents[0]
contents.loaded? #true, as you tried to access an item of the relation, rails execute the query and load its contents.
when you open the Rails console and you execute the following piece of code:
Content.all
Rails console is adds the inspect method to the value returned by the statement, thus the following code is executed:
Content.all.inspect
the inspect
method access the relation, forcing it to be loaded. This is only part of the rails console behavior.
Okay thanks for the information. Its very interesting.
But theres some question in my head. Im using graphql, so when im using switch and return in the block, the result still come from public schema.
In Example :
Apartment::Tenant.switch("domain.com") do
contents = Content.all
return contents
end
My question is why the data is still come from public schema even im return the data inside switch block ?
contents = Apartment::Tenant.switch("domain.com") do
contents = Content.all
contents.loaded? # false
return contents
end
contents.loaded? # false
contents[0] <-- in this line, you're in the public schema context, as the collection is not loaded, it's going to load the data from the public schema
In this example you might want to use load inside the switch. But, as I said, it depends.
Steps to reproduce
Expected behavior
Show content from expected schema
Actual behavior
Show content from public schema
System configuration
Database: (Tell us what database and its version you use.) Postgresql 11.2
Apartment version: 2.2.1
Apartment config (in
config/initializers/apartment.rb
or so):#
Apartment Configuration
# Apartment.configure do |config|
Add any models that you do not want to be multi-tenanted, but remain in the global (public) namespace.
A typical example would be a Customer or Tenant model that stores each Tenant's information.
# config.excluded_models = %w{ JwtBlacklist Organization OrganizationsUser Profile Role User }
In order to migrate all of your Tenants you need to provide a list of Tenant names to Apartment.
You can make this dynamic by providing a Proc object to be called on migrations.
This object should yield either:
- an array of strings representing each Tenant name.
- a hash which keys are tenant names, and values custom db config (must contain all key/values required in database.yml)
#
config.tenant_names = lambda{ Customer.pluck(:tenant_name) }
config.tenant_names = ['tenant1', 'tenant2']
config.tenant_names = {
'tenant1' => {
adapter: 'postgresql',
host: 'some_server',
port: 5555,
database: 'postgres' # this is not the name of the tenant's db
but the name of the database to connect to before creating the tenant's db
mandatory in postgresql
},
'tenant2' => {
adapter: 'postgresql',
database: 'postgres' # this is not the name of the tenant's db
but the name of the database to connect to before creating the tenant's db
mandatory in postgresql
}
}
config.tenant_names = lambda do
Tenant.all.each_with_object({}) do |tenant, hash|
hash[tenant.name] = tenant.db_configuration
end
end
# config.tenant_names = lambda { Organization.pluck :domain }
PostgreSQL:
Specifies whether to use PostgreSQL schemas or create a new database per Tenant.
#
MySQL:
Specifies whether to switch databases by using
use
statement or re-establish connection.#
The default behaviour is true.
#
config.use_schemas = true
#
==> PostgreSQL only options
Apartment can be forced to use raw SQL dumps instead of schema.rb for creating new schemas.
Use this when you are using some extra features in PostgreSQL that can't be represented in
schema.rb, like materialized views etc. (only applies with use_schemas set to true).
(Note: this option doesn't use db/structure.sql, it creates SQL dump by executing pg_dump)
#
config.use_sql = false
There are cases where you might want some schemas to always be in your search_path
e.g when using a PostgreSQL extension like hstore.
Any schemas added here will be available along with your selected Tenant.
#
config.persistent_schemas = %w{ hstore }
<== PostgreSQL only options
#
By default, and only when not using PostgreSQL schemas, Apartment will prepend the environment
to the tenant name to ensure there is no conflict between your environments.
This is mainly for the benefit of your development and test environments.
Uncomment the line below if you want to disable this behaviour in production.
#
config.prepend_environment = !Rails.env.production?
When using PostgreSQL schemas, the database dump will be namespaced, and
apartment will substitute the default namespace (usually public) with the
name of the new tenant when creating a new tenant. Some items must maintain
a reference to the default namespace (ie public) - for instance, a default
uuid generation. Uncomment the line below to create a list of namespaced
items in the schema dump that should not have their namespace replaced by
the new tenant
#
config.pg_excluded_names = ["uuid_generate_v4"]
end
Setup a custom Tenant switching middleware. The Proc should return the name of the Tenant that
you want to switch to.
Rails.application.config.middleware.use Apartment::Elevators::Generic, lambda { |request|
request.host.split('.').first
}
Rails.application.config.middleware.use Apartment::Elevators::Domain
Rails.application.config.middleware.use Apartment::Elevators::Subdomain
Rails.application.config.middleware.use Apartment::Elevators::FirstSubdomain
Rails.application.config.middleware.use Apartment::Elevators::Host
2.6.3 :036 > Apartment::Tenant.switch("domain.com") { Content.all } Content Load (0.9ms) SELECT "contents".* FROM "contents" LIMIT $1 [["LIMIT", 11]] => #<ActiveRecord::Relation []>
2.6.3 :001 > Apartment::Tenant.switch!("domain.com") => nil 2.6.3 :002 > Content.all Content Load (1.2ms) SELECT "contents".* FROM "contents" LIMIT $1 [["LIMIT", 11]] => #<ActiveRecord::Relation [#<Content id: 3, path: "test3", name: "test3", description: nil, published: false, active: true, user_id: nil, created_at: "2019-08-30 22:17:24", updated_at: "2019-08-30 22:17:24">, ...]>
2.6.3 :001 > Apartment::Tenant.switch("domain.com") { Content.all.load } Content Load (0.9ms) SELECT "contents".* FROM "contents" => #<ActiveRecord::Relation [#<Content id: 3, path: "test3", name: "test3", description: nil, published: false, active: true, user_id: nil, created_at: "2019-08-30 22:17:24", updated_at: "2019-08-30 22:17:24">, ...]>