company_scope 0.1.0
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 +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/README.md +44 -0
- data/Rakefile +13 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/company_scope.gemspec +44 -0
- data/lib/company_scope.rb +25 -0
- data/lib/company_scope/base.rb +39 -0
- data/lib/company_scope/control.rb +5 -0
- data/lib/company_scope/filter.rb +54 -0
- data/lib/company_scope/guardian.rb +24 -0
- data/lib/company_scope/railtie.rb +35 -0
- data/lib/company_scope/version.rb +3 -0
- metadata +202 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 76e4908c7c91d390e44c66f6c18891493831d1b3
|
4
|
+
data.tar.gz: bac3b43ef130a3f422b56e6129ec7227dd484f7d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c28bc166bcdddc387ffb508f0accb04d0151f9bcac62b84d5a700e505f60a9ed6519acfd73cd135bd6ccd415b9538de466dbb09d395feaea75260910add0d51d
|
7
|
+
data.tar.gz: 497ad6e99a12acee86b0858caab13273478142b7e3a260aef664ff4d7f3bc821e1e4b6a75b2ecf435551e888639c274029c8c9faae4ec539de0bcc206be0b0bb
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
companyscope
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.1.1
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# CompanyScope
|
2
|
+
|
3
|
+
This is a simple solution to scoping a rails project for multi-tenancy. It is based on the default_scope
|
4
|
+
in Active Record. Currently it uses a fixed model called company for the account/scoping.
|
5
|
+
|
6
|
+
Since the whole process needs a thread_safe way to store the current company identifier (such as a subdomain) as a class attribute, the gem uses the RequestStore gem to store this value.
|
7
|
+
|
8
|
+
Thread.current is the usual way to handle this but this is not entirely compatible with all ruby application servers - especially the Java based ones. RequestStore is the solution that works in all such application servers.
|
9
|
+
|
10
|
+
## Travis CI
|
11
|
+
|
12
|
+
[](https://travis-ci.org/netflakes/company_scope)
|
13
|
+
|
14
|
+
## Installation
|
15
|
+
|
16
|
+
Add this line to your application's Gemfile:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
gem 'company_scope'
|
20
|
+
```
|
21
|
+
|
22
|
+
And then execute:
|
23
|
+
|
24
|
+
$ bundle
|
25
|
+
|
26
|
+
Or install it yourself as:
|
27
|
+
|
28
|
+
$ gem install company_scope
|
29
|
+
|
30
|
+
## Usage
|
31
|
+
|
32
|
+
NB: This gem is currently under development and has not been published officially!
|
33
|
+
(use at your own risk..)
|
34
|
+
|
35
|
+
## Development
|
36
|
+
|
37
|
+
|
38
|
+
## Contributing
|
39
|
+
|
40
|
+
1. Fork it ( https://github.com/[my-github-username]/company_scope/fork )
|
41
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
42
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
43
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
44
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
#
|
4
|
+
RSpec::Core::RakeTask.new(:spec)
|
5
|
+
task :default => :spec
|
6
|
+
#
|
7
|
+
desc 'Run the test suite for active_record models'
|
8
|
+
namespace :spec do
|
9
|
+
task :all do
|
10
|
+
Rake::Task["spec"].reenable
|
11
|
+
Rake::Task["spec"].invoke
|
12
|
+
end
|
13
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "company_scope"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'company_scope/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "company_scope"
|
8
|
+
spec.version = CompanyScope::VERSION
|
9
|
+
spec.authors = ["Steve Forkin"]
|
10
|
+
spec.email = ["steve.forkin@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{A simple solution for Rails Multi Tenancy.}
|
13
|
+
spec.description = %q{A simple solution for Rails Multi Tenancy based on Active Record Default Scopes.}
|
14
|
+
spec.homepage = "http://github.com/netflakes/company_scope"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.bindir = "exe"
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
#if spec.respond_to?(:metadata)
|
22
|
+
#spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com' to prevent pushes to rubygems.org, or delete to allow pushes to any server."
|
23
|
+
#end
|
24
|
+
#
|
25
|
+
# Development dependencies
|
26
|
+
#
|
27
|
+
# - build related ones
|
28
|
+
spec.add_development_dependency "bundler", "~> 1.8"
|
29
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
30
|
+
#
|
31
|
+
# - gem related ones
|
32
|
+
spec.add_development_dependency 'rspec'
|
33
|
+
spec.add_development_dependency 'rspec-given'
|
34
|
+
spec.add_development_dependency 'rspec-collection_matchers'
|
35
|
+
spec.add_development_dependency 'rspec-rails'
|
36
|
+
spec.add_development_dependency 'database_cleaner', '>= 1.3.0'
|
37
|
+
spec.add_development_dependency 'sqlite3'
|
38
|
+
#
|
39
|
+
# Runtime dependencies
|
40
|
+
#
|
41
|
+
spec.add_runtime_dependency 'rails', '>= 4.1.1'
|
42
|
+
spec.add_runtime_dependency 'request_store', '>= 1.1.0'
|
43
|
+
#
|
44
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "request_store"
|
2
|
+
#
|
3
|
+
require File.dirname(__FILE__) + '/company_scope/base'
|
4
|
+
require File.dirname(__FILE__) + '/company_scope/guardian'
|
5
|
+
require File.dirname(__FILE__) + '/company_scope/control'
|
6
|
+
require File.dirname(__FILE__) + '/company_scope/filter'
|
7
|
+
require File.dirname(__FILE__) + '/company_scope/railtie'
|
8
|
+
|
9
|
+
if defined?(ActiveRecord::Base)
|
10
|
+
ActiveRecord::Base.send(:include, CompanyScope::Base)
|
11
|
+
ActiveRecord::Base.send(:include, CompanyScope::Guardian)
|
12
|
+
end
|
13
|
+
|
14
|
+
if defined?(ActionController::Base)
|
15
|
+
ActionController::Base.send(:include, CompanyScope::Control)
|
16
|
+
ActionController::Base.send(:include, CompanyScope::Filter)
|
17
|
+
end
|
18
|
+
# - if this is being used in an API..
|
19
|
+
if defined?(ActionController::API)
|
20
|
+
ActionController::API.send(:include, CompanyScope::Control)
|
21
|
+
ActionController::API.send(:include, CompanyScope::Filter)
|
22
|
+
end
|
23
|
+
|
24
|
+
module CompanyScope
|
25
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
#
|
2
|
+
module CompanyScope
|
3
|
+
#
|
4
|
+
module Base
|
5
|
+
#
|
6
|
+
def self.included(base)
|
7
|
+
base.extend(CompanyScopeClassMethods)
|
8
|
+
end
|
9
|
+
#
|
10
|
+
module CompanyScopeClassMethods
|
11
|
+
#
|
12
|
+
def acts_as_company
|
13
|
+
|
14
|
+
belongs_to :company
|
15
|
+
|
16
|
+
validates_presence_of :company_id
|
17
|
+
|
18
|
+
default_scope { where("#{self.table_name}.company_id = ?", Company.current_id) }
|
19
|
+
|
20
|
+
# - on creation we make sure the company_id is set!
|
21
|
+
before_validation(on: :create) do |obj|
|
22
|
+
obj.company_id = Company.current_id
|
23
|
+
end
|
24
|
+
|
25
|
+
before_save do |obj|
|
26
|
+
# catch each time someone attempts to violate the company relationship!
|
27
|
+
raise ::CompanyScope::Control::CompanyAccessViolationError unless obj.company_id == Company.current_id
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
before_destroy do |obj|
|
32
|
+
# force company to be correct for current_user
|
33
|
+
raise ::CompanyScope::Control::CompanyAccessViolationError unless obj.company_id == Company.current_id
|
34
|
+
true
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
#
|
2
|
+
module CompanyScope
|
3
|
+
#
|
4
|
+
module Filter
|
5
|
+
#
|
6
|
+
def self.included(base)
|
7
|
+
base.extend(TopLevelClassMethods)
|
8
|
+
base.extend(FilterClassMethods)
|
9
|
+
base.include(FilterMethods)
|
10
|
+
end
|
11
|
+
|
12
|
+
module FilterMethods
|
13
|
+
|
14
|
+
def current_company
|
15
|
+
request.env["COMPANY_ID"] # a default that is best overridden in the application controller..
|
16
|
+
end
|
17
|
+
|
18
|
+
def filter_by_current_company_scope
|
19
|
+
Company.current_id = current_company.id
|
20
|
+
yield
|
21
|
+
ensure
|
22
|
+
Company.current_id = nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module FilterClassMethods
|
27
|
+
#
|
28
|
+
def company_setup
|
29
|
+
helper_method :current_company
|
30
|
+
end
|
31
|
+
#
|
32
|
+
def acts_as_company_filter
|
33
|
+
around_filter :filter_by_current_company_scope
|
34
|
+
end
|
35
|
+
end
|
36
|
+
#
|
37
|
+
module TopLevelClassMethods
|
38
|
+
#
|
39
|
+
def rescue_from_company_access_violations
|
40
|
+
# - rescue from errors relating to the wrong company to avoid cross company data leakage
|
41
|
+
rescue_from ::CompanyScope::Control::CompanyAccessViolationError, with: :company_scope_company_not_set
|
42
|
+
end
|
43
|
+
|
44
|
+
def company_scope_company_not_set
|
45
|
+
flash[:error] = t('application.warnings.company_not_set')
|
46
|
+
if wrong_company_path.nil?
|
47
|
+
redirect_to(root_path) and return
|
48
|
+
else
|
49
|
+
redirect_to(wrong_company_path) and return
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
#
|
2
|
+
module CompanyScope
|
3
|
+
#
|
4
|
+
module Guardian
|
5
|
+
#
|
6
|
+
def self.included(base)
|
7
|
+
base.extend(GuardianClassMethods)
|
8
|
+
end
|
9
|
+
#
|
10
|
+
module GuardianClassMethods
|
11
|
+
#
|
12
|
+
def acts_as_guardian
|
13
|
+
#
|
14
|
+
def current_id=(id)
|
15
|
+
RequestStore.store[:default_scope_company_id] = id
|
16
|
+
end
|
17
|
+
|
18
|
+
def current_id
|
19
|
+
RequestStore.store[:default_scope_company_id]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'company_scope'
|
2
|
+
require 'rails'
|
3
|
+
#
|
4
|
+
module CompanyScope
|
5
|
+
class Railtie < Rails::Railtie
|
6
|
+
#
|
7
|
+
initializer :after_initialize do
|
8
|
+
# - the base module injects the default scope into company dependant models
|
9
|
+
ActiveRecord::Base.send(:include, CompanyScope::Base)
|
10
|
+
|
11
|
+
# - the company_entity module injects class methods for acting as the company!
|
12
|
+
ActiveRecord::Base.send(:include, CompanyScope::Guardian)
|
13
|
+
|
14
|
+
if defined?(ActionController::Base)
|
15
|
+
# - the control module has some error handling for the application controller
|
16
|
+
ActionController::Base.send(:include, CompanyScope::Control)
|
17
|
+
# - the controller_filter module injects an around filter to ensure all
|
18
|
+
# - actions in the controller are wrapped
|
19
|
+
ActionController::Base.send(:include, CompanyScope::Filter)
|
20
|
+
end
|
21
|
+
# - if this is being used in an API..
|
22
|
+
if defined?(ActionController::API)
|
23
|
+
# - the control module has some error handling for the application controller
|
24
|
+
ActionController::API.send(:include, CompanyScope::Control)
|
25
|
+
# - the controller_filter module injects an around filter to ensure all
|
26
|
+
# - actions in the controller are wrapped
|
27
|
+
ActionController::API.send(:include, CompanyScope::Filter)
|
28
|
+
end
|
29
|
+
|
30
|
+
#ActionController::Base.send(:include, CompanyScope::Control)
|
31
|
+
#ActionController::Base.send(:include, CompanyScope::Filter)
|
32
|
+
end
|
33
|
+
#
|
34
|
+
end
|
35
|
+
end
|
metadata
ADDED
@@ -0,0 +1,202 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: company_scope
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Steve Forkin
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-03-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.8'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.8'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec-given
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec-collection_matchers
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec-rails
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: database_cleaner
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 1.3.0
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 1.3.0
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: sqlite3
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
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
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rails
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 4.1.1
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 4.1.1
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: request_store
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: 1.1.0
|
146
|
+
type: :runtime
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: 1.1.0
|
153
|
+
description: A simple solution for Rails Multi Tenancy based on Active Record Default
|
154
|
+
Scopes.
|
155
|
+
email:
|
156
|
+
- steve.forkin@gmail.com
|
157
|
+
executables: []
|
158
|
+
extensions: []
|
159
|
+
extra_rdoc_files: []
|
160
|
+
files:
|
161
|
+
- ".gitignore"
|
162
|
+
- ".rspec"
|
163
|
+
- ".ruby-gemset"
|
164
|
+
- ".ruby-version"
|
165
|
+
- ".travis.yml"
|
166
|
+
- Gemfile
|
167
|
+
- README.md
|
168
|
+
- Rakefile
|
169
|
+
- bin/console
|
170
|
+
- bin/setup
|
171
|
+
- company_scope.gemspec
|
172
|
+
- lib/company_scope.rb
|
173
|
+
- lib/company_scope/base.rb
|
174
|
+
- lib/company_scope/control.rb
|
175
|
+
- lib/company_scope/filter.rb
|
176
|
+
- lib/company_scope/guardian.rb
|
177
|
+
- lib/company_scope/railtie.rb
|
178
|
+
- lib/company_scope/version.rb
|
179
|
+
homepage: http://github.com/netflakes/company_scope
|
180
|
+
licenses: []
|
181
|
+
metadata: {}
|
182
|
+
post_install_message:
|
183
|
+
rdoc_options: []
|
184
|
+
require_paths:
|
185
|
+
- lib
|
186
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
187
|
+
requirements:
|
188
|
+
- - ">="
|
189
|
+
- !ruby/object:Gem::Version
|
190
|
+
version: '0'
|
191
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
192
|
+
requirements:
|
193
|
+
- - ">="
|
194
|
+
- !ruby/object:Gem::Version
|
195
|
+
version: '0'
|
196
|
+
requirements: []
|
197
|
+
rubyforge_project:
|
198
|
+
rubygems_version: 2.2.2
|
199
|
+
signing_key:
|
200
|
+
specification_version: 4
|
201
|
+
summary: A simple solution for Rails Multi Tenancy.
|
202
|
+
test_files: []
|