tenancy 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.
@@ -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