cohabit 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4ee059cff9113f9b438d138c88d31a229b7732b9
4
+ data.tar.gz: ade1a69aca01dc0f1c82ace515771a459053f9d1
5
+ SHA512:
6
+ metadata.gz: ee9b05964894b01bd68cece139960de8234a73c3fbc673c4bb0a9ca0cee8add5c348135f804b846ed37aff1bc7c5671957e0369d36d7b0a7994a5cacecb81658
7
+ data.tar.gz: f83f0b91cc407b873384b396153720d48131586359949f49205c42c7a8ca231f9b4f1ead6203241658835eb412bdf32b10dca16b172ccf8296f7c64cdc72d20a
data/README.md CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  Cohabit adds comprehensive scoped multi-tenancy functionality to any application, simply set your options up in `config/cohabit.rb` using the DSL (inspired by capistrano).
4
4
 
5
- It adds:
5
+ This gem isn't really recommended for doing simple application wide scoping, for that I'd recommend https://github.com/wireframe/multitenant. Cohabit builds on what `multitenant` provides and allows you to define your own scoping strategies with the DSL for where more complexity is needed.
6
+
7
+ It provides (or it will):
6
8
 
7
9
  - Model scoping (duh)
8
10
  - Custom scoping strategies
@@ -11,15 +13,6 @@ It adds:
11
13
  - Rake task for importing single-tenanted databases into a multi-tenant one
12
14
  - Rake task for generating multi-tenanted scoped schema
13
15
 
14
- ## Todo
15
-
16
- Still a WIP. Need to:
17
-
18
- - Develop snippets to be included in strategies, i.e. scope_validations snippet (and remove that setting)
19
- - Should snippets just be nested strategies? wah, probably.
20
- - Work out how to integrate the url helper scopes as an option
21
- - Write the rake tasks
22
-
23
16
  ## Installation
24
17
 
25
18
  Add this line to your application's Gemfile:
@@ -36,7 +29,137 @@ Or install it yourself as:
36
29
 
37
30
  ## Usage
38
31
 
39
- TODO: Write usage instructions here
32
+ In its simplest form, using the basic scope (typical `belongs_to` assiciation scope):
33
+
34
+ # must have this line to use the included scopes
35
+ require 'basic'
36
+ scope [:foo, :bar], :basic
37
+
38
+ By default it assumes your tenant model is called tenant, if you wish to change this you can set it globally:
39
+
40
+ set :association, :organisation
41
+ scope [:foo, :bar], :basic
42
+
43
+ Or per scope with options:
44
+
45
+ scope [:foo, :bar], :basic, association: :organisation
46
+
47
+ Or you can specify options and other configuration settings in block form:
48
+
49
+ scope [:foo, :bar] do
50
+ use_strategy: :basic
51
+ set :association, :organisation
52
+ end
53
+
54
+ In your application, depending on how you determine the current tenant, you need to set `Cohabit.current_tenant`. If you're using subdomains, I would recommend writing some simple Rack middleware something like:
55
+
56
+ class TenantSetup
57
+ def initialize(app)
58
+ @app = app
59
+ end
60
+
61
+ def call(env)
62
+ @request = Rack::Request.new(env)
63
+ Cohabit.current_tenant = Tenant.find_by_subdomain!(get_subdomain)
64
+ @app.call(env)
65
+ end
66
+
67
+ private
68
+ def get_subdomain
69
+ # Check request host isn't an IP.
70
+ host = @request.host
71
+ return nil unless !(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
72
+ subdomain = host.split('.')[0..-3].first
73
+ return subdomain unless subdomain == "www"
74
+ return host.split('.')[0..-3][1]
75
+ end
76
+ end
77
+
78
+ Alternatively, write a `before_filter` in the `ApplicationController`.
79
+
80
+ ### Strategies
81
+
82
+ This part is a WIP, but you can define your own strategies to be used. The following is the :basic strategy that comes by default:
83
+
84
+ strategy :basic do
85
+ # simply gets evaluated in the models..
86
+ model_eval do |_scope|
87
+ # _scope var references the scope that uses the strategy,
88
+ # so to access settings, like :association, use
89
+ # _scope.settings[:association]. current_tenant is defined
90
+ # as Cohabit.current_tenant.
91
+
92
+ # add relationship
93
+ belongs_to _scope.settings[:association]
94
+
95
+ # get foreign key
96
+ reflection = reflect_on_association _scope.settings[:association]
97
+
98
+ # scope insertions
99
+ before_create Proc.new {|m|
100
+ return unless Cohabit.current_tenant
101
+ m.send "#{_scope.settings[:association]}=".to_sym, Cohabit.current_tenant
102
+ }
103
+
104
+ # scope selects
105
+ default_scope lambda {
106
+ where(reflection.foreign_key => Cohabit.current_tenant) if Cohabit.current_tenant
107
+ }
108
+ end
109
+ end
110
+
111
+ You can define additional global vars in the Cohabit namespace, in your strategies, e.g.:
112
+
113
+ strategy :test do
114
+ set :globals, [:current_view, :current_scope]
115
+ # ...
116
+ end
117
+
118
+ # application_controller.rb
119
+ before_filter :set_scope
120
+ def set_scope
121
+ Cohabit.current_scope = Cohabit.current_tenant.managed_clients
122
+ end
123
+
124
+ You can also nest strategies to DRY up your code a bit.
125
+
126
+ strategy :basic_tweaked do
127
+ include_strategy :basic
128
+ model_eval do |_scope|
129
+ # ...
130
+ end
131
+ end
132
+
133
+ ### Settings
134
+
135
+ Once I've implemented it, you'll be able to scope URL helpers, so for example if your tenant features in your URL like so:
136
+
137
+ # routes file
138
+ # ...
139
+ resources :tenants do
140
+ resources :posts
141
+ resources :foo
142
+ resources :bar
143
+ end
144
+ # ...
145
+
146
+ Giving you the paths `/tenant/1/posts/1` .. etc. You will be able to still call `posts_path(@post)`, and when the setting is enabled it will expand that internally to `tenants_posts_path(@post.tenant, @post)`.
147
+
148
+ ### Rake tasks
149
+
150
+ There are two rake tasks in the pipeline to make life a bit easier for anyone converting from multi-database architecture to a multi-tenant, single-database architecture:
151
+
152
+ 1. Migrate DB or create new DB schema based on the scopes in a cohabit configuration file
153
+ 2. Import a number of single-tenanted databases into the multi-tenanted equivalent
154
+
155
+ ## Todo
156
+
157
+ Still a WIP. Need to:
158
+
159
+ - Work out how to integrate the url helper scopes as an option
160
+ - Write the rake tasks
161
+ - Add custom `cohabit_unscoped` (or similar) class method to models which removes all Cohabit `default_scope`s, `before_create`s, validation scopes, etc for that chain. (Possible? hmf)
162
+ - Add a `conditions` option to `include_strategy`, like that of Rails routes perhaps
40
163
 
41
164
  ## Contributing
42
165
 
@@ -2,34 +2,30 @@ require 'cohabit/errors'
2
2
  require 'cohabit/configuration'
3
3
  require 'cohabit/strategy'
4
4
  require 'cohabit/scope'
5
+ require 'cohabit/route_helper_scope'
5
6
 
6
7
  module Cohabit
8
+ @data = {}
7
9
  class << self
8
10
  attr_accessor :current_tenant
9
- end
10
11
 
11
- module ActiveRecordExtensions
12
- def _apply_cohabit_scope(_scope)
13
- proc = _scope.strategy.model_code
14
- instance_exec(_scope, &proc) if proc
12
+ def add_global(name)
13
+ singleton_class.send(:define_method, name) { @data[name] }
14
+ singleton_class.send(:define_method, "#{name}=") { |val| @data[name] = val }
15
15
  end
16
16
  end
17
17
  end
18
18
 
19
- ActiveRecord::Base.extend Cohabit::ActiveRecordExtensions
20
-
21
- if defined? ::Rails::Railtie
19
+ if defined?(Rails) && Rails::VERSION::MAJOR.to_i >= 3
22
20
  module Cohabit
23
21
  class Railtie < Rails::Railtie
24
- initializer "cohabit.configure" do |app|
25
- config = Cohabit::Configuration.new
26
- config.load(file: File.join(Rails.root, "config/cohabit.rb"))
27
- config.apply_scopes!
22
+ initializer "cohabit.initialize_scopes" do
23
+ ActiveSupport.on_load :after_initialize do
24
+ config = Cohabit::Configuration.new
25
+ config.load(file: File.join(Rails.root, "config/cohabit.rb"))
26
+ config.apply_all!
27
+ end
28
28
  end
29
29
  end
30
30
  end
31
- else
32
- config = Cohabit::Configuration.new
33
- config.load(file: File.join(Rails.root, "config/cohabit.rb"))
34
- config.apply_scopes!
35
31
  end
@@ -2,7 +2,9 @@ require "cohabit/version"
2
2
  require "cohabit/configuration/settings"
3
3
  require "cohabit/configuration/strategies"
4
4
  require "cohabit/configuration/scopes"
5
+ require "cohabit/configuration/route_helper_scopes"
5
6
  require "active_record"
7
+ require "active_support/inflector"
6
8
 
7
9
  module Cohabit
8
10
  class Configuration
@@ -10,7 +12,7 @@ module Cohabit
10
12
  attr_reader :load_paths
11
13
 
12
14
  def initialize(config_file = nil)
13
- @load_paths = [".", File.expand_path(File.join(File.dirname(__FILE__), "strategies"))]
15
+ @load_paths = [Rails.root.join("lib"), Rails.root.join("config"), ".", File.expand_path(File.join(File.dirname(__FILE__), "strategies"))]
14
16
  end
15
17
 
16
18
  def load(*args, &block)
@@ -40,7 +42,16 @@ module Cohabit
40
42
  end
41
43
  end
42
44
 
43
- include Settings, Strategies, Scopes
45
+ def apply_all!
46
+ self.class.ancestors.take_while{|a| a != self.class.superclass}
47
+ .reject{|a| a == self.class}
48
+ .each do |a|
49
+ method = "apply_#{a.name.demodulize.underscore}!"
50
+ self.send(method, self) if self.respond_to?(method)
51
+ end
52
+ end
53
+
54
+ include Settings, Strategies, Scopes, RouteHelperScopes
44
55
 
45
56
  end
46
57
  end
@@ -0,0 +1,18 @@
1
+ module Cohabit
2
+ class Configuration
3
+ module RouteHelperScopes
4
+
5
+ attr_accessor :route_scopes
6
+
7
+ def scope_route_helpers(*args)
8
+ generate_settings_hash!(args)
9
+ (@route_scopes ||= []) << RouteHelperScope.new(*args)
10
+ end
11
+
12
+ def apply_route_helper_scopes!(context = self)
13
+ @route_scopes.each{|rs| rs.apply!(context)}
14
+ end
15
+
16
+ end
17
+ end
18
+ end
@@ -16,13 +16,13 @@ module Cohabit
16
16
  attr_reader :scopes
17
17
 
18
18
  def scope(*args, &block)
19
- args[1] &&= find_strategy_by_name(args[1])
19
+ generate_settings_hash!(args)
20
20
  scope = Scope.new(*args, &block)
21
21
  add_scope(scope)
22
22
  end
23
23
 
24
- def apply_scopes!
25
- @scopes.each{ |s| s.apply! }
24
+ def apply_scopes!(context = self)
25
+ @scopes.each{ |s| s.apply!(context) }
26
26
  end
27
27
 
28
28
  private
@@ -2,12 +2,13 @@ module Cohabit
2
2
  class Configuration
3
3
  module Settings
4
4
 
5
- DEFULT_SETTINGZ = {
6
- scope_validations: false,
7
- scope_url_helpers: false,
8
- association: :tenant
5
+ DEFAULT_SETTINGS = {
6
+ association: :tenant,
7
+ association_as: :tenant
9
8
  }
10
9
 
10
+ CUSTOM_HANDLERS = [:globals]
11
+
11
12
  def self.included(base)
12
13
  base.send :alias_method, :initialize_without_settings, :initialize
13
14
  base.send :alias_method, :initialize, :initialize_with_settings
@@ -17,12 +18,12 @@ module Cohabit
17
18
  attr_reader :settings
18
19
 
19
20
  def initialize_with_settings(*args, &block)
20
- @settings = DEFULT_SETTINGZ.dup
21
+ @settings = DEFAULT_SETTINGS.dup
21
22
  initialize_without_settings(*args, &block)
22
23
  end
23
24
 
24
25
  def merge_settings!(settings)
25
- settings.delete_if{ |s| !DEFULT_SETTINGZ.include?(s) }
26
+ settings.delete_if{ |s| !DEFAULT_SETTINGS.include?(s) }
26
27
  @settings.merge!(settings)
27
28
  end
28
29
 
@@ -35,10 +36,17 @@ module Cohabit
35
36
  end
36
37
 
37
38
  def set(setting, value)
38
- if !DEFULT_SETTINGZ.include?(setting.to_sym)
39
- raise ArgumentError, "what the fuck are you doing.. that's not a setting"
39
+ setting = setting.to_sym
40
+ if CUSTOM_HANDLERS.include?(setting)
41
+ send("set_#{setting}", value) and return
42
+ end
43
+ @settings[setting] = value
44
+ end
45
+
46
+ def set_globals(value)
47
+ [value].flatten.each do |v|
48
+ Cohabit.add_global(v) unless Cohabit.respond_to?(v)
40
49
  end
41
- @settings[setting.to_sym] = value
42
50
  end
43
51
 
44
52
  end
@@ -29,7 +29,7 @@ module Cohabit
29
29
 
30
30
  private
31
31
  def add_strategy(strategy)
32
- raise StrategyNameExistsError if named_strategy_exists?(strategy.name)
32
+ raise StrategyNameExistsError, strategy.name if named_strategy_exists?(strategy.name)
33
33
  strategies << strategy
34
34
  end
35
35
 
@@ -4,6 +4,7 @@ module Cohabit
4
4
 
5
5
  StrategyNameExistsError = Class.new(Cohabit::Error)
6
6
  StrategyNotFoundError = Class.new(Cohabit::Error)
7
+ StrategyNestingError = Class.new(Cohabit::Error)
7
8
  InvalidScopeError = Class.new(Cohabit::Error)
8
9
 
9
10
  end
@@ -0,0 +1,65 @@
1
+ module Cohabit
2
+ class RouteHelperScope
3
+
4
+ def initialize(*args)
5
+ merge_settings!(args.last) if args.last.is_a?(Hash)
6
+ end
7
+
8
+ include Configuration::Settings
9
+
10
+ def apply!(context)
11
+ named_routes = get_route_helpers
12
+
13
+ route_helpers = Module.new
14
+ route_helpers.module_exec(named_routes, @settings[:association], &route_override_proc)
15
+ Cohabit.const_set("RouteHelpers", route_helpers)
16
+ ActionView::Base.send :include, Cohabit::RouteHelpers
17
+ end
18
+
19
+ private
20
+ def get_route_helpers
21
+ named_routes_arr = Rails.application.routes.named_routes
22
+ .find_all{|rn, _| rn =~ /#{Regexp.escape(@settings[:association_as])}_/}
23
+ .group_by do |_, r|
24
+ name = r.path.names[r.path.names.index("#{@settings[:association_as]}_id")+1]
25
+ if name =~ /_id/
26
+ name.gsub("_id", "")
27
+ else
28
+ r.defaults[:controller].classify.downcase
29
+ end
30
+ end
31
+ .reject{|k, _| k.nil?}
32
+ .map{|g, rs| [g, Hash[rs.map{|rn, r| [rn.to_s.gsub("#{@settings[:association_as]}_", "").to_sym, rn]}]]}
33
+ # e.g. { student: { :school_student => :student, :school_edit_student => :edit_student } }
34
+ return Hash[named_routes_arr]
35
+ end
36
+
37
+ def route_override_proc
38
+ Proc.new do |named_routes, assoc|
39
+ named_routes.each do |mr, rs|
40
+ rs.each do |r, orig_r|
41
+ module_eval <<-EOT, __FILE__, __LINE__ + 1
42
+ def #{r}_path(*args)
43
+ do_dat_thang!("path", "#{assoc}", "#{mr}", "#{orig_r}", *args) || super
44
+ end
45
+ def #{r}_url(*args)
46
+ do_dat_thang!("url", "#{assoc}", "#{mr}", "#{orig_r}", *args) || super
47
+ end
48
+ EOT
49
+ end
50
+ end
51
+
52
+ def do_dat_thang!(type, assoc, main_resource, orig_route, obj, *args)
53
+ if obj.is_a?(Integer)
54
+ model = main_resource.classify.constantize
55
+ obj = model.find(obj)
56
+ end
57
+ if Cohabit.current_tenant.is_cluster? && obj.respond_to?(assoc)
58
+ send("#{orig_route}_#{type}", obj.send(assoc), obj, *args)
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ end
65
+ end
@@ -10,7 +10,6 @@ module Cohabit
10
10
  apply_to(args[0])
11
11
  use_strategy(args[1])
12
12
  instance_eval(&block) unless block.nil?
13
- merge_settings!(@strategy.settings)
14
13
  unless valid?
15
14
  raise InvalidScopeError, "provide valid model(s) and strategy"
16
15
  end
@@ -22,7 +21,7 @@ module Cohabit
22
21
 
23
22
  def use_strategy(strategy)
24
23
  unless strategy.nil?
25
- @strategy = strategy
24
+ @strategy_name = strategy
26
25
  end
27
26
  end
28
27
 
@@ -30,17 +29,22 @@ module Cohabit
30
29
  @models = parse_models(models)
31
30
  end
32
31
 
33
- def apply!
34
- # apply yourself, that's what my teachers always said.
32
+ def apply!(context)
33
+ # apply yourself! that's what my teachers always said.
34
+ strategy_stack = get_strategies(@strategy_name, context)
35
+ main_strategy = context.find_strategy_by_name(@strategy_name)
36
+ merge_settings!(main_strategy.settings)
35
37
  @models.each do |model|
36
- model._apply_cohabit_scope(self)
38
+ strategy_stack.each do |strategy|
39
+ model.instance_exec(self, &strategy.model_code)
40
+ end
37
41
  end
38
42
  end
39
43
 
40
44
  private
41
45
  def valid?
42
46
  return false if @models.empty?
43
- return false if @strategy.nil?
47
+ return false if @strategy_name.nil?
44
48
  @models.each do |model|
45
49
  return false if !ActiveRecord::Base.descendants.include?(model)
46
50
  end
@@ -53,5 +57,20 @@ module Cohabit
53
57
  end
54
58
  end
55
59
 
60
+ def get_strategies(strategy_name, context, strategy_stack = [])
61
+ strategy = context.find_strategy_by_name(strategy_name)
62
+ strategy.strategies.each do |s|
63
+ if s == strategy.name
64
+ if strategy_stack.include?(strategy)
65
+ raise StrategyNestingError, "strategies can't be nested twice in the same stack"
66
+ end
67
+ strategy_stack << strategy
68
+ else
69
+ strategy_stack = get_strategies(s, context, strategy_stack)
70
+ end
71
+ end
72
+ strategy_stack
73
+ end
74
+
56
75
  end
57
76
  end
@@ -1,8 +1,11 @@
1
1
  strategy :basic do
2
2
  model_eval do |_scope|
3
+ # _scope var references the scope that uses the strategy,
4
+ # so to access settings, like :association, use
5
+ # _scope.settings[:association]. current_tenant is defined
6
+ # as Cohabit.current_tenant.
3
7
  belongs_to _scope.settings[:association]
4
8
  reflection = reflect_on_association _scope.settings[:association]
5
- scope_validators(reflection.foreign_key) if _scope.settings[:scope_validations]
6
9
  before_create Proc.new {|m|
7
10
  return unless Cohabit.current_tenant
8
11
  m.send "#{_scope.settings[:association]}=".to_sym, Cohabit.current_tenant
@@ -1,2 +1,3 @@
1
1
  require 'basic'
2
- require 'multi'
2
+ require 'multi'
3
+ require 'scope_validators'
@@ -1,13 +1,16 @@
1
- # strategy :multi do
2
- # model_eval do
3
- # reflection = reflect_on_association _scope.settings[:association]
4
- # scope_validators(reflection.foreign_key) if _scope.settings[:scope_validations]
5
- # before_create Proc.new {|m|
6
- # return unless _scope.current_school
7
- # m.send "#{_scope.settings[:association]}=".to_sym, _scope.current_school
8
- # }
9
- # default_scope lambda {
10
- # where(reflection.foreign_key => _scope.current_scope) if _scope.current_scope
11
- # }
12
- # end
13
- # end
1
+ strategy :multi do
2
+ set :globals, :current_scope
3
+ model_eval do |_scope|
4
+ belongs_to _scope.settings[:association]
5
+ reflection = reflect_on_association _scope.settings[:association]
6
+ # insertions are scoped to current_tenant
7
+ before_create Proc.new {|m|
8
+ return unless Cohabit.current_tenant
9
+ m.send "#{association}=".to_sym, Cohabit.current_tenant
10
+ }
11
+ # selects are scoped to multiple clients (stored in current_scope)
12
+ default_scope lambda {
13
+ where(reflection.foreign_key => Cohabit.current_scope) if Cohabit.current_scope
14
+ }
15
+ end
16
+ end
@@ -0,0 +1,20 @@
1
+ strategy :scope_validators do
2
+ model_eval do |_scope|
3
+ reflection = reflect_on_association _scope.settings[:association]
4
+ foreign_key = reflection.foreign_key
5
+ _validators.each do |attribute, validations|
6
+ validations.reject!{|v| v.kind == :uniqueness}
7
+ end
8
+ new_callback_chain = self._validate_callbacks.reject do |callback|
9
+ callback.raw_filter.is_a?(ActiveRecord::Validations::UniquenessValidator)
10
+ end
11
+ deleted = self._validate_callbacks - new_callback_chain
12
+ (self._validate_callbacks.clear << new_callback_chain).flatten!
13
+ deleted.each do |c|
14
+ v = c.raw_filter
15
+ v.attributes.each do |a|
16
+ validates_uniqueness_of *v.attributes, v.options.merge(scope: foreign_key)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -5,6 +5,7 @@ module Cohabit
5
5
 
6
6
  def initialize(*args, &block)
7
7
  raise ArgumentError, "you must supply a name" if args.empty?
8
+ @strategies = []
8
9
  @name = args.shift.to_sym
9
10
  @settings = args.last.is_a?(Hash) ? args.last : {}
10
11
  instance_eval(&block) unless block.nil?
@@ -12,10 +13,21 @@ module Cohabit
12
13
 
13
14
  include Configuration::Settings
14
15
 
15
- attr_reader :name, :model_code
16
+ attr_reader :name, :model_code, :strategies
16
17
 
17
18
  def model_eval(&block)
18
19
  @model_code = block
20
+ @strategies << @name
21
+ end
22
+
23
+ def include_strategy(name, options = {})
24
+ name = name.to_sym
25
+ raise ArgumentError if name.nil?
26
+ if @strategies.include?(name)
27
+ raise Argumenterror, "can't nest the same strategy twice
28
+ or use two model_eval blocks in the same strategy"
29
+ end
30
+ @strategies << name
19
31
  end
20
32
 
21
33
  end
@@ -1,3 +1,3 @@
1
1
  module Cohabit
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -31,11 +31,20 @@ class ScopesTest < Test::Unit::TestCase
31
31
  scope :ybur, :basic, association: :client
32
32
  end
33
33
  assert_equal(:client, @c.scopes.first.settings[:association])
34
- end
34
+ end
35
+
36
+ def test_settings_for_scope_globally
37
+ @c.load do
38
+ require 'basic'
39
+ set :association, :client
40
+ scope :ybur, :basic
41
+ end
42
+ assert_equal(:client, @c.scopes.first.settings[:association])
43
+ end
35
44
 
36
45
  def test_apply_scope_to_model
37
- c = Client.create(name: "fubar")
38
- Cohabit.current_tenant = c
46
+ client = Client.create(name: "fubar")
47
+ Cohabit.current_tenant = client
39
48
  @c.load do
40
49
  require 'basic'
41
50
  scope :ybur, :basic
@@ -45,8 +54,8 @@ class ScopesTest < Test::Unit::TestCase
45
54
  end
46
55
 
47
56
  def test_setting_association_name
48
- c = Client.create(name: "fubar")
49
- Cohabit.current_tenant = c
57
+ client = Client.create(name: "fubar")
58
+ Cohabit.current_tenant = client
50
59
  @c.load do
51
60
  require 'basic'
52
61
  scope :ybur, :basic, association: :client
@@ -55,4 +64,57 @@ class ScopesTest < Test::Unit::TestCase
55
64
  assert_match(/client_id/, Ybur.scoped.to_sql)
56
65
  end
57
66
 
67
+ def test_nested_strategy_scope_basic
68
+ client = Client.create(name: "fubar")
69
+ Cohabit.current_tenant = client
70
+ # should run like normal basic strategy
71
+ @c.load do
72
+ require 'basic'
73
+ strategy :frankel, { association: :client } do
74
+ include_strategy :basic
75
+ end
76
+ scope :ybur, :frankel
77
+ end
78
+ @c.apply_scopes!
79
+ assert_not_equal(Ybur.unscoped.to_sql, Ybur.scoped.to_sql)
80
+ end
81
+
82
+ def test_nested_strategy_scope_override
83
+ client = Client.create(name: "fubar")
84
+ Cohabit.current_tenant = client
85
+ # should remove basic strategy's default scope, as it
86
+ # is evaluated after it in the main strategy
87
+ @c.load do
88
+ require 'basic'
89
+ strategy :frankel, { association: :client } do
90
+ include_strategy :basic
91
+ model_eval do |_scope|
92
+ default_scopes.clear
93
+ end
94
+ end
95
+ scope :ybur, :frankel
96
+ end
97
+ @c.apply_scopes!
98
+ assert_equal(Ybur.unscoped.to_sql, Ybur.scoped.to_sql)
99
+ end
100
+
101
+ def test_evaluation_order_in_nested_strategies
102
+ client = Client.create(name: "fubar")
103
+ Cohabit.current_tenant = client
104
+ # should remove basic strategy's default scope, as it
105
+ # is evaluated after it in the main strategy
106
+ @c.load do
107
+ require 'basic'
108
+ strategy :frankel, { association: :client } do
109
+ model_eval do |_scope|
110
+ default_scopes.clear
111
+ end
112
+ include_strategy :basic
113
+ end
114
+ scope :ybur, :frankel
115
+ end
116
+ @c.apply_scopes!
117
+ assert_not_equal(Ybur.unscoped.to_sql, Ybur.scoped.to_sql)
118
+ end
119
+
58
120
  end
@@ -4,24 +4,17 @@ class SettingsTest < Test::Unit::TestCase
4
4
  @c = Cohabit::Configuration.new
5
5
  end
6
6
 
7
- def test_set_real_setting
8
- assert_not_equal(@c.settings[:scope_validations], true)
7
+ def test_set_setting
9
8
  @c.set :scope_validations, true
10
9
  assert_equal(true, @c.settings[:scope_validations])
11
10
  end
12
11
 
13
- def test_set_nonexitant_setting
14
- assert_raise(ArgumentError) do
15
- @c.set :wtf_not_a_real_setting, "dis is a value, innit"
16
- end
17
- end
18
-
19
12
  def test_set_setting_with_config
20
- assert_equal(false, @c.settings[:scope_validations])
13
+ assert_equal(:tenant, @c.settings[:association])
21
14
  @c.load do
22
- set :scope_validations, true
15
+ set :association, :client
23
16
  end
24
- assert_equal(true, @c.settings[:scope_validations])
17
+ assert_equal(:client, @c.settings[:association])
25
18
  end
26
19
 
27
20
  end
@@ -37,7 +37,6 @@ class StrategiesTest < Test::Unit::TestCase
37
37
  # strategy block defaults > strategy arg defaults
38
38
 
39
39
  # should take on strategy default settings when passed in
40
- setup
41
40
  @c.load do
42
41
  strategy :frankel, { scope_validations: true }
43
42
  end
@@ -54,4 +53,14 @@ class StrategiesTest < Test::Unit::TestCase
54
53
  assert_equal(true, @c.strategies.first.settings[:scope_validations])
55
54
  end
56
55
 
56
+ def test_nested_strategy
57
+ @c.load do
58
+ strategy :jim
59
+ strategy :frankel, { association: :client } do
60
+ include_strategy :jim
61
+ end
62
+ end
63
+ assert_equal([:jim], @c.find_strategy_by_name(:frankel).strategies)
64
+ end
65
+
57
66
  end
@@ -3,9 +3,9 @@ require "test/unit"
3
3
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
4
4
  $LOAD_PATH.unshift(File.dirname(__FILE__))
5
5
 
6
+ require "cohabit"
6
7
  require "active_record"
7
8
  require "models"
8
- require "cohabit"
9
9
 
10
10
  def load_schema
11
11
  config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
metadata CHANGED
@@ -1,52 +1,46 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cohabit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
5
- prerelease:
4
+ version: 0.0.2
6
5
  platform: ruby
7
6
  authors:
8
7
  - Mike Campbell
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-08-02 00:00:00.000000000 Z
11
+ date: 2014-03-18 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: activerecord
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
- - - ! '>='
17
+ - - '>='
20
18
  - !ruby/object:Gem::Version
21
19
  version: '0'
22
20
  type: :runtime
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
- - - ! '>='
24
+ - - '>='
28
25
  - !ruby/object:Gem::Version
29
26
  version: '0'
30
27
  - !ruby/object:Gem::Dependency
31
28
  name: activesupport
32
29
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
30
  requirements:
35
- - - ! '>='
31
+ - - '>='
36
32
  - !ruby/object:Gem::Version
37
33
  version: '0'
38
34
  type: :runtime
39
35
  prerelease: false
40
36
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
37
  requirements:
43
- - - ! '>='
38
+ - - '>='
44
39
  - !ruby/object:Gem::Version
45
40
  version: '0'
46
41
  - !ruby/object:Gem::Dependency
47
42
  name: bundler
48
43
  requirement: !ruby/object:Gem::Requirement
49
- none: false
50
44
  requirements:
51
45
  - - ~>
52
46
  - !ruby/object:Gem::Version
@@ -54,7 +48,6 @@ dependencies:
54
48
  type: :development
55
49
  prerelease: false
56
50
  version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
51
  requirements:
59
52
  - - ~>
60
53
  - !ruby/object:Gem::Version
@@ -62,17 +55,15 @@ dependencies:
62
55
  - !ruby/object:Gem::Dependency
63
56
  name: rake
64
57
  requirement: !ruby/object:Gem::Requirement
65
- none: false
66
58
  requirements:
67
- - - ! '>='
59
+ - - '>='
68
60
  - !ruby/object:Gem::Version
69
61
  version: '0'
70
62
  type: :development
71
63
  prerelease: false
72
64
  version_requirements: !ruby/object:Gem::Requirement
73
- none: false
74
65
  requirements:
75
- - - ! '>='
66
+ - - '>='
76
67
  - !ruby/object:Gem::Version
77
68
  version: '0'
78
69
  description: Handle application scoping for multi-tenant applications with table scopes.
@@ -90,15 +81,17 @@ files:
90
81
  - cohabit.gemspec
91
82
  - lib/cohabit.rb
92
83
  - lib/cohabit/configuration.rb
84
+ - lib/cohabit/configuration/route_helper_scopes.rb
93
85
  - lib/cohabit/configuration/scopes.rb
94
86
  - lib/cohabit/configuration/settings.rb
95
87
  - lib/cohabit/configuration/strategies.rb
96
88
  - lib/cohabit/errors.rb
89
+ - lib/cohabit/route_helper_scope.rb
97
90
  - lib/cohabit/scope.rb
98
- - lib/cohabit/snippets.rb
99
91
  - lib/cohabit/strategies/basic.rb
100
92
  - lib/cohabit/strategies/defaults.rb
101
93
  - lib/cohabit/strategies/multi.rb
94
+ - lib/cohabit/strategies/scope_validators.rb
102
95
  - lib/cohabit/strategy.rb
103
96
  - lib/cohabit/version.rb
104
97
  - test/all_tests.rb
@@ -113,27 +106,26 @@ files:
113
106
  homepage: http://github.com/mikecmpbll/cohabit
114
107
  licenses:
115
108
  - MIT
109
+ metadata: {}
116
110
  post_install_message:
117
111
  rdoc_options: []
118
112
  require_paths:
119
113
  - lib
120
114
  required_ruby_version: !ruby/object:Gem::Requirement
121
- none: false
122
115
  requirements:
123
- - - ! '>='
116
+ - - '>='
124
117
  - !ruby/object:Gem::Version
125
118
  version: '0'
126
119
  required_rubygems_version: !ruby/object:Gem::Requirement
127
- none: false
128
120
  requirements:
129
- - - ! '>='
121
+ - - '>='
130
122
  - !ruby/object:Gem::Version
131
123
  version: '0'
132
124
  requirements: []
133
125
  rubyforge_project:
134
- rubygems_version: 1.8.25
126
+ rubygems_version: 2.0.6
135
127
  signing_key:
136
- specification_version: 3
128
+ specification_version: 4
137
129
  summary: Scope multi-tenant applications.
138
130
  test_files:
139
131
  - test/all_tests.rb
@@ -1,4 +0,0 @@
1
- module Cohabit
2
- class Snippets
3
- end
4
- end