tenancy 0.2.0 → 1.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 960f4ccd4c80911a5f803c6865f782a468e166e7
4
- data.tar.gz: 387cf8e4f3f74bc79ed2f28402c4c33b641788b3
3
+ metadata.gz: fe538f2809f42fdca981b88b3bbf9bb5ca19bc24
4
+ data.tar.gz: e65ce5cffe075fe7be07dd8d3525194d791e0199
5
5
  SHA512:
6
- metadata.gz: 6a41030d55da3b492c2325f5b61c8feb4cf976793d3388d10eeeace10d354d4ad7fa6267c5c7f6084e62734468d0d05981042a40843cac3dfc0b8de6a4780e13
7
- data.tar.gz: 943e3ccd96321fa385a5e0a689c4ff03b43cb5eaf445f70337e166484e5dd2ab7f6ae5a9f6fd285337f7dc0c43d86ed7b15d76de36c47fefa1d2cbad7b469404
6
+ metadata.gz: a8d0631db5f4f03b05e219d32f46e4b0f5eb57f60a5603331bff2eeba7c22590bb02967c65042bcbbcd40d819fd152f52a9e0b033ce1c6291e068f50d1156e59
7
+ data.tar.gz: d1425e735c6d9c125a749d8e41b52acd6f0b96351c7ee3bd668d5d393bb0842f05b10603d4b189dc3f2290253e0687955d127fc40b4fbdeca7898e3e02ec01d7
data/.coveralls.yml ADDED
@@ -0,0 +1,2 @@
1
+ service_name: travis-ci
2
+ repo_token: rKGLArZVmQzAMDN87mkpPahF5zlXDNbLL
data/.rspec CHANGED
@@ -1 +1,2 @@
1
- --colour
1
+ --colour
2
+ --profile
data/.rvmrc CHANGED
@@ -4,7 +4,7 @@
4
4
  # development environment upon cd'ing into the directory
5
5
 
6
6
  # First we specify our desired <ruby>[@<gemset>], the @gemset name is optional.
7
- environment_id="ruby-2.0.0-p247@tenancy"
7
+ environment_id="ruby-2.1.0@tenancy"
8
8
 
9
9
  #
10
10
  # Uncomment the following lines if you want to verify rvm version per project
data/.travis.yml CHANGED
@@ -1,15 +1,19 @@
1
+ services: mongodb
1
2
  language: ruby
2
3
  script: "bundle exec rake spec"
3
4
  rvm:
4
5
  - 1.9.3
5
6
  - 2.0.0
7
+ - 2.1.0
6
8
  env:
7
9
  - CODECLIMATE_REPO_TOKEN=891d362268d07d6ff0f5534f92252b6195f6be8795054d3627643eb6314a8c9e
8
10
  gemfile:
9
11
  - gemfiles/active_record_32.gemfile
10
12
  - gemfiles/active_record_40.gemfile
13
+ - gemfiles/mongoid_3.gemfile
14
+ - gemfiles/mongoid_4.gemfile
11
15
  notifications:
12
16
  email: false
13
17
  addons:
14
18
  code_climate:
15
- repo_token: 891d362268d07d6ff0f5534f92252b6195f6be8795054d3627643eb6314a8c9e
19
+ repo_token: 891d362268d07d6ff0f5534f92252b6195f6be8795054d3627643eb6314a8c9e
data/CHANGELOG.md ADDED
@@ -0,0 +1,24 @@
1
+ # Overview
2
+
3
+ For instructions on upgrading to newer versions, visit
4
+ [mongoid.org](http://mongoid.org/en/mongoid/docs/upgrading.html).
5
+
6
+ ## 1.0.0
7
+
8
+ ### Major Changes (Backwards Incompatible)
9
+
10
+ * Rename `#with` and `#use` to `#with_tenant` and `#use_tenant` because it conflicts with mongoid.
11
+ * Replace `#without_scope` to `#tenant_scope`.
12
+ * Support Mongoid 3/4.
13
+
14
+ ## 0.2.0
15
+
16
+ * Add [request_store](https://github.com/steveklabnik/request_store) as dependency.
17
+ * Add `#without_scope`.
18
+ * Support ActiveRecord 4.
19
+
20
+ ## 0.1.0
21
+
22
+ * First Release
23
+ * Support ActiveRecord 3.
24
+ * Add `tenany/matchers`.
data/Gemfile CHANGED
@@ -1,6 +1,16 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- # Specify your gem's dependencies in tenancy.gemspec
4
- gemspec
3
+ gem "codeclimate-test-reporter", group: :test, require: nil
4
+ gem "coveralls", require: false
5
+ gem "activerecord", "~> 4.0.2", :require => "active_record"
6
+ gem "mongoid", "~> 4.0.0.beta1"
7
+ gem "rspec"
8
+ gem "mongoid-rspec"
9
+ gem "database_cleaner"
10
+ gem "shoulda-matchers"
11
+ gem "pry"
12
+ gem "sqlite3"
13
+ gem "rake"
5
14
 
6
- gem "codeclimate-test-reporter", group: :test, require: nil
15
+ # Specify your gem's dependencies in tenancy.gemspec
16
+ gemspec
data/README.md CHANGED
@@ -1,13 +1,15 @@
1
- # Tenancy [![Build Status](https://travis-ci.org/yoolk/tenancy.png?branch=master)](https://travis-ci.org/yoolk/tenancy) [![Code Climate](https://codeclimate.com/repos/527a1d45f3ea005378005fdb/badges/cfbc9ff8993d02e13b9d/gpa.png)](https://codeclimate.com/repos/527a1d45f3ea005378005fdb/feed)
1
+ # Tenancy [![Gem Version](https://badge.fury.io/rb/tenancy.png)](http://badge.fury.io/rb/tenancy) [![Build Status](https://travis-ci.org/yoolk/tenancy.png?branch=master)](https://travis-ci.org/yoolk/tenancy) [![Dependency Status](https://gemnasium.com/yoolk/tenancy.png)](https://gemnasium.com/yoolk/tenancy) [![Coverage Status](https://coveralls.io/repos/yoolk/tenancy/badge.png?branch=master)](https://coveralls.io/r/yoolk/tenancy?branch=master)
2
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).
3
+ **Tenancy** is a simple gem that provides multi-tenancy support on activerecord/mongoid (3/4) 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
+ This `README.md` file is for the latest version, v1.0.0. For the previous version, check out this [README.md](https://github.com/yoolk/tenancy/blob/v0.2.0/README.md). Please, see the [CHANGELOG.md](https://github.com/yoolk/tenancy/blob/master/CHANGELOG.md#100) to do an upgrade.
4
6
 
5
7
  ## Installation
6
8
 
7
9
  Add this line to your application's Gemfile:
8
10
 
9
11
  ```ruby
10
- gem 'tenancy'
12
+ gem "tenancy"
11
13
  ```
12
14
 
13
15
  And then execute:
@@ -18,7 +20,7 @@ $ bundle
18
20
 
19
21
  ## Usage
20
22
 
21
- This gem provides two modules: `Tenancy::Resource` and `Tenancy::ResourceScope`.
23
+ This gem provides two modules: `Tenancy::Resource` and `Tenancy::ResourceScope`. Include them into your activerecord/mongoid models.
22
24
 
23
25
  ### Tenancy::Resource
24
26
 
@@ -43,7 +45,7 @@ Portal.current
43
45
  # => <Portal id: 1, domain_name: 'yp.com.kh'>
44
46
 
45
47
  # scope with this portal
46
- Portal.with(camyp) do
48
+ Portal.with_tenant(camyp) do
47
49
  # Do something here with this portal
48
50
  end
49
51
  ```
@@ -79,23 +81,23 @@ class ExtraCommunication < ActiveRecord::Base
79
81
  end
80
82
 
81
83
  > Portal.current = 1
82
- > Listing.find(1).to_sql
84
+ > Listing.find(1)
83
85
  # => SELECT "listings".* FROM "listings" WHERE "portal_id" = 1 AND "id" = 1
84
86
 
85
87
  > Listing.current = 1
86
- > Communication.find(1).to_sql
88
+ > Communication.find(1)
87
89
  # => SELECT "communications".* FROM "communications" WHERE "portal_id" = 1 AND "listing_id" = 1 AND "is_active" = true AND "id" = 1
88
90
 
89
- # unscoped :current_portal, :current_listing
90
- > Communication.without_scope(:portal).find(1)
91
- # => SELECT "communications".* FROM "communications" WHERE "listing_id" = 1 AND "is_active" = true AND "id" = 1
92
- > Communication.without_scope(:listing).find(1)
91
+ # include/exclude tenant_scope :current_portal, :current_listing
92
+ > Communication.tenant_scope(:portal).find(1)
93
93
  # => SELECT "communications".* FROM "communications" WHERE "portal_id" = 1 AND "is_active" = true AND "id" = 1
94
- > Communication.without_scope(:portal, :listing).find(1)
94
+ > Communication.tenant_scope(:listing).find(1)
95
+ # => SELECT "communications".* FROM "communications" WHERE "listing_id" = 1 AND "is_active" = true AND "id" = 1
96
+ > Communication.tenant_scope(nil).find(1)
95
97
  # => SELECT "communications".* FROM "communications" WHERE "is_active" = true AND "id" = 1
96
98
  ```
97
99
 
98
- `scope_to :portal` does 4 things:
100
+ `scope_to :portal` does these things:
99
101
 
100
102
  1. it adds `belongs_to :portal`.
101
103
 
@@ -105,42 +107,26 @@ end
105
107
 
106
108
  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`.
107
109
 
108
- `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`.
109
-
110
- ## Rails
111
-
112
- 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=`.
113
-
114
- ```ruby
115
- class ApplicationController < ActionController::Base
116
- around_filter :route_domain
117
-
118
- protected
110
+ 5. it overrides `#portal_id` so that it returns `Portal.current_id`. (mongoid 3 only)
119
111
 
120
- def route_domain(&block)
121
- Portal.with(current_portal, &block)
122
- end
112
+ 6. it overrides `#shard_key_selector` so that every update/delete query includes current tenant_id. (mongoid 3/4)
123
113
 
124
- def current_portal
125
- @current_portal ||= Portal.find_by_domain_name(request.host)
126
- end
127
- end
128
- ```
114
+ `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`.
129
115
 
130
- From version 0.2.0 up, you don't need to use `around_filter` because this gem add [request_store](https://github.com/steveklabnik/request_store) as dependency. It will clear threaded variables inside middleware on every request. You can just use `before_filter` to set the current tenant.
116
+ ## Rails
131
117
 
132
118
  ```ruby
133
119
  class ApplicationController < ActionController::Base
134
- before_filter :set_current_portal
120
+ before_action :set_current_portal
135
121
 
136
122
  protected
137
123
 
138
124
  def current_portal
139
- @current_portal
125
+ Portal.current
140
126
  end
141
127
 
142
128
  def set_current_portal
143
- @current_portal = Portal.find_by_domain_name(request.host)
129
+ Portal.current = Portal.find_by_domain_name(request.host)
144
130
  end
145
131
  end
146
132
  ```
@@ -175,6 +161,40 @@ describe Listing do
175
161
  end
176
162
  ```
177
163
 
164
+ ```ruby
165
+ describe Mongo::Listing do
166
+ it { should have_scope_to(:portal) }
167
+ it { should have_scope_to(:portal).of_type(Mongo::Portal) }
168
+ end
169
+ ```
170
+
171
+ I have this rspec configuration in my rails 4 apps:
172
+
173
+ ```ruby
174
+ RSpec.configure do |config|
175
+ config.before(:suite) do
176
+ DatabaseCleaner[:active_record].strategy = :transaction
177
+ DatabaseCleaner[:mongoid].strategy = :truncation
178
+
179
+ DatabaseCleaner[:active_record].clean_with(:truncation)
180
+ DatabaseCleaner[:mongoid].clean_with(:truncation)
181
+ end
182
+
183
+ config.around(:each) do |example|
184
+ DatabaseCleaner[:active_record].start
185
+ DatabaseCleaner[:mongoid].start
186
+
187
+ current_portal = FactoryGirl.create(:portal, domain_name: "yellowpages-cambodia.dev")
188
+ Yoolk::Portal.use(current_portal) do
189
+ example.run
190
+ end
191
+
192
+ DatabaseCleaner[:active_record].clean
193
+ DatabaseCleaner[:mongoid].clean if example.metadata[:mongodb]
194
+ end
195
+ end
196
+ ```
197
+
178
198
  ## Authors
179
199
 
180
- * [Chamnap Chhorn](https://github.com/chamnap)
200
+ * [Chamnap Chhorn](https://github.com/chamnap)
data/Rakefile CHANGED
@@ -10,19 +10,19 @@ end
10
10
  task :default => "spec:all"
11
11
 
12
12
  namespace :spec do
13
- %w(active_record_40 active_record_32).each do |gemfile|
13
+ %w(active_record_40 active_record_32 mongoid_4 mongoid_3).each do |gemfile|
14
14
  desc "Run Tests against #{gemfile}"
15
15
  task gemfile do
16
16
  sh "BUNDLE_GEMFILE='gemfiles/#{gemfile}.gemfile' bundle --quiet"
17
- sh "BUNDLE_GEMFILE='gemfiles/#{gemfile}.gemfile' bundle exec rake spec"
17
+ sh "BUNDLE_GEMFILE='gemfiles/#{gemfile}.gemfile' bundle exec rspec"
18
18
  end
19
19
  end
20
20
 
21
21
  desc "Run Tests against active_record versions"
22
22
  task :all do
23
- %w(active_record_40 active_record_32).each do |gemfile|
23
+ %w(active_record_40 active_record_32 mongoid_4 mongoid_3).each do |gemfile|
24
24
  sh "BUNDLE_GEMFILE='gemfiles/#{gemfile}.gemfile' bundle --quiet"
25
- sh "BUNDLE_GEMFILE='gemfiles/#{gemfile}.gemfile' bundle exec rake spec"
25
+ sh "BUNDLE_GEMFILE='gemfiles/#{gemfile}.gemfile' bundle exec rspec"
26
26
  end
27
27
  end
28
28
  end
@@ -1,7 +1,13 @@
1
- source 'https://rubygems.org'
2
-
3
- gem 'activerecord', '~> 3.2.13', :require => 'active_record'
1
+ source "https://rubygems.org"
4
2
 
3
+ gem "activerecord", "~> 3.2.16", :require => "active_record"
4
+ gem "coveralls", require: false
5
5
  gem "codeclimate-test-reporter", group: :test, require: nil
6
+ gem "rspec"
7
+ gem "database_cleaner"
8
+ gem "shoulda-matchers"
9
+ gem "pry"
10
+ gem "sqlite3"
11
+ gem "rake"
6
12
 
7
- gemspec :path => '../'
13
+ gemspec :path => "../"
@@ -1,7 +1,13 @@
1
- source 'https://rubygems.org'
2
-
3
- gem 'activerecord', '~> 4.0.1', :require => 'active_record'
1
+ source "https://rubygems.org"
4
2
 
3
+ gem "activerecord", "~> 4.0.2", :require => "active_record"
4
+ gem "coveralls", require: false
5
5
  gem "codeclimate-test-reporter", group: :test, require: nil
6
+ gem "rspec"
7
+ gem "database_cleaner"
8
+ gem "shoulda-matchers"
9
+ gem "pry"
10
+ gem "sqlite3"
11
+ gem "rake"
6
12
 
7
- gemspec :path => '../'
13
+ gemspec :path => "../"
@@ -0,0 +1,12 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "mongoid", "~> 3.1.6"
4
+ gem "coveralls", require: false
5
+ gem "codeclimate-test-reporter", group: :test, require: nil
6
+ gem "rspec"
7
+ gem "database_cleaner"
8
+ gem "mongoid-rspec"
9
+ gem "pry"
10
+ gem "rake"
11
+
12
+ gemspec :path => "../"
@@ -0,0 +1,12 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "mongoid", "~> 4.0.0.beta1"
4
+ gem "coveralls", require: false
5
+ gem "codeclimate-test-reporter", group: :test, require: nil
6
+ gem "rspec"
7
+ gem "database_cleaner"
8
+ gem "mongoid-rspec"
9
+ gem "pry"
10
+ gem "rake"
11
+
12
+ gemspec :path => "../"
data/lib/tenancy.rb CHANGED
@@ -3,6 +3,8 @@ require "active_support/concern"
3
3
  require "request_store"
4
4
 
5
5
  module Tenancy
6
- autoload :Resource, 'tenancy/resource'
7
- autoload :ResourceScope, 'tenancy/resource_scope'
6
+ autoload :Resource, "tenancy/resource"
7
+ autoload :ResourceScope, "tenancy/resource_scope"
8
+ autoload :Scoping, "tenancy/scoping"
9
+ autoload :Tenant, "tenancy/tenant"
8
10
  end
@@ -1,4 +1,5 @@
1
- require 'shoulda-matchers'
1
+ require "shoulda-matchers" if defined?(ActiveRecord)
2
+ require "mongoid-rspec" if defined?(Mongoid)
2
3
 
3
4
  module Tenancy
4
5
  module Shoulda
@@ -12,16 +13,29 @@ module Tenancy
12
13
  end
13
14
 
14
15
  class HaveScopeToMatcher
16
+ attr_reader :scope_name
17
+
15
18
  def initialize(scope_name)
16
- @scope_name = scope_name
17
- @presence_matcher = ::Shoulda::Matchers::ActiveModel::ValidatePresenceOfMatcher.new(@scope_name)
18
- @belong_to_matcher = ::Shoulda::Matchers::ActiveRecord::AssociationMatcher.new(:belongs_to, @scope_name)
19
+ @scope_name = scope_name
20
+ if defined?(ActiveRecord)
21
+ @ar_presence_matcher = ::Shoulda::Matchers::ActiveModel::ValidatePresenceOfMatcher.new(@scope_name)
22
+ @ar_belong_to_matcher = ::Shoulda::Matchers::ActiveRecord::AssociationMatcher.new(:belongs_to, @scope_name)
23
+ end
24
+ if defined?(Mongoid)
25
+ @mid_presence_matcher = ::Mongoid::Matchers::Validations::HaveValidationMatcher.new(@scope_name, :presence)
26
+ @mid_belong_to_matcher = ::Mongoid::Matchers::Associations::HaveAssociationMatcher.new(@scope_name, ::Mongoid::Matchers::Associations::BELONGS_TO)
27
+ end
19
28
  end
20
29
 
21
30
  def matches?(subject)
22
- @presence_matches = @presence_matcher.matches?(subject)
23
- @belong_to_matches = @belong_to_matcher.matches?(subject)
24
- @default_scope_matches = default_scope_matches?(subject)
31
+ if defined?(ActiveRecord) && subject.class <= ::ActiveRecord::Base
32
+ @ar_presence_matcher.matches?(subject) &&
33
+ @ar_belong_to_matcher.matches?(subject) &&
34
+ ar_default_scope_matches?(subject)
35
+ elsif defined?(Mongoid) && subject.class <= ::Mongoid::Document
36
+ @mid_presence_matcher.matches?(subject) &&
37
+ @mid_belong_to_matcher.matches?(subject)
38
+ end
25
39
  end
26
40
 
27
41
  def failure_message
@@ -35,7 +49,8 @@ module Tenancy
35
49
  end
36
50
 
37
51
  private
38
- def default_scope_matches?(subject)
52
+
53
+ def ar_default_scope_matches?(subject)
39
54
  actual_class = subject.class
40
55
  reflection = actual_class.reflect_on_association(@scope_name.to_sym)
41
56
  scoped_class = reflection.class_name.constantize
@@ -48,8 +63,10 @@ module Tenancy
48
63
  end
49
64
 
50
65
  def method_missing(method, *args, &block)
51
- if @belong_to_matcher.respond_to?(method)
52
- @belong_to_matcher.send(method, *args, &block)
66
+ if @ar_belong_to_matcher && @ar_belong_to_matcher.respond_to?(method)
67
+ @ar_belong_to_matcher.send(method, *args, &block)
68
+ elsif @mid_belong_to_matcher && @mid_belong_to_matcher.respond_to?(method)
69
+ @mid_belong_to_matcher.send(method, *args, &block)
53
70
  else
54
71
  super
55
72
  end
@@ -77,7 +94,7 @@ module Tenancy
77
94
  end
78
95
 
79
96
 
80
- require 'rspec/core'
97
+ require "rspec/core"
81
98
  RSpec.configure do |config|
82
99
  config.include Tenancy::Shoulda::Matchers
83
- end
100
+ end
@@ -25,7 +25,7 @@ module Tenancy
25
25
  current.try(:id)
26
26
  end
27
27
 
28
- def with(tenant, &block)
28
+ def with_tenant(tenant, &block)
29
29
  raise ArgumentError, "block required" if block.nil?
30
30
 
31
31
  begin
@@ -37,7 +37,7 @@ module Tenancy
37
37
  self.current = old
38
38
  end
39
39
  end
40
- alias_method :use, :with
40
+ alias_method :use_tenant, :with_tenant
41
41
  end
42
42
  end
43
43
  end