required_scopes 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.
@@ -0,0 +1,98 @@
1
+ require 'active_record'
2
+ require 'active_record/migration'
3
+
4
+ module RequiredScopes
5
+ module Helpers
6
+ module SystemHelpers
7
+ def migrate(&block)
8
+ migration_class = Class.new(::ActiveRecord::Migration)
9
+ metaclass = migration_class.class_eval { class << self; self; end }
10
+ metaclass.instance_eval { define_method(:up, &block) }
11
+
12
+ ::ActiveRecord::Migration.suppress_messages do
13
+ migration_class.migrate(:up)
14
+ end
15
+ end
16
+
17
+ def define_model_class(name, table_name, &block)
18
+ model_class = Class.new(::ActiveRecord::Base)
19
+ ::Object.send(:remove_const, name) if ::Object.const_defined?(name)
20
+ ::Object.const_set(name, model_class)
21
+ model_class.table_name = table_name
22
+ model_class.class_eval(&block)
23
+ end
24
+
25
+ def create_standard_system_spec_tables!
26
+ migrate do
27
+ drop_table :rec_spec_users rescue nil
28
+ create_table :rec_spec_users do |t|
29
+ t.string :name, :null => false
30
+ t.string :favorite_color
31
+ t.string :favorite_taste
32
+ end
33
+ end
34
+ end
35
+
36
+ def define_color_and_taste_scopes!
37
+ ::User.class_eval do
38
+ must_scope_by :color, :taste
39
+
40
+ scope :red, lambda { where(:favorite_color => 'red') }, :satisfies => :color
41
+ scope :green, lambda { where(:favorite_color => 'green') }, :satisfies => :color
42
+
43
+ scope :salty, lambda { where(:favorite_taste => 'salty') }, :satisfies => :taste
44
+ scope :sweet, lambda { where(:favorite_taste => 'sweet') }, :satisfies => :taste
45
+
46
+ scope :red_and_salty, lambda { where(:favorite_color => 'red', :favorite_taste => 'salty') }, :satisfies => [ :color, :taste ]
47
+ end
48
+ end
49
+
50
+ def should_raise_missing_scopes(triggering_method, required, satisfied, options = { })
51
+ e = result = nil
52
+
53
+ begin
54
+ result = yield
55
+ rescue RequiredScopes::Errors::RequiredScopeCategoriesNotSatisfiedError => rscnse
56
+ e = rscnse
57
+ end
58
+
59
+ raise "Expected a scopes-not-satisfied error, but got none" unless e
60
+
61
+ expected_model_class = options[:model_class] || ::User
62
+
63
+ e.class.should == RequiredScopes::Errors::RequiredScopeCategoriesNotSatisfiedError
64
+ e.model_class.should == expected_model_class
65
+ e.current_relation.should be
66
+ e.current_relation.kind_of?(::ActiveRecord::Relation).should be
67
+ e.triggering_method.should == triggering_method
68
+ e.required_categories.sort_by(&:to_s).should == required.sort_by(&:to_s)
69
+ e.satisfied_categories.sort_by(&:to_s).should == satisfied.sort_by(&:to_s)
70
+
71
+ expected_missing_categories = required - satisfied
72
+ e.missing_categories.sort_by(&:to_s).should == expected_missing_categories.sort_by(&:to_s)
73
+
74
+ e.message.should match(/#{expected_model_class.name}/)
75
+ e.message.should match(/#{expected_missing_categories.sort_by(&:to_s).join(", ")}/)
76
+ end
77
+
78
+ def create_standard_system_spec_models!
79
+ define_model_class(:User, 'rec_spec_users') { }
80
+ end
81
+
82
+ def create_standard_system_spec_instances!
83
+ @red_salty = ::User.create!(:name => 'red-salty', :favorite_color => 'red', :favorite_taste => 'salty')
84
+ @green_salty = ::User.create!(:name => 'green-salty', :favorite_color => 'green', :favorite_taste => 'salty')
85
+ @blue_salty = ::User.create!(:name => 'blue-salty', :favorite_color => 'blue', :favorite_taste => 'salty')
86
+ @red_sweet = ::User.create!(:name => 'red-sweet', :favorite_color => 'red', :favorite_taste => 'sweet')
87
+ @green_sweet = ::User.create!(:name => 'green-sweet', :favorite_color => 'green', :favorite_taste => 'sweet')
88
+ @blue_sweet = ::User.create!(:name => 'blue-sweet', :favorite_color => 'blue', :favorite_taste => 'sweet')
89
+ end
90
+
91
+ def drop_standard_system_spec_tables!
92
+ migrate do
93
+ drop_table :rec_spec_users rescue nil
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,150 @@
1
+ require 'required_scopes'
2
+ require 'required_scopes/helpers/database_helper'
3
+ require 'required_scopes/helpers/system_helpers'
4
+
5
+ describe "RequiredScopes and associations" do
6
+ include RequiredScopes::Helpers::SystemHelpers
7
+
8
+ before :each do
9
+ @dh = RequiredScopes::Helpers::DatabaseHelper.new
10
+ @dh.setup_activerecord!
11
+
12
+ migrate do
13
+ drop_table :rec_spec_groups rescue nil
14
+ create_table :rec_spec_groups do |t|
15
+ t.string :name, :null => false
16
+ end
17
+
18
+ drop_table :rec_spec_users rescue nil
19
+ create_table :rec_spec_users do |t|
20
+ t.string :name, :null => false
21
+ t.integer :group_id
22
+ t.string :favorite_color
23
+ end
24
+
25
+ drop_table :rec_spec_user_preferences rescue nil
26
+ create_table :rec_spec_user_preferences do |t|
27
+ t.integer :user_id
28
+ t.string :preference
29
+ end
30
+
31
+ drop_table :rec_spec_categories rescue nil
32
+ create_table :rec_spec_categories do |t|
33
+ t.string :name
34
+ end
35
+
36
+ drop_table :rec_spec_categories_users rescue nil
37
+ create_table :rec_spec_categories_users, :id => false do |t|
38
+ t.integer :user_id
39
+ t.integer :category_id
40
+ end
41
+ end
42
+
43
+ define_model_class(:User, :rec_spec_users) do
44
+ must_scope_by :color
45
+ belongs_to :group
46
+ has_one :user_preference
47
+ has_and_belongs_to_many :categories, :join_table => :rec_spec_categories_users
48
+
49
+ scope :red, lambda { where(:favorite_color => :red) }, :satisfies => :color
50
+ end
51
+
52
+ define_model_class(:Group, :rec_spec_groups) do
53
+ has_many :users
54
+ end
55
+
56
+ define_model_class(:UserPreference, :rec_spec_user_preferences) do
57
+ belongs_to :user
58
+
59
+ must_scope_by :prefcolor
60
+ end
61
+
62
+ define_model_class(:Category, :rec_spec_categories) do
63
+ has_and_belongs_to_many :users, :join_table => :rec_spec_categories_users
64
+
65
+ must_scope_by :catcolor
66
+ end
67
+
68
+ @group1 = ::Group.create!(:name => 'Group 1')
69
+ @user1 = ::User.create!(:name => 'g1u1', :favorite_color => 'red', :group => @group1)
70
+ @user2 = ::User.create!(:name => 'g1u2', :favorite_color => 'green', :group => @group1)
71
+
72
+ @user1pref = ::UserPreference.create!(:user => @user1, :preference => 'u1p')
73
+ @user2pref = ::UserPreference.create!(:user => @user2, :preference => 'u2p')
74
+
75
+ @cat1 = ::Category.create!(:name => 'c1')
76
+ @cat2 = ::Category.create!(:name => 'c2')
77
+
78
+ @cat1.users << @user1
79
+ @cat1.users << @user2
80
+ @cat1.save!
81
+
82
+ @cat2.users << @user1
83
+ @cat2.users << @user2
84
+ @cat2.save!
85
+ end
86
+
87
+ after :each do
88
+ migrate do
89
+ drop_table :rec_spec_groups rescue nil
90
+ drop_table :rec_spec_users rescue nil
91
+ drop_table :rec_spec_user_preferences rescue nil
92
+ drop_table :rec_spec_categories rescue nil
93
+ drop_table :rec_spec_categories_users rescue nil
94
+ end
95
+ end
96
+
97
+ it "should require a scope when accessed directly" do
98
+ lambda { ::User.where(:group_id => @group1.id).to_a }.should raise_error(RequiredScopes::Errors::RequiredScopeCategoriesNotSatisfiedError)
99
+ end
100
+
101
+ it "should not require a scope when accessed via an association" do
102
+ @group1.users.map(&:id).sort.should == [ @user1, @user2 ].map(&:id).sort
103
+ end
104
+
105
+ it "should not require a scope when accessed via an association, with eager loading" do
106
+ Group.includes(:users).find(@group1.id).users.map(&:id).sort.should == [ @user1, @user2 ].map(&:id).sort
107
+ end
108
+
109
+ it "should not require a scope when accessed via #joins" do
110
+ Group.joins(:users).find(@group1.id).users.map(&:id).sort.should == [ @user1, @user2 ].map(&:id).sort
111
+ end
112
+
113
+ it "should not require a scope when accessed via #eager_load" do
114
+ Group.eager_load(:users).find(@group1.id).users.map(&:id).sort.should == [ @user1, @user2 ].map(&:id).sort
115
+ end
116
+
117
+ it "should not require a scope when accessed via #preload" do
118
+ Group.preload(:users).find(@group1.id).users.map(&:id).sort.should == [ @user1, @user2 ].map(&:id).sort
119
+ end
120
+
121
+ it "should not require a scope when accessed via #references" do
122
+ if ::RequiredScopes::ActiveRecord::VersionCompatibility.supports_references_method?
123
+ Group.references(:users).find(@group1.id).users.map(&:id).sort.should == [ @user1, @user2 ].map(&:id).sort
124
+ end
125
+ end
126
+
127
+ it "should not require a scope when accessed via has_one" do
128
+ @user1.user_preference.should == @user1pref
129
+ end
130
+
131
+ it "should not require a scope when accessed via has_one, with eager loading" do
132
+ User.includes(:user_preference).red.find(@user1.id).id.should == @user1pref.id
133
+ end
134
+
135
+ it "should not require a scope when accessed via belongs_to" do
136
+ @user1pref.user.should == @user1
137
+ end
138
+
139
+ it "should not require a scope when accessed via has_one, with eager loading" do
140
+ UserPreference.includes(:user).all_scope_categories_satisfied.find(@user1pref.id).id.should == @user1.id
141
+ end
142
+
143
+ it "should not require a scope when accessed via has_and_belongs_to_many" do
144
+ @user1.categories.map(&:id).sort.should == [ @cat1, @cat2 ].map(&:id).sort
145
+ end
146
+
147
+ it "should not require a scope when accessed via has_and_belongs_to_many, with eager loading" do
148
+ ::User.includes(:categories).all_scope_categories_satisfied.find(@user1).categories.map(&:id).should == [ @cat1, @cat2 ].map(&:id).sort
149
+ end
150
+ end
@@ -0,0 +1,71 @@
1
+ require 'required_scopes'
2
+ require 'required_scopes/helpers/database_helper'
3
+ require 'required_scopes/helpers/system_helpers'
4
+
5
+ describe "RequiredScopes base scope operations" do
6
+ include RequiredScopes::Helpers::SystemHelpers
7
+
8
+ before :each do
9
+ @dh = RequiredScopes::Helpers::DatabaseHelper.new
10
+ @dh.setup_activerecord!
11
+
12
+ create_standard_system_spec_tables!
13
+ create_standard_system_spec_models!
14
+ create_standard_system_spec_instances!
15
+
16
+ ::User.class_eval do
17
+ base_scope_required!
18
+
19
+ base_scope :red, lambda { where(:favorite_color => 'red') }
20
+ base_scope :green, lambda { where(:favorite_color => 'green') }
21
+
22
+ scope :salty, lambda { where(:favorite_taste => 'salty') }
23
+
24
+ class << self
25
+ def blue
26
+ base_scope_satisfied.where(:favorite_color => 'blue')
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ after :each do
33
+ drop_standard_system_spec_tables!
34
+ end
35
+
36
+ it "should require the base scope" do
37
+ e = nil
38
+
39
+ begin
40
+ ::User.all.to_a
41
+ rescue RequiredScopes::Errors::BaseScopeNotSatisfiedError => bsnse
42
+ e = bsnse
43
+ end
44
+
45
+ e.should be
46
+ e.class.should == RequiredScopes::Errors::BaseScopeNotSatisfiedError
47
+ e.model_class.should == ::User
48
+
49
+ e.current_relation.should be
50
+ e.current_relation.kind_of?(::ActiveRecord::Relation).should be
51
+ e.triggering_method.should == :exec_queries
52
+
53
+ e.required_categories.should == [ :base ]
54
+ e.satisfied_categories.should == [ ]
55
+ e.missing_categories.should == [ :base ]
56
+
57
+ e.message.should match(/base scope/i)
58
+ end
59
+
60
+ it "should work if a base scope is used" do
61
+ ::User.red.to_a
62
+ end
63
+
64
+ it "should work if a class method satisfying the base scope is used" do
65
+ ::User.blue.to_a
66
+ end
67
+
68
+ it "should work if you manually tell it that the base scope is satisfied" do
69
+ ::User.base_scope_satisfied.to_a
70
+ end
71
+ end
@@ -0,0 +1,121 @@
1
+ require 'required_scopes'
2
+ require 'required_scopes/helpers/database_helper'
3
+ require 'required_scopes/helpers/system_helpers'
4
+
5
+ describe "RequiredScopes basic operations" do
6
+ include RequiredScopes::Helpers::SystemHelpers
7
+
8
+ before :each do
9
+ @dh = RequiredScopes::Helpers::DatabaseHelper.new
10
+ @dh.setup_activerecord!
11
+
12
+ create_standard_system_spec_tables!
13
+ create_standard_system_spec_models!
14
+ create_standard_system_spec_instances!
15
+
16
+ define_color_and_taste_scopes!
17
+ end
18
+
19
+ after :each do
20
+ drop_standard_system_spec_tables!
21
+ end
22
+
23
+ describe "queries" do
24
+ it "should raise if no categories are applied" do
25
+ should_raise_missing_scopes(:exec_queries, [ :color, :taste ], [ ]) { ::User.all.to_a }
26
+ end
27
+
28
+ it "should raise if only one category is applied" do
29
+ should_raise_missing_scopes(:exec_queries, [ :color, :taste ], [ :color ]) { ::User.red.to_a }
30
+ end
31
+
32
+ it "should not raise if both categories are individually applied" do
33
+ ::User.red.salty.to_a.should == [ @red_salty ]
34
+ end
35
+
36
+ it "should not raise if both categories are satisfied by a single scope" do
37
+ ::User.red_and_salty.to_a.should == [ @red_salty ]
38
+ end
39
+
40
+ it "should allow further qualifying the scopes, and in the middle" do
41
+ ::User.where("name LIKE 'red%'").red.where("name LIKE '%salty'").salty.where("name LIKE '%d-s%'").to_a.should == [ @red_salty ]
42
+ ::User.where("name LIKE 'green%'").red.salty.to_a.should == [ ]
43
+ ::User.red.where("name LIKE '%sweet'").salty.to_a.should == [ ]
44
+ ::User.red.salty.where("name LIKE '%sweet'").to_a.should == [ ]
45
+ end
46
+
47
+ it "should automatically have a scope that includes all of each category" do
48
+ ::User.red.ignoring_taste.to_a.sort.should == [ @red_salty, @red_sweet ].sort
49
+ ::User.ignoring_color.sweet.to_a.sort.should == [ @red_sweet, @green_sweet, @blue_sweet ].sort
50
+ end
51
+
52
+ it "should still require scopes even if #unscoped is used" do
53
+ should_raise_missing_scopes(:exec_queries, [ :color, :taste ], [ ]) { ::User.unscoped.to_a }
54
+ end
55
+
56
+ it "should still require scopes even if #unscoped is used in a block form" do
57
+ should_raise_missing_scopes(:exec_queries, [ :color, :taste ], [ ]) { ::User.unscoped { ::User.all.to_a } }
58
+ end
59
+
60
+ it "should allow manually saying that categories are satisfied" do
61
+ ::User.red.scope_category_satisfied(:taste).to_a.sort.should == [ @red_salty, @red_sweet ].to_a
62
+ ::User.scope_category_satisfied(:color).sweet.to_a.sort.should == [ @red_sweet, @green_sweet, @blue_sweet ].to_a
63
+ ::User.scope_categories_satisfied(:color, :taste).to_a.sort.should ==
64
+ [ @red_sweet, @green_sweet, @blue_sweet, @red_salty, @green_salty, @blue_salty ].to_a.sort
65
+ ::User.scope_categories_satisfied(:color, :taste).sweet.to_a.sort.should ==
66
+ [ @red_sweet, @green_sweet, @blue_sweet ].to_a.sort
67
+ end
68
+
69
+ it "should allow manually saying that categories are satisfied, in a block" do
70
+ ran_block = false
71
+ ::User.scope_category_satisfied(:taste) do
72
+ ::User.red.to_a.sort.should == [ @red_salty, @red_sweet].to_a
73
+ ran_block = true
74
+ end
75
+ ran_block.should be
76
+
77
+ ran_block = false
78
+ ::User.scope_category_satisfied(:taste) do
79
+ should_raise_missing_scopes(:exec_queries, [ :color, :taste ], [ :taste ]) { ::User.all.to_a }
80
+ ran_block = true
81
+ end
82
+ ran_block.should be
83
+
84
+ ran_block = false
85
+ ::User.scope_category_satisfied(:taste) do
86
+ ::User.red.to_a.sort.should == [ @red_salty, @red_sweet ].sort
87
+ ran_block = true
88
+ end
89
+ ran_block.should be
90
+
91
+ ran_block = false
92
+ ::User.all_scope_categories_satisfied do
93
+ ::User.all.to_a.sort.should == [ @red_salty, @red_sweet, @green_salty, @green_sweet, @blue_salty, @blue_sweet ].sort
94
+ ran_block = true
95
+ end
96
+ ran_block.should be
97
+ end
98
+
99
+ it "should allow saying that categories are satisfied in a class method, in any position" do
100
+ ::User.class_eval do
101
+ class << self
102
+ def red_and_green
103
+ scope_category_satisfied(:color).where(:favorite_color => %w{red green})
104
+ end
105
+
106
+ def green_and_blue
107
+ where(:favorite_color => %w{green blue}).scope_category_satisfied(:color)
108
+ end
109
+
110
+ def red_and_blue
111
+ where(:favorite_color => %w{red blue}).scope_category_satisfied(:color).where(:favorite_taste => %w{salty sweet})
112
+ end
113
+ end
114
+ end
115
+
116
+ ::User.red_and_green.salty.to_a.sort.should == [ @red_salty, @green_salty ].sort
117
+ ::User.green_and_blue.salty.to_a.sort.should == [ @green_salty, @blue_salty ].sort
118
+ ::User.red_and_blue.salty.to_a.sort.should == [ @red_salty, @blue_salty ].sort
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,67 @@
1
+ require 'required_scopes'
2
+ require 'required_scopes/helpers/database_helper'
3
+ require 'required_scopes/helpers/system_helpers'
4
+
5
+ describe "RequiredScopes and inheritance" do
6
+ include RequiredScopes::Helpers::SystemHelpers
7
+
8
+ before :each do
9
+ @dh = RequiredScopes::Helpers::DatabaseHelper.new
10
+ @dh.setup_activerecord!
11
+
12
+ create_standard_system_spec_tables!
13
+ create_standard_system_spec_models!
14
+ create_standard_system_spec_instances!
15
+ end
16
+
17
+ after :each do
18
+ drop_standard_system_spec_tables!
19
+ end
20
+
21
+ it "should combine multiple #must_scope_by declarations" do
22
+ ::User.class_eval do
23
+ must_scope_by :color
24
+ must_scope_by :taste
25
+
26
+ scope :red, lambda { where(:favorite_color => 'red') }, :satisfies => :color
27
+ scope :salty, lambda { where(:favorite_taste => 'salty') }, :satisfies => :taste
28
+ end
29
+
30
+ should_raise_missing_scopes(:exec_queries, [ :color, :taste ], [ :taste ]) { ::User.salty.to_a }
31
+ end
32
+
33
+ it "should inherit #must_scope_by in child classes" do
34
+ ::User.class_eval do
35
+ must_scope_by :color
36
+ scope :red, lambda { where(:favorite_color => 'red') }, :satisfies => :color
37
+ end
38
+
39
+ class ::UserSub1 < ::User
40
+ self.table_name = ::User.table_name
41
+
42
+ must_scope_by :taste
43
+ scope :salty, lambda { where(:favorite_taste => 'salty') }, :satisfies => :taste
44
+ end
45
+
46
+ should_raise_missing_scopes(:exec_queries, [ :color, :taste ], [ :taste ], :model_class => ::UserSub1) { ::UserSub1.salty.to_a }
47
+ end
48
+
49
+ it "should allow ignoring a required scope in a subclass" do
50
+ ::User.class_eval do
51
+ must_scope_by :color
52
+ scope :red, lambda { where(:favorite_color => 'red') }, :satisfies => :color
53
+ end
54
+
55
+ class ::UserSub2 < ::User
56
+ self.table_name = ::User.table_name
57
+
58
+ must_scope_by :taste
59
+ scope :salty, lambda { where(:favorite_taste => 'salty') }, :satisfies => :taste
60
+
61
+ ignore_parent_scope_requirement :color
62
+ end
63
+
64
+ ::UserSub2.salty.to_a.should be
65
+ should_raise_missing_scopes(:exec_queries, [ :taste ], [ ], :model_class => ::UserSub2) { ::UserSub2.all.to_a }
66
+ end
67
+ end