apartment 0.20.0 → 0.21.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -3,6 +3,9 @@
3
3
  Gemfile.lock
4
4
  pkg/*
5
5
  *.log
6
+ .idea
6
7
  *.sw[pno]
7
8
  spec/config/database.yml
8
9
  spec/dummy/config/database.yml
10
+ cookbooks
11
+ tmp
data/.rspec CHANGED
@@ -1,2 +1,3 @@
1
1
  --colour
2
2
  --format documentation
3
+ --tty
data/.travis.yml CHANGED
@@ -2,4 +2,5 @@ language: ruby
2
2
  rvm:
3
3
  - 1.9.2
4
4
  - 1.9.3
5
- bundler_args: --verbose
5
+ - jruby-19mode
6
+ bundler_args: --without local --verbose
data/.vagrant ADDED
@@ -0,0 +1 @@
1
+ {"active":{"default":"c780c5fa-2739-4d9a-be05-2f4f6c1dcea6"}}
data/Cheffile ADDED
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env ruby
2
+ #^syntax detection
3
+
4
+ site 'http://community.opscode.com/api/v1'
5
+
6
+ # cookbook 'chef-client'
7
+
8
+ # cookbook 'apache2', '>= 1.0.0'
9
+
10
+ cookbook 'apt'
11
+ cookbook 'build-essential'
12
+ cookbook 'openssl'
13
+
14
+ cookbook 'postgresql'
15
+ cookbook 'mysql'
16
+
17
+ cookbook 'git'
18
+
19
+ cookbook 'rvm',
20
+ :git => 'https://github.com/fnichol/chef-rvm'
21
+
22
+ # cookbook 'postgresql',
23
+ # :git => 'https://github.com/findsyou/cookbooks',
24
+ # :ref => 'postgresql-improvements'
data/Cheffile.lock ADDED
@@ -0,0 +1,43 @@
1
+ SITE
2
+ remote: http://community.opscode.com/api/v1
3
+ specs:
4
+ apt (1.9.0)
5
+ build-essential (1.3.4)
6
+ chef_gem (0.0.5)
7
+ chef_handler (1.1.4)
8
+ dmg (1.1.0)
9
+ git (2.3.0)
10
+ build-essential (>= 0.0.0)
11
+ dmg (>= 0.0.0)
12
+ runit (~> 1.0)
13
+ windows (>= 0.0.0)
14
+ yum (>= 0.0.0)
15
+ mysql (2.1.2)
16
+ build-essential (>= 0.0.0)
17
+ openssl (>= 0.0.0)
18
+ openssl (1.0.2)
19
+ postgresql (2.2.2)
20
+ openssl (>= 0.0.0)
21
+ runit (1.1.0)
22
+ build-essential (>= 0.0.0)
23
+ windows (1.8.4)
24
+ chef_handler (>= 0.0.0)
25
+ yum (2.1.0)
26
+
27
+ GIT
28
+ remote: https://github.com/fnichol/chef-rvm
29
+ ref: master
30
+ sha: 37ae6f0d6540ebc95d22004d68dd45fe304d7255
31
+ specs:
32
+ rvm (0.9.1)
33
+ chef_gem (>= 0.0.0)
34
+
35
+ DEPENDENCIES
36
+ apt (>= 0)
37
+ build-essential (>= 0)
38
+ git (>= 0)
39
+ mysql (>= 0)
40
+ openssl (>= 0)
41
+ postgresql (>= 0)
42
+ rvm (>= 0)
43
+
data/Gemfile CHANGED
@@ -2,14 +2,28 @@ source 'http://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
5
- gem 'pry'
6
- gem 'rails', '>= 3.1.2'
7
- gem 'rake', '~> 0.9.2'
8
- gem 'sqlite3'
9
- gem 'rspec', '~> 2.11'
10
- gem 'rspec-rails', '~> 2.11'
11
- gem 'capybara', '~> 1.0'
12
- gem 'pg', '>= 0.11.0'
13
- gem 'mysql2', '~> 0.3.10'
14
- gem 'delayed_job', '~> 3.0'
5
+ gem 'rails', '>= 3.1.2'
6
+ gem 'rake', '~> 0.9'
7
+ gem 'rspec', '~> 2.11'
8
+ gem 'rspec-rails', '~> 2.11'
9
+ gem 'capybara', '~> 1.0.0'
10
+ gem 'delayed_job', '~> 3.0'
15
11
  gem 'delayed_job_active_record'
12
+
13
+ platform :ruby do
14
+ gem 'mysql2', '~> 0.3.10'
15
+ gem 'pg', '>= 0.11.0'
16
+ end
17
+
18
+ platform :jruby do
19
+ gem 'activerecord-jdbc-adapter'
20
+ gem 'activerecord-jdbcpostgresql-adapter'
21
+ gem 'activerecord-jdbcmysql-adapter'
22
+ gem 'jdbc-postgres', '9.2.1002'
23
+ gem 'jdbc-mysql'
24
+ gem 'jruby-openssl'
25
+ end
26
+
27
+ group :local do
28
+ gem 'pry'
29
+ end
data/HISTORY.md CHANGED
@@ -1,3 +1,8 @@
1
+ # 0.21.0
2
+ * April 24, 2013
3
+
4
+ - JDBC support!! [PetrolMan]
5
+
1
6
  # 0.20.0
2
7
  * Feb 6, 2013
3
8
 
data/README.md CHANGED
@@ -15,7 +15,9 @@ but still allow some data to exist in a common database, Apartment can help.
15
15
 
16
16
  Add the following to your Gemfile:
17
17
 
18
- gem 'apartment'
18
+ ```ruby
19
+ gem 'apartment'
20
+ ```
19
21
 
20
22
  That's all you need to set up the Apartment libraries. If you want to switch databases
21
23
  on a per-user basis, look under "Usage - Switching databases per request", below.
@@ -31,7 +33,9 @@ on a per-user basis, look under "Usage - Switching databases per request", below
31
33
  Before you can switch to a new apartment database, you will need to create it. Whenever
32
34
  you need to create a new database, you can run the following command:
33
35
 
34
- Apartment::Database.create('database_name')
36
+ ```ruby
37
+ Apartment::Database.create('database_name')
38
+ ```
35
39
 
36
40
  If you're using the [prepend environment](https://github.com/influitive/apartment#handling-environments) config option or you AREN'T using Postgresql Schemas, this will create a database in the following format: "#{environment}\_database_name".
37
41
  In the case of a sqlite database, this will be created in your 'db/' folder. With
@@ -53,7 +57,9 @@ One can optionally use the full database creation instead if they want, though t
53
57
 
54
58
  To switch databases using Apartment, use the following command:
55
59
 
56
- Apartment::Database.switch('database_name')
60
+ ```ruby
61
+ Apartment::Database.switch('database_name')
62
+ ```
57
63
 
58
64
  When switch is called, all requests coming to ActiveRecord will be routed to the database
59
65
  you specify (with the exception of excluded models, see below). To return to the 'root'
@@ -67,36 +73,39 @@ Apartment can support many different "Elevators" that can take care of this rout
67
73
  **Switch on subdomain**
68
74
  In house, we use the subdomain elevator, which analyzes the subdomain of the request and switches to a database schema of the same name. It can be used like so:
69
75
 
70
- # application.rb
71
- module My Application
72
- class Application < Rails::Application
73
-
74
- config.middleware.use 'Apartment::Elevators::Subdomain'
75
- end
76
- end
76
+ ```ruby
77
+ # application.rb
78
+ module My Application
79
+ class Application < Rails::Application
80
+ config.middleware.use 'Apartment::Elevators::Subdomain'
81
+ end
82
+ end
83
+ ```
77
84
 
78
85
  **Switch on domain**
79
86
  To switch based on full domain (excluding subdomains *ie 'www'* and top level domains *ie '.com'* ) use the following:
80
87
 
81
- # application.rb
82
- module My Application
83
- class Application < Rails::Application
84
-
85
- config.middleware.use 'Apartment::Elevators::Domain'
86
- end
87
- end
88
+ ```ruby
89
+ # application.rb
90
+ module My Application
91
+ class Application < Rails::Application
92
+ config.middleware.use 'Apartment::Elevators::Domain'
93
+ end
94
+ end
95
+ ```
88
96
 
89
97
  **Custom Elevator**
90
98
  A Generic Elevator exists that allows you to pass a `Proc` (or anything that responds to `call`) to the middleware. This Object will be passed in an `ActionDispatch::Request` object when called for you to do your magic. Apartment will use the return value of this proc to switch to the appropriate database. Use like so:
91
99
 
92
- # application.rb
93
- module My Application
94
- class Application < Rails::Application
95
- # Obviously not a contrived example
96
- config.middleware.use 'Apartment::Elevators::Generic', Proc.new { |request| request.host.reverse }
97
- end
98
- end
99
-
100
+ ```ruby
101
+ # application.rb
102
+ module MyApplication
103
+ class Application < Rails::Application
104
+ # Obviously not a contrived example
105
+ config.middleware.use 'Apartment::Elevators::Generic', Proc.new { |request| request.host.reverse }
106
+ end
107
+ end
108
+ ```
100
109
 
101
110
  ## Config
102
111
 
@@ -106,41 +115,53 @@ The following config options should be set up in a Rails initializer such as:
106
115
 
107
116
  To set config options, add this to your initializer:
108
117
 
109
- Apartment.configure do |config|
110
- # set your options (described below) here
111
- end
118
+ ```ruby
119
+ Apartment.configure do |config|
120
+ # set your options (described below) here
121
+ end
122
+ ```
112
123
 
113
124
  ### Excluding models
114
125
 
115
126
  If you have some models that should always access the 'root' database, you can specify this by configuring Apartment using `Apartment.configure`. This will yield a config object for you. You can set excluded models like so:
116
127
 
117
- config.excluded_models = ["User", "Company"] # these models will not be multi-tenanted, but remain in the global (public) namespace
128
+ ```ruby
129
+ config.excluded_models = ["User", "Company"] # these models will not be multi-tenanted, but remain in the global (public) namespace
130
+ ```
118
131
 
119
132
  Note that a string representation of the model name is now the standard so that models are properly constantized when reloaded in development
120
133
 
134
+ Rails will always access the 'root' database when accessing these models, but note that tables will be created in all schemas. This may not be ideal, but its done this way because otherwise rails wouldn't be able to properly generate the schema.rb file.
135
+
121
136
  ### Postgresql Schemas
122
137
 
123
138
  **Providing a Different default_schema**
124
139
  By default, ActiveRecord will use `"$user", public` as the default `schema_search_path`. This can be modified if you wish to use a different default schema be setting:
125
140
 
126
- config.default_schema = "some_other_schema"
141
+ ```ruby
142
+ config.default_schema = "some_other_schema"
143
+ ```
127
144
 
128
145
  With that set, all excluded models will use this schema as the table name prefix instead of `public` and `reset` on `Apartment::Database` will return to this schema also
129
146
 
130
147
  **Persistent Schemas**
131
148
  Apartment will normally just switch the `schema_search_path` whole hog to the one passed in. This can lead to problems if you want other schemas to always be searched as well. Enter `persistent_schemas`. You can configure a list of other schemas that will always remain in the search path, while the default gets swapped out:
132
149
 
133
- config.persistent_schemas = ['some', 'other', 'schemas']
150
+ ```ruby
151
+ config.persistent_schemas = ['some', 'other', 'schemas']
152
+ ```
134
153
 
135
154
  This has numerous useful applications. [Hstore](http://www.postgresql.org/docs/9.1/static/hstore.html), for instance, is a popular storage engine for Postgresql. In order to use Hstore, you have to install it to a specific schema and have that always in the `schema_search_path`. This could be achieved like so:
136
155
 
137
- # NOTE do not do this in a migration, must be done
138
- # manually before you configure apartment with hstore
139
- # In a rake task, or on the console...
140
- ActiveRecord::Base.connection.execute("CREATE SCHEMA hstore; CREATE EXTENSION HSTORE SCHEMA hstore")
156
+ ```ruby
157
+ # NOTE do not do this in a migration, must be done
158
+ # manually before you configure apartment with hstore
159
+ # In a rake task, or on the console...
160
+ ActiveRecord::Base.connection.execute("CREATE SCHEMA hstore; CREATE EXTENSION HSTORE SCHEMA hstore")
141
161
 
142
- # configure Apartment to maintain the `hstore` schema in the `schema_search_path`
143
- config.persistent_schemas = ['hstore']
162
+ # configure Apartment to maintain the `hstore` schema in the `schema_search_path`
163
+ config.persistent_schemas = ['hstore']
164
+ ```
144
165
 
145
166
  There are a few caveats to be aware of when using `hstore`. First off, the hstore schema and extension creation need to be done manually *before* you reference it in any way in your migrations, database.yml or apartment. This is an unfortunate manual step, but I haven't found a way around it. You can achieve this from the command line using something like:
146
167
 
@@ -148,11 +169,13 @@ There are a few caveats to be aware of when using `hstore`. First off, the hsto
148
169
 
149
170
  Next, your `database.yml` file must mimic what you've set for your default and persistent schemas in Apartment. When you run migrataions with Rails, it won't know about the hstore schema because Apartment isn't injected into the default connection, it's done on a per-request basis, therefore Rails doesn't know about `hstore` during migrations. To do so, add the following to your `database.yml` for all environments
150
171
 
151
- # database.yml
152
- ...
153
- adapter: postgresql
154
- schema_search_path: "public,hstore"
155
- ...
172
+ ```yaml
173
+ # database.yml
174
+ ...
175
+ adapter: postgresql
176
+ schema_search_path: "public,hstore"
177
+ ...
178
+ ```
156
179
 
157
180
  This would be for a config with `default_schema` set to `public` and `persistent_schemas` set to `['hstore']`
158
181
 
@@ -163,13 +186,15 @@ In order to migrate all of your databases (or posgresql schemas) you need to pro
163
186
  of dbs to Apartment. You can make this dynamic by providing a Proc object to be called on migrations.
164
187
  This object should yield an array of string representing each database name. Example:
165
188
 
166
- # Dynamically get database names to migrate
167
- config.database_names = lambda{ Customer.pluck(:database_name) }
189
+ ```ruby
190
+ # Dynamically get database names to migrate
191
+ config.database_names = lambda{ Customer.pluck(:database_name) }
168
192
 
169
- # Use a static list of database names for migrate
170
- config.database_names = ['db1', 'db2']
193
+ # Use a static list of database names for migrate
194
+ config.database_names = ['db1', 'db2']
195
+ ```
171
196
 
172
- You can then migration your databases using the rake task:
197
+ You can then migrate your databases using the rake task:
173
198
 
174
199
  rake apartment:migrate
175
200
 
@@ -182,10 +207,33 @@ By default, when not using postgresql schemas, Apartment will prepend the enviro
182
207
  to ensure there is no conflict between your environments. This is mainly for the benefit of your development
183
208
  and test environments. If you wish to turn this option off in production, you could do something like:
184
209
 
185
- config.prepend_environment = !Rails.env.production?
210
+ ```ruby
211
+ config.prepend_environment = !Rails.env.production?
212
+ ```
186
213
 
187
214
  ## Delayed::Job
188
215
 
216
+ ### Read me before using Delayed::Job!!!
217
+ > It should be noted that we now consider Delayed::Job usage with Apartment effectively deprecated
218
+ > It will be stripped out into it's own gem for backwards compatibility, but we've had far too
219
+ > many problems with it to continue supporting it.
220
+ >
221
+ > The main problem is that Delayed::Job [monkey](https://github.com/collectiveidea/delayed_job/blob/master/lib/delayed/psych_ext.rb) [patches](https://github.com/collectiveidea/delayed_job/blob/master/lib/delayed/syck_ext.rb) YAML quite a bit, which
222
+ > forces us to in tern [monkey](https://github.com/influitive/apartment/blob/development/lib/apartment/delayed_job/psych_ext.rb) [patch](https://github.com/influitive/apartment/blob/development/lib/apartment/delayed_job/syck_ext.rb) their monkey patches, which provides
223
+ > no end of frustrations whenever upgrades are made.
224
+ >
225
+ > To this end, we recommend you look into other solutions such as Resque or Sidekiq and write
226
+ > your own hooks to switch db per job. Our aim is to have separate gems for each of these
227
+ > worker libs and would love it if anyone would like to contribute something to achieve this
228
+ >
229
+ > If you are hell bent on using `Delayed::Job`, this is what has worked for us in Production
230
+ >
231
+ > ```ruby
232
+ > gem 'delayed_job', '= 3.0.1' # change at own risk!
233
+ > gem 'delayed_job_active_record', '= 0.3.2' # change at own risk!
234
+ > ```
235
+ >
236
+
189
237
  If using Rails ~> 3.2, you *must* use `delayed_job ~> 3.0`. It has better Rails 3 support plus has some major changes that affect the serialization of models.
190
238
 
191
239
  ### If using Ruby 1.9.3-p362 you MUST use psych as your parser. YAML seems to fall down using syck
@@ -193,38 +241,45 @@ If using Rails ~> 3.2, you *must* use `delayed_job ~> 3.0`. It has better Rails
193
241
  ### If you're using syck on an earlier version of ruby, here's what you must do (in a rails app for instance)
194
242
  This can be done in the `boot.rb` of your rails config *just above* where Bundler requires the gems from the Gemfile. It will look something like:
195
243
 
196
- require 'rubygems'
197
- require 'yaml'
198
- YAML::ENGINE.yamler = 'syck'
244
+ ```ruby
245
+ require 'rubygems'
246
+ require 'yaml'
247
+ YAML::ENGINE.yamler = 'syck'
199
248
 
200
- # Set up gems listed in the Gemfile.
201
- gemfile = File.expand_path('../../Gemfile', __FILE__)
202
- ...
249
+ # Set up gems listed in the Gemfile.
250
+ gemfile = File.expand_path('../../Gemfile', __FILE__)
251
+ # ...
252
+ ```
203
253
 
204
254
  In order to make ActiveRecord models play nice with DJ and Apartment, include `Apartment::Delayed::Requirements` in any model that is being serialized by DJ. Also ensure that the `database` attribute (provided by Apartment::Delayed::Requirements) is set on this model *before* it is serialized, to ensure that when it is fetched again, it is done so in the proper Apartment db context. For example:
205
255
 
206
- class SomeModel < ActiveRecord::Base
207
- include Apartment::Delayed::Requirements
208
- end
256
+ ```ruby
257
+ class SomeModel < ActiveRecord::Base
258
+ include Apartment::Delayed::Requirements
259
+ end
260
+ ```
209
261
 
210
262
  Any classes that are being used as a Delayed::Job Job need to include the `Apartment::Delayed::Job::Hooks` module into the class. This ensures that when a job runs, it switches to the appropriate tenant before performing its task. It is also required (manually at the moment) that you set a `@database` attribute on your job so the hooks know what tennant to switch to
211
263
 
212
- class SomeDJ
213
-
214
- include Apartment::Delayed::Job::Hooks
264
+ ```ruby
265
+ class SomeDJ
266
+ include Apartment::Delayed::Job::Hooks
215
267
 
216
- def initialize
217
- @database = Apartment::Database.current_database
218
- end
268
+ def initialize
269
+ @database = Apartment::Database.current_database
270
+ end
219
271
 
220
- def perform
221
- # do some stuff (will automatically switch to @database before performing and switch back after)
222
- end
223
- end
272
+ def perform
273
+ # do some stuff (will automatically switch to @database before performing and switch back after)
274
+ end
275
+ end
276
+ ```
224
277
 
225
278
  All jobs *must* stored in the global (public) namespace, so add it to the list of excluded models:
226
279
 
227
- config.excluded_models = ["Delayed::Job"]
280
+ ```ruby
281
+ config.excluded_models = ["Delayed::Job"]
282
+ ```
228
283
 
229
284
  ## Development
230
285
 
data/Vagrantfile ADDED
@@ -0,0 +1,112 @@
1
+ # -*- mode: ruby -*-
2
+ # vi: set ft=ruby :
3
+
4
+ Vagrant::Config.run do |config|
5
+ # All Vagrant configuration is done here. The most common configuration
6
+ # options are documented and commented below. For a complete reference,
7
+ # please see the online documentation at vagrantup.com.
8
+
9
+ # Every Vagrant virtual environment requires a box to build off of.
10
+ config.vm.box = "precise32-12.04"
11
+
12
+ # The url from where the 'config.vm.box' box will be fetched if it
13
+ # doesn't already exist on the user's system.
14
+ config.vm.box_url = "http://files.vagrantup.com/precise32.box"
15
+
16
+ # Boot with a GUI so you can see the screen. (Default is headless)
17
+ # config.vm.boot_mode = :gui
18
+
19
+ # Assign this VM to a host-only network IP, allowing you to access it
20
+ # via the IP. Host-only networks can talk to the host machine as well as
21
+ # any other machines on the same network, but cannot be accessed (through this
22
+ # network interface) by any external networks.
23
+ # config.vm.network :hostonly, "192.168.33.10"
24
+
25
+ # Assign this VM to a bridged network, allowing you to connect directly to a
26
+ # network using the host's network device. This makes the VM appear as another
27
+ # physical device on your network.
28
+ # config.vm.network :bridged
29
+
30
+ # Forward a port from the guest to the host, which allows for outside
31
+ # computers to access the VM, whereas host only networking does not.
32
+ # config.vm.forward_port 80, 8080
33
+
34
+ # Share an additional folder to the guest VM. The first argument is
35
+ # an identifier, the second is the path on the guest to mount the
36
+ # folder, and the third is the path on the host to the actual folder.
37
+ # config.vm.share_folder "v-data", "/vagrant_data", "../data"
38
+
39
+ # Enable provisioning with chef solo, specifying a cookbooks path, roles
40
+ # path, and data_bags path (all relative to this Vagrantfile), and adding
41
+ # some recipes and/or roles.
42
+ #
43
+ config.vm.provision :chef_solo do |chef|
44
+ chef.cookbooks_path = "cookbooks"
45
+
46
+ # chef.roles_path = "recipes/roles"
47
+ # chef.data_bags_path = "../my-recipes/data_bags"
48
+ chef.add_recipe "apt"
49
+
50
+ chef.add_recipe "mysql"
51
+ chef.add_recipe "mysql::server"
52
+ chef.add_recipe "postgresql"
53
+ chef.add_recipe "postgresql::server"
54
+
55
+ chef.add_recipe "rvm::vagrant"
56
+ chef.add_recipe "rvm::user"
57
+ chef.add_recipe "git"
58
+ # chef.add_role "web"
59
+ #
60
+ # # You may also specify custom JSON attributes:
61
+ chef.json = {
62
+ mysql: {
63
+ version: '5.5',
64
+ server_root_password: "",
65
+ server_repl_password: "",
66
+ server_debian_password: ""
67
+ },
68
+ postgresql: {
69
+ version: '9.1',
70
+ password: {
71
+ postgres: ''
72
+ },
73
+ pg_hba: [
74
+ {type: 'local', db: 'all', user: 'all', addr: nil, method: 'trust'},
75
+ {type: 'host', db: 'all', user: 'all', addr: '127.0.0.1/32', method: 'trust'},
76
+ {type: 'host', db: 'all', user: 'all', addr: '::1/128', method: 'trust'}
77
+ ]
78
+ },
79
+ rvm: {
80
+ user_default_ruby: 'ruby-1.9.3-p392',
81
+ user_installs: [{
82
+ user: 'vagrant',
83
+ default_ruby: 'ruby-1.9.3-p392@apartment',
84
+ versions: %w{1.9.3-p392}
85
+ }]
86
+ }
87
+ }
88
+ end
89
+
90
+ # Enable provisioning with chef server, specifying the chef server URL,
91
+ # and the path to the validation key (relative to this Vagrantfile).
92
+ #
93
+ # The Opscode Platform uses HTTPS. Substitute your organization for
94
+ # ORGNAME in the URL and validation key.
95
+ #
96
+ # If you have your own Chef Server, use the appropriate URL, which may be
97
+ # HTTP instead of HTTPS depending on your configuration. Also change the
98
+ # validation key to validation.pem.
99
+ #
100
+ # config.vm.provision :chef_client do |chef|
101
+ # chef.chef_server_url = "https://api.opscode.com/organizations/ORGNAME"
102
+ # chef.validation_key_path = "ORGNAME-validator.pem"
103
+ # end
104
+ #
105
+ # If you're using the Opscode platform, your validator client is
106
+ # ORGNAME-validator, replacing ORGNAME with your organization name.
107
+ #
108
+ # IF you have your own Chef Server, the default validation client name is
109
+ # chef-validator, unless you changed the configuration.
110
+ #
111
+ # chef.validation_client_name = "ORGNAME-validator"
112
+ end