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 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,8 @@
1
+ module Tenanfy
2
+ class Url < ActiveRecord::Base
3
+ belongs_to :tenant, inverse_of: :urls
4
+ attr_accessible :url
5
+ validates :url, presence: true, uniqueness: true
6
+ validates :tenant_id, presence: true
7
+ end
8
+ 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,8 @@
1
+ Description:
2
+ Explain the generator
3
+
4
+ Example:
5
+ rails generate install Thing
6
+
7
+ This will create:
8
+ what/will/it/create
@@ -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,3 @@
1
+ Apartment.configure do |config|
2
+ config.excluded_models = ["Tenanfy::Tenant", "Tenanfy::Url"]
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :tenanfy do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,14 @@
1
+ module Tenanfy
2
+ module Apartment
3
+ class Elevator
4
+ def call(request)
5
+ tenant = Tenant.find_by_domain(request.host)
6
+ if tenant
7
+ tenant.schema_name
8
+ else
9
+ "public"
10
+ end
11
+ end
12
+ end
13
+ end
14
+ 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,9 @@
1
+ module Tenanfy
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Tenanfy
4
+
5
+ initializer "tenanfy.include_helpers" do |app|
6
+ ActionController::Base.helper ::Tenanfy::Helpers
7
+ end
8
+ end
9
+ 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
@@ -0,0 +1,3 @@
1
+ module Tenanfy
2
+ VERSION = "0.0.1"
3
+ end
data/lib/tenanfy.rb ADDED
@@ -0,0 +1,11 @@
1
+ require "apartment"
2
+
3
+ module Tenanfy
4
+ require 'tenanfy/configurations/keys'
5
+ require 'tenanfy/configurations/values'
6
+ require 'tenanfy/apartment/elevator'
7
+ require 'tenanfy/controller'
8
+ require 'tenanfy/helpers'
9
+ end
10
+
11
+ require "tenanfy/engine"
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: []