tenancy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ OGQ1N2M3MjhhMTIwNzUwNDg5MjM3YjdmZTAzNmU0MjViYjk1ZjhjYQ==
5
+ data.tar.gz: !binary |-
6
+ MzUyY2NjNjA0YWRlOGY3M2QzZjY5ODI1Y2I5ZGU4ODMyYmI4YzljZA==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ YjM4NWFhMjEwZTAxMGZhNTRmZTcyMWYwYzQ2Njk2ZjNkZTZlMDg3YjRjYTRi
10
+ ZWY2OTg3ODliYzQ5YzQwMjNkYmRlNzc4MTZmMTdlZTI0OTAzNjI2ZGQ3MmIz
11
+ NzUxNjFjYzc3NDc1MmE0NzY4Mzg0YTUxZDYyNWE3ZDU5MDkyNDY=
12
+ data.tar.gz: !binary |-
13
+ MGI2MWY0ZjI2NjM1ZDcyMWRhMTAwNDhkMzIxOTIwNGQ2YmYzYmM4NmNlZjk5
14
+ YzE1MWJhNDUwZTAyNjY4M2M3YzViNjEyYzI0MzU4YTlmMzRiYmExZGJkMzRh
15
+ MTQ4MWI3NWQ3MzhmNDQwZjBmMGY0YjE1MTc2Y2QyNTQwNDQ2YTc=
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ spec/*.sqlite3
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour
data/.rvmrc ADDED
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # This is an RVM Project .rvmrc file, used to automatically load the ruby
4
+ # development environment upon cd'ing into the directory
5
+
6
+ # First we specify our desired <ruby>[@<gemset>], the @gemset name is optional.
7
+ environment_id="ruby-2.0.0-p247@tenancy"
8
+
9
+ #
10
+ # Uncomment the following lines if you want to verify rvm version per project
11
+ #
12
+ rvmrc_rvm_version="1.17.10" # 1.10.1 seams as a safe start
13
+ eval "$(echo ${rvm_version}.${rvmrc_rvm_version} | awk -F. '{print "[[ "$1*65536+$2*256+$3" -ge "$4*65536+$5*256+$6" ]]"}' )" || {
14
+ echo "This .rvmrc file requires at least RVM ${rvmrc_rvm_version}, aborting loading."
15
+ return 1
16
+ }
17
+ #
18
+
19
+ #
20
+ # Uncomment following line if you want options to be set only for given project.
21
+ #
22
+ # PROJECT_JRUBY_OPTS=( --1.9 )
23
+ #
24
+ # The variable PROJECT_JRUBY_OPTS requires the following to be run in shell:
25
+ #
26
+ # chmod +x ${rvm_path}/hooks/after_use_jruby_opts
27
+ #
28
+
29
+ #
30
+ # First we attempt to load the desired environment directly from the environment
31
+ # file. This is very fast and efficient compared to running through the entire
32
+ # CLI and selector. If you want feedback on which environment was used then
33
+ # insert the word 'use' after --create as this triggers verbose mode.
34
+ #
35
+ if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \
36
+ && -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]
37
+ then
38
+ \. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
39
+
40
+ if [[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]]
41
+ then
42
+ . "${rvm_path:-$HOME/.rvm}/hooks/after_use"
43
+ fi
44
+ else
45
+ # If the environment file has not yet been created, use the RVM CLI to select.
46
+ if ! rvm --create use "$environment_id"
47
+ then
48
+ echo "Failed to create RVM environment '${environment_id}'."
49
+ return 1
50
+ fi
51
+ fi
52
+
53
+ #
54
+ # If you use an RVM gemset file to install a list of gems (*.gems), you can have
55
+ # it be automatically loaded. Uncomment the following and adjust the filename if
56
+ # necessary.
57
+ #
58
+ # filename=".gems"
59
+ # if [[ -s "$filename" ]]
60
+ # then
61
+ # rvm gemset import "$filename" | grep -v already | grep -v listed | grep -v complete | sed '/^$/d'
62
+ # fi
63
+
64
+ # If you use bundler, this might be useful to you:
65
+ if [[ -s Gemfile ]] && ! command -v bundle >/dev/null
66
+ then
67
+ printf "%b" "The rubygem 'bundler' is not installed. Installing it now.\n"
68
+ gem install bundler
69
+ fi
70
+ if [[ -s Gemfile ]] && command -v bundle
71
+ then
72
+ bundle install
73
+ fi
74
+
75
+ if [[ $- == *i* ]] # check for interactive shells
76
+ then
77
+ echo "Using: $(tput setaf 2)$GEM_HOME$(tput sgr0)" # show the user the ruby and gemset they are using in green
78
+ else
79
+ echo "Using: $GEM_HOME" # don't use colors in interactive shells
80
+ fi
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in tenancy.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 chamnap
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,146 @@
1
+ # Tenancy
2
+
3
+ `tenancy` is a simple gem that provides multi-tenancy support on activerecord through scoping. I suggest you to watch an excellent [RailsCast on Multitenancy with Scopes](http://railscasts.com/episodes/388-multitenancy-with-scopes) and read this book [Multitenancy with Rails](https://leanpub.com/multi-tenancy-rails).
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'tenancy'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ ```
16
+ $ bundle
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ This gem provides two modules: `Tenancy::Resource` and `Tenancy::ResourceScope`.
22
+
23
+ ### Tenancy::Resource
24
+
25
+ `Tenancy::Resource` is a module which you want others to be scoped by.
26
+
27
+ ```ruby
28
+ class Portal < ActiveRecord::Base
29
+ include Tenancy::Resource
30
+ end
31
+
32
+ camyp = Portal.where(domain_name: 'yp.com.kh').first
33
+ # => <Portal id: 1, domain_name: 'yp.com.kh'>
34
+
35
+ # set current portal by id
36
+ Portal.current = camyp
37
+
38
+ # or portal object
39
+ Portal.current = 1
40
+
41
+ # get current portal
42
+ Portal.current
43
+ # => <Portal id: 1, domain_name: 'yp.com.kh'>
44
+
45
+ # scope with this portal
46
+ Portal.with(camyp) do
47
+ # Do something here with this portal
48
+ end
49
+ ```
50
+
51
+ ### Tenancy::ResourceScope
52
+
53
+ `Tenancy::ResourceScope` is a module which you want to scope itself to `Tenancy::Resource`.
54
+
55
+ ```ruby
56
+ class Listing < ActiveRecord::Base
57
+ include Tenancy::Resource
58
+ include Tenancy::ResourceScope
59
+
60
+ scope_to :portal
61
+ validates_uniqueness_in_scope :name, case_sensitive: false
62
+ end
63
+
64
+ class Communication < ActiveRecord::Base
65
+ include Tenancy::ResourceScope
66
+
67
+ scope_to :portal, :listing
68
+ validates_uniqueness_in_scope :value
69
+ end
70
+
71
+ class ExtraCommunication < ActiveRecord::Base
72
+ include Tenancy::ResourceScope
73
+
74
+ # options here will send to #belongs_to
75
+ scope_to :portal, class_name: 'Portal'
76
+ scope_to :listing, class_name: 'Listing'
77
+ validates_uniqueness_in_scope :value
78
+ end
79
+
80
+ Portal.current = 1
81
+ Listing.find(1).to_sql
82
+ # => SELECT "listings".* FROM "listings" WHERE "portal_id" = 1 AND "id" = 1
83
+
84
+ Listing.current = 1
85
+ Communication.find(1).to_sql
86
+ # => SELECT "communications".* FROM "communications" WHERE "portal_id" = 1 AND "listing_id" = 1 AND "id" = 1
87
+ ```
88
+
89
+ `scope_to :portal` does 4 things:
90
+
91
+ 1. it adds `belongs_to :portal`.
92
+
93
+ 2. it adds `validates :portal, presence: true`.
94
+
95
+ 3. it adds `default_scope { where(portal_id: Portal.current) if Portal.current }`.
96
+
97
+ 4. it overrides `#portal` so that it doesn't touch the database if `portal_id` in that record is the same as `Portal.current_id`.
98
+
99
+ `validates :value, uniqueness: true` will validates uniqueness against the whole table. `validates_uniqueness_in_scope` validates uniqueness with the scopes you passed in `scope_to`.
100
+
101
+ ## Rails
102
+
103
+ Because `#current` is using thread variable, it's advisable to set to `nil` after processing controller action. This can be easily achievable by using `around_filter` and `#with` inside `application_controller.rb`. Or, you can do it manually by using `#current=`.
104
+
105
+ ```ruby
106
+ class ApplicationController < ActionController::Base
107
+ around_filter :route_domain
108
+
109
+ protected
110
+ def route_domain(&block)
111
+ Portal.with(current_portal, &block)
112
+ end
113
+
114
+ def current_portal
115
+ @current_portal ||= Portal.find_by_domain_name(request.host)
116
+ end
117
+ end
118
+ ```
119
+
120
+ ## Indexes
121
+
122
+ ```ruby
123
+ add_index :listings, :portal_id
124
+ add_index :communications, [:portal_id, :listing_id]
125
+ ```
126
+
127
+ ## RSpec
128
+
129
+ In spec_helper.rb, you'll need to require the matchers:
130
+
131
+ ```ruby
132
+ require "tenancy/matchers"
133
+ ```
134
+
135
+ Example:
136
+
137
+ ```ruby
138
+ describe Listing do
139
+ it { should have_scope_to(:portal) }
140
+ it { should have_scope_to(:portal).class_name('Portal') }
141
+ end
142
+ ```
143
+
144
+ ## Authors
145
+
146
+ * [Chamnap Chhorn](https://github.com/chamnap)
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,7 @@
1
+ require "tenancy/version"
2
+ require "active_support/concern"
3
+
4
+ module Tenancy
5
+ autoload :Resource, 'tenancy/resource'
6
+ autoload :ResourceScope, 'tenancy/resource_scope'
7
+ end
@@ -0,0 +1,62 @@
1
+ require 'shoulda-matchers'
2
+
3
+ module Tenancy
4
+ module Shoulda
5
+ module Matchers
6
+ def have_scope_to(name)
7
+ HaveScopeToMatcher.new(name)
8
+ end
9
+
10
+ class HaveScopeToMatcher
11
+ def initialize(scope_name)
12
+ @scope_name = scope_name
13
+ @presence_matcher = ::Shoulda::Matchers::ActiveModel::ValidatePresenceOfMatcher.new(@scope_name)
14
+ @belong_to_matcher = ::Shoulda::Matchers::ActiveRecord::AssociationMatcher.new(:belongs_to, @scope_name)
15
+ end
16
+
17
+ def matches?(subject)
18
+ @presence_matches = @presence_matcher.matches?(subject)
19
+ @belong_to_matches = @belong_to_matcher.matches?(subject)
20
+ @default_scope_matches = default_scope_matches?(subject)
21
+ end
22
+
23
+ def failure_message
24
+ @presence_matcher.failure_message unless @presence_matches
25
+ @belong_to_matcher.failure_message unless @belong_to_matches
26
+ "Expected to have default_scope on :#{@scope_name}" unless @default_scope_matches
27
+ end
28
+
29
+ def description
30
+ "require to have scope_to :#{@scope_name}"
31
+ end
32
+
33
+ private
34
+ def default_scope_matches?(subject)
35
+ actual_class = subject.class
36
+ reflection = actual_class.reflect_on_association(@scope_name.to_sym)
37
+ scoped_class = reflection.class_name.constantize
38
+
39
+ if scoped_class.current_id
40
+ actual_class.scoped.to_sql.include? %Q{#{actual_class.quoted_table_name}.#{scoped_class.connection.quote_column_name(reflection.foreign_key)} = #{scoped_class.connection.quote(scoped_class.current_id)}}
41
+ else
42
+ true
43
+ end
44
+ end
45
+
46
+ def method_missing(method, *args, &block)
47
+ if @belong_to_matcher.respond_to?(method)
48
+ @belong_to_matcher.send(method, *args, &block)
49
+ else
50
+ super
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+
59
+ require 'rspec/core'
60
+ RSpec.configure do |config|
61
+ config.include Tenancy::Shoulda::Matchers
62
+ end
@@ -0,0 +1,42 @@
1
+ module Tenancy
2
+ module Resource
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+
7
+ def current=(value)
8
+ tenant = case value
9
+ when self
10
+ value
11
+ when nil
12
+ nil
13
+ else
14
+ find(value)
15
+ end
16
+
17
+ Thread.current["#{name}.current"] = tenant
18
+ end
19
+
20
+ def current
21
+ Thread.current["#{name}.current"]
22
+ end
23
+
24
+ def current_id
25
+ current.try(:id)
26
+ end
27
+
28
+ def with(tenant, &block)
29
+ raise ArgumentError, "block required" if block.nil?
30
+
31
+ begin
32
+ old = self.current
33
+ self.current = tenant
34
+
35
+ block.call
36
+ ensure
37
+ self.current = old
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,52 @@
1
+ module Tenancy
2
+ module ResourceScope
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ attr_reader :scope_fields
7
+
8
+ def scope_fields
9
+ @scope_fields ||= []
10
+ end
11
+
12
+ def scope_to(*resources)
13
+ options = resources.extract_options!.dup
14
+ raise ArgumentError, 'options should be blank if there are multiple resources' if resources.count > 1 and options.present?
15
+
16
+ resources.each do |resource|
17
+ resource = resource.to_sym
18
+ resource_class_name ||= (options[:class_name].to_s.presence || resource.to_s).classify
19
+ resource_class = resource_class_name.constantize
20
+ association_name = self.to_s.downcase.pluralize.to_sym
21
+
22
+ # validates and belongs_to
23
+ validates resource, presence: true
24
+ belongs_to resource, options
25
+
26
+ # default_scope
27
+ resource_foreign_key = reflect_on_association(resource).foreign_key
28
+ scope_fields << resource_foreign_key
29
+ default_scope { where(:"#{resource_foreign_key}" => resource_class.current_id) if resource_class.current_id }
30
+
31
+ # override to return current resource instance
32
+ # so that it doesn't touch db
33
+ define_method(resource) do |reload=false|
34
+ return super(reload) if reload
35
+ return resource_class.current if send(resource_foreign_key) == resource_class.current_id
36
+ super(reload)
37
+ end
38
+ end
39
+ end
40
+
41
+ def validates_uniqueness_in_scope(fields, args={})
42
+ if args[:scope]
43
+ args[:scope] = Array.wrap(args[:scope]) << scope_fields
44
+ else
45
+ args[:scope] = scope_fields
46
+ end
47
+
48
+ validates_uniqueness_of(fields, args)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,3 @@
1
+ module Tenancy
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,107 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Tenancy::ResourceScope" do
4
+ let(:camyp) { Portal.create(domain_name: 'yp.com.kh') }
5
+ let(:panpages) { Portal.create(domain_name: 'panpages.com') }
6
+ let(:listing) { Listing.create(name: 'Listing 1', portal_id: camyp.id) }
7
+
8
+ after(:all) do
9
+ Portal.delete_all
10
+ end
11
+
12
+ describe Listing do
13
+ it { should belong_to(:portal) }
14
+
15
+ it { should validate_presence_of(:portal) }
16
+
17
+ it { should validate_uniqueness_of(:name).scoped_to(:portal_id).case_insensitive }
18
+
19
+ it "have default_scope with :portal_id field" do
20
+ Portal.current = camyp
21
+
22
+ Listing.scoped.to_sql.should == Listing.where(portal_id: Portal.current_id).to_sql
23
+ end
24
+
25
+ it "doesn't have default_scope when it doesn't have current portal" do
26
+ Portal.current = nil
27
+
28
+ Listing.scoped.to_sql.should == "SELECT \"listings\".* FROM \"listings\" "
29
+ end
30
+ end
31
+
32
+ describe Communication do
33
+ it { should belong_to(:portal) }
34
+
35
+ it { should validate_presence_of(:portal) }
36
+
37
+ it { should belong_to(:listing) }
38
+
39
+ it { should validate_presence_of(:listing) }
40
+
41
+ it { should validate_uniqueness_of(:value).scoped_to(:portal_id, :listing_id) }
42
+
43
+ it "have default_scope with :portal_id field" do
44
+ Portal.current = camyp
45
+ Listing.current = listing
46
+
47
+ Communication.scoped.to_sql.should == Communication.where(portal_id: Portal.current_id, listing_id: Listing.current_id).to_sql
48
+ end
49
+
50
+ it "doesn't have default_scope when it doesn't have current portal and listing" do
51
+ Portal.current = nil
52
+ Listing.current = nil
53
+
54
+ Communication.scoped.to_sql.should == "SELECT \"communications\".* FROM \"communications\" "
55
+ end
56
+ end
57
+
58
+ describe ExtraCommunication do
59
+ it { should belong_to(:portal) }
60
+
61
+ it { should belong_to(:listing) }
62
+
63
+ it "raise exception when passing two resources and options" do
64
+ expect { ExtraCommunication.scope_to(:portal, :listing, class_name: 'Listing') }.to raise_error(ArgumentError)
65
+ end
66
+
67
+ it "uses the correct scope" do
68
+ listing2 = Listing.create(name: 'Name 2', portal: camyp)
69
+
70
+ Portal.current = camyp
71
+ Listing.current = listing2
72
+
73
+ extra_communication = ExtraCommunication.new
74
+ extra_communication.listing_id.should == listing2.id
75
+ extra_communication.portal_id.should == camyp.id
76
+ end
77
+ end
78
+
79
+ describe "belongs_to method override" do
80
+ before(:each) { Portal.current = camyp }
81
+ after(:each) { Portal.current = nil }
82
+
83
+ it "reload belongs_to when passes true" do
84
+ listing.portal.domain_name = 'abc.com'
85
+ listing.portal(true).object_id.should_not == Portal.current.object_id
86
+ end
87
+
88
+ it "doesn't reload belongs_to" do
89
+ listing.portal.domain_name = 'abc.com'
90
+ listing.portal.object_id.should == Portal.current.object_id
91
+ end
92
+
93
+ it "returns different object" do
94
+ listing.portal_id = panpages.id
95
+ listing.portal.object_id.should_not == Portal.current.object_id
96
+ end
97
+
98
+ it "doesn't touch db" do
99
+ current_listing = listing
100
+
101
+ Portal.establish_connection(adapter: "sqlite3", database: "spec/invalid.sqlite3")
102
+ current_listing.portal.object_id.should == Portal.current.object_id
103
+
104
+ Portal.establish_connection(ActiveRecord::Base.connection_config)
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Tenancy::Resource" do
4
+ before(:all) do
5
+ @camyp = Portal.create(id: 1, domain_name: 'yp.com.kh')
6
+ @panpage = Portal.create(id: 2, domain_name: 'panpages.my')
7
+ @yoolk = Portal.create(id: 3, domain_name: 'yoolk.com')
8
+ end
9
+
10
+ after(:all) do
11
+ Portal.delete_all
12
+ end
13
+
14
+ before(:each) { Thread.current['Portal.current'] = nil }
15
+
16
+ it "set current with instance" do
17
+ Portal.current = @camyp
18
+
19
+ Portal.current.should == @camyp
20
+ Thread.current['Portal.current'].should == @camyp
21
+ end
22
+
23
+ it "set current with id" do
24
+ Portal.current = @panpage.id
25
+
26
+ Portal.current.should == @panpage
27
+ Thread.current['Portal.current'].should == @panpage
28
+ end
29
+
30
+ it "set current with nil" do
31
+ Portal.current = @panpage
32
+ Portal.current = nil
33
+
34
+ Portal.current.should == nil
35
+ Thread.current['Portal.current'].should == nil
36
+ end
37
+
38
+ it "#current_id" do
39
+ Portal.current = @yoolk
40
+
41
+ Portal.current_id.should == @yoolk.id
42
+ end
43
+
44
+ it "#with with block" do
45
+ Portal.current.should == nil
46
+
47
+ Portal.with(@yoolk) do
48
+ Portal.current.should == @yoolk
49
+ end
50
+
51
+ Portal.current.should == nil
52
+ end
53
+
54
+ it "#with without block" do
55
+ expect { Portal.with(@yoolk) }.to raise_error(ArgumentError)
56
+ end
57
+ end
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+ require 'tenancy/matchers'
3
+
4
+ describe ExtraCommunication do
5
+ let(:camyp) { Portal.create(domain_name: 'yp.com.kh') }
6
+ before { Portal.current = camyp }
7
+
8
+ it { should have_scope_to(:portal) }
9
+ it { should have_scope_to(:portal).class_name('Portal') }
10
+ it { should have_scope_to(:listing) }
11
+ it { should have_scope_to(:listing).class_name('Listing') }
12
+ end
@@ -0,0 +1,14 @@
1
+ require 'tenancy'
2
+
3
+ # active_record
4
+ load File.dirname(__FILE__) + '/support/schema.rb'
5
+ load File.dirname(__FILE__) + '/support/models.rb'
6
+
7
+ require 'pry'
8
+ require 'shoulda-matchers'
9
+
10
+ RSpec.configure do |config|
11
+ config.filter_run focus: true
12
+ config.run_all_when_everything_filtered = true
13
+ config.treat_symbols_as_metadata_keys_with_true_values = true
14
+ end
@@ -0,0 +1,25 @@
1
+ class Portal < ActiveRecord::Base
2
+ include Tenancy::Resource
3
+ end
4
+
5
+ class Listing < ActiveRecord::Base
6
+ include Tenancy::Resource
7
+ include Tenancy::ResourceScope
8
+
9
+ scope_to :portal
10
+ validates_uniqueness_in_scope :name, case_sensitive: false
11
+ end
12
+
13
+ class Communication < ActiveRecord::Base
14
+ include Tenancy::ResourceScope
15
+
16
+ scope_to :portal, :listing
17
+ validates_uniqueness_in_scope :value
18
+ end
19
+
20
+ class ExtraCommunication < ActiveRecord::Base
21
+ include Tenancy::ResourceScope
22
+
23
+ scope_to :portal, class_name: 'Portal'
24
+ scope_to :listing, class_name: 'Listing'
25
+ end
@@ -0,0 +1,36 @@
1
+ require 'active_record'
2
+ require 'logger'
3
+
4
+ ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: 'spec/test.sqlite3')
5
+ ActiveRecord::Migration.verbose = false
6
+
7
+ ActiveRecord::Schema.define do
8
+ self.verbose = false
9
+
10
+ create_table :portals, :force => true do |t|
11
+ t.string :domain_name
12
+ t.timestamps
13
+ end
14
+
15
+ create_table :listings, :force => true do |t|
16
+ t.string :name
17
+ t.references :portal
18
+ t.timestamps
19
+ end
20
+
21
+ create_table :communications, :force => true do |t|
22
+ t.string :label
23
+ t.string :value
24
+ t.references :listing
25
+ t.references :portal
26
+ t.timestamps
27
+ end
28
+
29
+ create_table :extra_communications, :force => true do |t|
30
+ t.string :label
31
+ t.string :value
32
+ t.references :listing
33
+ t.references :portal
34
+ t.timestamps
35
+ end
36
+ end
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'tenancy/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "tenancy"
8
+ spec.version = Tenancy::VERSION
9
+ spec.authors = ["chamnap"]
10
+ spec.email = ["chamnapchhorn@gmail.com"]
11
+ spec.description = %q{A simple multi-tenancy with activerecord through scoping}
12
+ spec.summary = %q{A simple multi-tenancy with activerecord through scoping}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "rspec", "~> 2.12.0"
22
+ spec.add_development_dependency "shoulda", "~> 3.5.0"
23
+ spec.add_development_dependency "pry", "~> 0.9.12"
24
+ spec.add_development_dependency "sqlite3", "~> 1.3.7"
25
+ spec.add_development_dependency "rake"
26
+
27
+ spec.add_dependency "activerecord", "~> 3.2.13"
28
+ end
metadata ADDED
@@ -0,0 +1,153 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tenancy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - chamnap
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-11-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ~>
17
+ - !ruby/object:Gem::Version
18
+ version: 2.12.0
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ~>
22
+ - !ruby/object:Gem::Version
23
+ version: 2.12.0
24
+ type: :development
25
+ prerelease: false
26
+ name: rspec
27
+ - !ruby/object:Gem::Dependency
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: 3.5.0
33
+ version_requirements: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 3.5.0
38
+ type: :development
39
+ prerelease: false
40
+ name: shoulda
41
+ - !ruby/object:Gem::Dependency
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ~>
45
+ - !ruby/object:Gem::Version
46
+ version: 0.9.12
47
+ version_requirements: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ~>
50
+ - !ruby/object:Gem::Version
51
+ version: 0.9.12
52
+ type: :development
53
+ prerelease: false
54
+ name: pry
55
+ - !ruby/object:Gem::Dependency
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ~>
59
+ - !ruby/object:Gem::Version
60
+ version: 1.3.7
61
+ version_requirements: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ~>
64
+ - !ruby/object:Gem::Version
65
+ version: 1.3.7
66
+ type: :development
67
+ prerelease: false
68
+ name: sqlite3
69
+ - !ruby/object:Gem::Dependency
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ! '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ version_requirements: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ! '>='
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ type: :development
81
+ prerelease: false
82
+ name: rake
83
+ - !ruby/object:Gem::Dependency
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ~>
87
+ - !ruby/object:Gem::Version
88
+ version: 3.2.13
89
+ version_requirements: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 3.2.13
94
+ type: :runtime
95
+ prerelease: false
96
+ name: activerecord
97
+ description: A simple multi-tenancy with activerecord through scoping
98
+ email:
99
+ - chamnapchhorn@gmail.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - .gitignore
105
+ - .rspec
106
+ - .rvmrc
107
+ - Gemfile
108
+ - LICENSE.txt
109
+ - README.md
110
+ - Rakefile
111
+ - lib/tenancy.rb
112
+ - lib/tenancy/matchers.rb
113
+ - lib/tenancy/resource.rb
114
+ - lib/tenancy/resource_scope.rb
115
+ - lib/tenancy/version.rb
116
+ - spec/lib/resource_scope_spec.rb
117
+ - spec/lib/resource_spec.rb
118
+ - spec/lib/shoulda_matchers_spec.rb
119
+ - spec/spec_helper.rb
120
+ - spec/support/models.rb
121
+ - spec/support/schema.rb
122
+ - tenancy.gemspec
123
+ homepage: ''
124
+ licenses:
125
+ - MIT
126
+ metadata: {}
127
+ post_install_message:
128
+ rdoc_options: []
129
+ require_paths:
130
+ - lib
131
+ required_ruby_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ! '>='
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ required_rubygems_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ! '>='
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ requirements: []
142
+ rubyforge_project:
143
+ rubygems_version: 2.0.7
144
+ signing_key:
145
+ specification_version: 4
146
+ summary: A simple multi-tenancy with activerecord through scoping
147
+ test_files:
148
+ - spec/lib/resource_scope_spec.rb
149
+ - spec/lib/resource_spec.rb
150
+ - spec/lib/shoulda_matchers_spec.rb
151
+ - spec/spec_helper.rb
152
+ - spec/support/models.rb
153
+ - spec/support/schema.rb