Browse files

Initial Release

  • Loading branch information...
0 parents commit 77da582670b6fac4ce2d865740b1ff8bec14bafb @mconnell committed Jun 18, 2010
Showing with 217 additions and 0 deletions.
  1. +20 −0 MIT-LICENSE
  2. +36 −0 README.markdown
  3. +23 −0 Rakefile
  4. +1 −0 init.rb
  5. +43 −0 lib/multi_tenant.rb
  6. +14 −0 test/db/load_schema.rb
  7. +4 −0 test/models/account.rb
  8. +3 −0 test/models/property.rb
  9. +46 −0 test/multi_tenant_test.rb
  10. +27 −0 test/test_helper.rb
20 MIT-LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2010 [Mark Connell]
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36 README.markdown
@@ -0,0 +1,36 @@
+# MultiTenant
+
+MultiTenant is a Rails 3 plugin to help ease the development of web applications
+that utilise the database in a multi-tenant manner, and provide each end-user/account
+with their own subdomained version of the application.
+
+This plugin is currently work in progess. Managing subdomains still to do..
+
+# How to Use
+Add `multi_tenant_model` to the primary model
+ class Account < ActiveRecord::Base
+ multi_tenant_model
+ has_many :properties
+ end
+
+Any models that should be explicitly scoped to a `multi_tenant_model` require the `belongs_to_tenant` to be set
+ class Property < ActiveRecord::Base
+ belongs_to_tenant :account
+ end
+
+Property scoping will behave as normal unless a current account has been set:
+ Property.all
+ #> [#<Property account_id: 1>, #<Property account_id: 2>]
+ Account.current = Account.find(1)
+ Property.all
+ #> [#<Property account_id: 1>]
+
+If the current account is set, instantiating new property records will automatically be assigned to the current account:
+ Property.new
+ #> <Property account_id: nil>
+
+ Account.current = Account.find(25)
+ Property.new
+ #> <Property account_id: 25>
+
+Copyright (c) 2010 [Mark Connell], released under the MIT license
23 Rakefile
@@ -0,0 +1,23 @@
+require 'rake'
+require 'rake/testtask'
+require 'rake/rdoctask'
+
+desc 'Default: run unit tests.'
+task :default => :test
+
+desc 'Test the multi_tenant plugin.'
+Rake::TestTask.new(:test) do |t|
+ t.libs << 'lib'
+ t.libs << 'test'
+ t.pattern = 'test/**/*_test.rb'
+ t.verbose = true
+end
+
+desc 'Generate documentation for the multi_tenant plugin.'
+Rake::RDocTask.new(:rdoc) do |rdoc|
+ rdoc.rdoc_dir = 'rdoc'
+ rdoc.title = 'MultiTenant'
+ rdoc.options << '--line-numbers' << '--inline-source'
+ rdoc.rdoc_files.include('README')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+end
1 init.rb
@@ -0,0 +1 @@
+ActiveRecord::Base.send :include, MultiTenant
43 lib/multi_tenant.rb
@@ -0,0 +1,43 @@
+module MultiTenant
+ def self.included(base)
+ base.extend(ClassMethods)
+ end
+
+ module ClassMethods
+ def multi_tenant_model
+ self.class_eval do
+ cattr_accessor :current
+ end
+ end
+
+ def belongs_to_tenant(tenant = :account)
+ self.class_eval do
+ cattr_accessor :tenant, :tenant_class
+ end
+
+ self.tenant ||= tenant
+ self.tenant_class ||= tenant.to_s.capitalize.constantize
+
+ self.class_eval do
+ belongs_to tenant
+
+ def self.unscoped
+ if tenant_class.current
+ super.apply_finder_options(:conditions => { "#{tenant}_id" => tenant_class.current.id })
+ else
+ super
+ end
+ end
+
+ def initialize(attributes = nil)
+ if tenant_class.current
+ new_attributes = {"#{tenant}_id" => tenant_class.current.id }
+ new_attributes.merge!(attributes) if attributes.is_a?(Hash)
+ attributes = new_attributes
+ end
+ super(attributes)
+ end
+ end
+ end
+ end
+end
14 test/db/load_schema.rb
@@ -0,0 +1,14 @@
+ActiveRecord::Schema.define do
+ create_table "accounts", :force => true do |t|
+ t.string "name"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ create_table "properties", :force => true do |t|
+ t.string "name"
+ t.integer "account_id"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+end
4 test/models/account.rb
@@ -0,0 +1,4 @@
+class Account < ActiveRecord::Base
+ multi_tenant_model
+ has_many :properties
+end
3 test/models/property.rb
@@ -0,0 +1,3 @@
+class Property < ActiveRecord::Base
+ belongs_to_tenant :account
+end
46 test/multi_tenant_test.rb
@@ -0,0 +1,46 @@
+require 'test_helper'
+
+class MultiTenantTest < ActiveSupport::TestCase
+ test "Property.all should return all property records when the current account hasn't been set" do
+ Account.current = nil
+ assert_equal(4, Property.all.count)
+ end
+
+ test "Property.all is explicitly scoped to the current account when set" do
+ Account.current = Account.find_by_name('Acme Housing Ltd')
+
+ assert_equal(2, Property.all.count)
+ Property.all.each do |property|
+ assert_equal(Account.current.id, property.account_id)
+ end
+ end
+
+ test "new property records are not associated to any account when Account.current is unset" do
+ Account.current = nil
+ assert_nil(Property.new.account_id)
+ end
+
+ test "new property records are automatically associated to the current account if set" do
+ Account.current = Account.find_by_name('Cardboard Housing Ltd')
+ assert_equal(Account.current.id, Property.new.account_id)
+ end
+
+ test "no records should be return when attempting to find records belonging to a different account when Account.current is set" do
+ Account.current = nil
+ assert(Property.find_by_name('4 Bed House'))
+
+ Account.current = Account.find_by_name('Cardboard Housing Ltd')
+ assert_nil(Property.find_by_name('4 Bed House'))
+ end
+
+ test "find_or_create_by_attribute juju should work as expected" do
+ Account.current = nil
+ existing_acme_property = Property.find_or_create_by_name('3 Bed House')
+
+ Account.current = Account.find_by_name('Cardboard Housing Ltd')
+ new_cardboard_property = Property.find_or_create_by_name('3 Bed House')
+
+ assert(existing_acme_property != new_cardboard_property)
+ assert_equal(new_cardboard_property.account_id, Account.current.id)
+ end
+end
27 test/test_helper.rb
@@ -0,0 +1,27 @@
+require 'rubygems'
+require 'test/unit'
+require 'active_support'
+
+require 'active_record'
+require 'active_record/fixtures'
+
+ActiveRecord::Base.establish_connection(
+ :adapter => "sqlite3",
+ :database => ":memory:"
+)
+require 'db/load_schema'
+
+require 'multi_tenant'
+ActiveRecord::Base.send :include, MultiTenant
+
+require 'models/account'
+require 'models/property'
+
+# Fixtures
+acme_account = Account.create(:name => 'Acme Housing Ltd')
+Property.create(:name => '3 Bed House', :account_id => acme_account.id)
+Property.create(:name => '4 Bed House', :account_id => acme_account.id)
+
+cardboard_account = Account.create(:name => 'Cardboard Housing Ltd')
+Property.create(:name => 'Double Layered Box', :account_id => cardboard_account.id)
+Property.create(:name => 'Corregated Iron Sheet', :account_id => cardboard_account.id)

0 comments on commit 77da582

Please sign in to comment.