tenanfy 0.0.1
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.
- data/MIT-LICENSE +20 -0
- data/README.rdoc +57 -0
- data/Rakefile +27 -0
- data/app/models/tenanfy/tenant.rb +42 -0
- data/app/models/tenanfy/url.rb +8 -0
- data/db/migrate/20130128025640_create_tenanfy_tenants.rb +12 -0
- data/db/migrate/20130128025755_create_tenanfy_urls.rb +11 -0
- data/lib/generators/tenanfy/install/USAGE +8 -0
- data/lib/generators/tenanfy/install/install_generator.rb +24 -0
- data/lib/generators/tenanfy/install/templates/initializer.rb +3 -0
- data/lib/tasks/tenantfy_tasks.rake +4 -0
- data/lib/tenanfy/apartment/elevator.rb +14 -0
- data/lib/tenanfy/configurations/keys.rb +21 -0
- data/lib/tenanfy/configurations/values.rb +13 -0
- data/lib/tenanfy/controller.rb +48 -0
- data/lib/tenanfy/engine.rb +9 -0
- data/lib/tenanfy/helpers.rb +41 -0
- data/lib/tenanfy/version.rb +3 -0
- data/lib/tenanfy.rb +11 -0
- metadata +182 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2013 YOURNAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
= Tenanfy
|
2
|
+
|
3
|
+
This is opinionated use of the awesome Apartment gem.
|
4
|
+
It's a plug and play ready solution for enabling multitenancy in rails applications.
|
5
|
+
It comes with configurations and helpers I find usefull in my projects.
|
6
|
+
|
7
|
+
Apartment is an awesome gem by itself, be sure to read more about it at https://github.com/bradrobertson/apartment
|
8
|
+
|
9
|
+
|
10
|
+
=== How
|
11
|
+
|
12
|
+
If you don't know what is tenantcy or how the apartment gem works, please read https://github.com/bradrobertson/apartment first.
|
13
|
+
|
14
|
+
This gem creates 2 models, Tenanfy::[Tenant, Url].
|
15
|
+
When you include the Tenanfy::Controller to your controller, it will automatically check for the correct Tenant for each request.
|
16
|
+
This is done by looking for the request.host in the Tenanfy::Url table. The current tenant will be available in the `current_tenant` method.
|
17
|
+
|
18
|
+
This is meant to be used and only tested using PostgreSQL.
|
19
|
+
Every tenant will have it own schema in the database, and the apartment gem will use postgres' awesome `search_path` to look in the current_tenant schema first.
|
20
|
+
You may have shared tables between your tenants, check the `config/initializers/apartment.rb`.
|
21
|
+
|
22
|
+
=== Why
|
23
|
+
|
24
|
+
If you don't need any of this customizations, I recommend you use the Apartment gem alone.
|
25
|
+
I only made this because I had these requirements in more than one project, and maybe someone could find a good use of it too.
|
26
|
+
|
27
|
+
=== Customizations
|
28
|
+
|
29
|
+
==== Custom Views per tenant
|
30
|
+
|
31
|
+
In my projects, each tenant usually have a different layout/theme. So the tenant model have a `theme` attribute.
|
32
|
+
When this is setted, the `Tenanfy::Controller` will prepend the tenant theme in the view path, for example, if the theme is `blue`,
|
33
|
+
the path `app/views/blue` will be prepended to the controller view_path.
|
34
|
+
|
35
|
+
==== Custom JS/CSS per tenant
|
36
|
+
|
37
|
+
There are helpers to include the javascripts/stylesheets with the tenant theme (ex: app/assets/javascripts/blue/application.js).
|
38
|
+
|
39
|
+
Example:
|
40
|
+
|
41
|
+
<%= tenant_stylesheet_link_tag('application.css') %> # will include blue/application.css
|
42
|
+
<%= tenant_javascript_include_tag('application.js') %> # will include blue/application.js
|
43
|
+
|
44
|
+
== Installation
|
45
|
+
|
46
|
+
Gemfile:
|
47
|
+
|
48
|
+
gem 'tenanfy'
|
49
|
+
|
50
|
+
Console:
|
51
|
+
|
52
|
+
rails g tenanfy:install
|
53
|
+
rake db:migrate
|
54
|
+
|
55
|
+
= License
|
56
|
+
|
57
|
+
This project uses MIT-LICENSE.
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
rescue LoadError
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
|
+
end
|
7
|
+
begin
|
8
|
+
require 'rdoc/task'
|
9
|
+
rescue LoadError
|
10
|
+
require 'rdoc/rdoc'
|
11
|
+
require 'rake/rdoctask'
|
12
|
+
RDoc::Task = Rake::RDocTask
|
13
|
+
end
|
14
|
+
|
15
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
16
|
+
rdoc.rdoc_dir = 'rdoc'
|
17
|
+
rdoc.title = 'Tenanfy'
|
18
|
+
rdoc.options << '--line-numbers'
|
19
|
+
rdoc.rdoc_files.include('README.rdoc')
|
20
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
|
26
|
+
Bundler::GemHelper.install_tasks
|
27
|
+
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Tenanfy
|
2
|
+
class Tenant < ActiveRecord::Base
|
3
|
+
attr_accessible :description, :keywords, :name, :theme, :configs
|
4
|
+
validates :name, :theme, presence: true
|
5
|
+
validates :theme, format: { with: /[a-z0-9_]+/ }
|
6
|
+
serialize :configs, Hash
|
7
|
+
|
8
|
+
has_many :urls, inverse_of: :tenant
|
9
|
+
|
10
|
+
after_create :build_schema
|
11
|
+
after_destroy :drop_schema
|
12
|
+
|
13
|
+
scope :filter_by_domain, ->(domain) { joins(:urls).where("tenanfy_urls.url" => domain).readonly(false) }
|
14
|
+
|
15
|
+
def self.find_by_domain(host)
|
16
|
+
self.filter_by_domain(host).first
|
17
|
+
end
|
18
|
+
|
19
|
+
def schema_name
|
20
|
+
"tenant_#{self.id}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def switch_to_tenant
|
24
|
+
::Apartment::Database.switch(self.schema_name)
|
25
|
+
end
|
26
|
+
|
27
|
+
def theme_path
|
28
|
+
"app/views/#{self.theme}"
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def build_schema
|
34
|
+
::Apartment::Database.create(self.schema_name)
|
35
|
+
end
|
36
|
+
|
37
|
+
def drop_schema
|
38
|
+
::Apartment::Database.drop(self.schema_name)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class CreateTenanfyTenants < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :tenanfy_tenants do |t|
|
4
|
+
t.string :name , null: false
|
5
|
+
t.string :theme , null: false
|
6
|
+
t.string :description
|
7
|
+
t.string :keywords
|
8
|
+
t.text :configs
|
9
|
+
t.timestamps
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class CreateTenanfyUrls < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :tenanfy_urls do |t|
|
4
|
+
t.string :url , null: false
|
5
|
+
t.references :tenant, null: false
|
6
|
+
t.timestamps
|
7
|
+
end
|
8
|
+
add_index :tenanfy_urls, :tenant_id
|
9
|
+
add_index :tenanfy_urls, :url, unique: true
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class Tenanfy::InstallGenerator < Rails::Generators::Base
|
2
|
+
source_root File.expand_path('../templates', __FILE__)
|
3
|
+
|
4
|
+
def run_migrations
|
5
|
+
rake("tenanfy:install:migrations")
|
6
|
+
end
|
7
|
+
|
8
|
+
def copy_initializer
|
9
|
+
template "initializer.rb", "config/initializers/apartment.rb"
|
10
|
+
end
|
11
|
+
|
12
|
+
def setup_middleware
|
13
|
+
inject_into_class "config/application.rb", 'Application' do
|
14
|
+
"\n require 'tenanfy/apartment/elevator'\n" +
|
15
|
+
" config.middleware.use 'Apartment::Elevators::Generic', Tenanfy::Apartment::Elevator.new\n"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def setup_application_controller
|
20
|
+
inject_into_class "app/controllers/application_controller.rb", 'ApplicationController' do
|
21
|
+
"\n include Tenanfy::Controller\n"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Tenanfy
|
2
|
+
module Configurations
|
3
|
+
# These are constant key names for the tenant configurations.
|
4
|
+
# The values for each of these keys must be set (if used) in
|
5
|
+
# Tenanfy::Tenant.configurations
|
6
|
+
module Keys
|
7
|
+
# Holds what kind of comment the tenant uses
|
8
|
+
COMMENT_TYPE = 'comment_type'
|
9
|
+
|
10
|
+
# FACEBOOK configuration
|
11
|
+
FACEBOOK_APP_ID = 'facebook_app_id'
|
12
|
+
FACEBOOK_APP_SECRET = 'facebook_app_secret'
|
13
|
+
|
14
|
+
# DISQUS configuration
|
15
|
+
DISQUS_SHORT_NAME = 'disqus_name'
|
16
|
+
|
17
|
+
# LIVEFYRE configuration
|
18
|
+
LIVEFYRE_SITE_ID = 'livefyre_id'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Tenanfy
|
2
|
+
module Configurations
|
3
|
+
# These are constant key names for the tenant configurations.
|
4
|
+
# The values for each of these keys must be set (if used) in
|
5
|
+
# Tenanfy::Tenant.configurations
|
6
|
+
module Values
|
7
|
+
# Comment possible values
|
8
|
+
COMMENT_FACEBOOK = 'facebook'
|
9
|
+
COMMENT_DISQUS = 'disqus'
|
10
|
+
COMMENT_LIVEFYRE = 'livefyre'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Tenanfy
|
2
|
+
module Controller
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
around_filter :setup_tenant_thread
|
7
|
+
helper_method :current_tenant, :current_tenant_theme, :current_tenant_name
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
# Setups the current_tenant in the Thread.current
|
13
|
+
# so it can be accessed by different contexts in the
|
14
|
+
# same request
|
15
|
+
#
|
16
|
+
# ensures that it will be set to nil after the request
|
17
|
+
# this way there isn't any dead tenant hanging
|
18
|
+
#
|
19
|
+
def setup_tenant_thread
|
20
|
+
Thread.current[:tenant] = current_tenant
|
21
|
+
prepend_tenant_theme
|
22
|
+
yield
|
23
|
+
ensure
|
24
|
+
Thread.current[:tenant] = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
# Adds the tenant theme in the controller view path
|
28
|
+
def prepend_tenant_theme
|
29
|
+
prepend_view_path current_tenant.theme_path if current_tenant && current_tenant.theme_path
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns the current tenant for this request
|
33
|
+
def current_tenant
|
34
|
+
@current_tenant ||= Tenant.find_by_domain(request.host)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns the current tenant theme
|
38
|
+
def current_tenant_theme
|
39
|
+
current_tenant ? current_tenant.theme : ""
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns the current tenant name
|
43
|
+
def current_tenant_name
|
44
|
+
current_tenant ? current_tenant.name : "Global"
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Tenanfy
|
2
|
+
module Helpers
|
3
|
+
#extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
# Appends the tenant theme to the source if not a full-path
|
6
|
+
# or url
|
7
|
+
def tenant_stylesheet_link_tag(*sources)
|
8
|
+
new_sources = append_tenant_theme_to_assets(*sources)
|
9
|
+
stylesheet_link_tag(*new_sources)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Appends the tenant theme to the source if not a full-path
|
13
|
+
# or url
|
14
|
+
def tenant_javascript_include_tag(*sources)
|
15
|
+
new_sources = append_tenant_theme_to_assets(*sources)
|
16
|
+
javascript_include_tag(*new_sources)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
# updates the asset list with tenant theme where it's
|
22
|
+
# necessary
|
23
|
+
#
|
24
|
+
def append_tenant_theme_to_assets(*assets)
|
25
|
+
assets.map! do |asset|
|
26
|
+
should_add_tenant_theme_to_asset?(asset) && current_tenant ? "#{current_tenant.theme}/#{asset}" : asset
|
27
|
+
end
|
28
|
+
assets
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns false if the asset is a full-path or url
|
32
|
+
# true for everything else
|
33
|
+
def should_add_tenant_theme_to_asset?(asset)
|
34
|
+
if asset.is_a? String
|
35
|
+
return true if (! asset.match /^http:\/\//) && (! asset.match /^\//)
|
36
|
+
end
|
37
|
+
false
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
data/lib/tenanfy.rb
ADDED
metadata
ADDED
@@ -0,0 +1,182 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tenanfy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Tiago Scolari
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-01-28 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rails
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 3.2.11
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 3.2.11
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: apartment
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: pg
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rspec-rails
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: factory_girl_rails
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: database_cleaner
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: debugger
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
description: Offers a opinated use of the Apartment gem.
|
127
|
+
email:
|
128
|
+
- tscolari@gmail.com
|
129
|
+
executables: []
|
130
|
+
extensions: []
|
131
|
+
extra_rdoc_files: []
|
132
|
+
files:
|
133
|
+
- app/models/tenanfy/tenant.rb
|
134
|
+
- app/models/tenanfy/url.rb
|
135
|
+
- db/migrate/20130128025640_create_tenanfy_tenants.rb
|
136
|
+
- db/migrate/20130128025755_create_tenanfy_urls.rb
|
137
|
+
- lib/generators/tenanfy/install/install_generator.rb
|
138
|
+
- lib/generators/tenanfy/install/templates/initializer.rb
|
139
|
+
- lib/generators/tenanfy/install/USAGE
|
140
|
+
- lib/tasks/tenantfy_tasks.rake
|
141
|
+
- lib/tenanfy/apartment/elevator.rb
|
142
|
+
- lib/tenanfy/configurations/keys.rb
|
143
|
+
- lib/tenanfy/configurations/values.rb
|
144
|
+
- lib/tenanfy/controller.rb
|
145
|
+
- lib/tenanfy/engine.rb
|
146
|
+
- lib/tenanfy/helpers.rb
|
147
|
+
- lib/tenanfy/version.rb
|
148
|
+
- lib/tenanfy.rb
|
149
|
+
- MIT-LICENSE
|
150
|
+
- Rakefile
|
151
|
+
- README.rdoc
|
152
|
+
homepage: https://github.com/tscolari/tenanfy
|
153
|
+
licenses: []
|
154
|
+
post_install_message:
|
155
|
+
rdoc_options: []
|
156
|
+
require_paths:
|
157
|
+
- lib
|
158
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
159
|
+
none: false
|
160
|
+
requirements:
|
161
|
+
- - ! '>='
|
162
|
+
- !ruby/object:Gem::Version
|
163
|
+
version: '0'
|
164
|
+
segments:
|
165
|
+
- 0
|
166
|
+
hash: 639758106311764618
|
167
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
168
|
+
none: false
|
169
|
+
requirements:
|
170
|
+
- - ! '>='
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: '0'
|
173
|
+
segments:
|
174
|
+
- 0
|
175
|
+
hash: 639758106311764618
|
176
|
+
requirements: []
|
177
|
+
rubyforge_project:
|
178
|
+
rubygems_version: 1.8.24
|
179
|
+
signing_key:
|
180
|
+
specification_version: 3
|
181
|
+
summary: Mult-Tenant drop and play solution.
|
182
|
+
test_files: []
|