cohabit 0.0.1 → 0.0.2

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,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