scope_chain 0.0.3 → 0.0.6
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.
- data/README.md +78 -33
- data/lib/scope_chain/version.rb +1 -1
- data/lib/scope_chain.rb +96 -6
- data/scope_chain.gemspec +2 -1
- data/spec/scope_chain_spec.rb +56 -0
- metadata +20 -9
data/README.md
CHANGED
@@ -1,62 +1,107 @@
|
|
1
1
|
# ScopeChain
|
2
2
|
|
3
|
-
ScopeChain is a tiny
|
3
|
+
ScopeChain is a tiny helper class useful for testing ActiveRecord model scope usage
|
4
4
|
and scope chaining
|
5
5
|
|
6
6
|
# Usage
|
7
7
|
|
8
8
|
For example, say you have:
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
```ruby
|
10
|
+
class User < ActiveRecord::Base
|
11
|
+
end
|
12
|
+
```
|
12
13
|
|
13
14
|
And you use that model:
|
15
|
+
```ruby
|
16
|
+
def some_method
|
17
|
+
User.where(active: 1).order("created_at desc")
|
18
|
+
end
|
19
|
+
```
|
14
20
|
|
15
|
-
|
16
|
-
User.where(active: 1).order("created_at desc")
|
17
|
-
end
|
18
|
-
|
19
|
-
And you want to test that method, but without setting creating data in your database,
|
21
|
+
And you want to test that method, but without creating data in your database,
|
20
22
|
or actually using the data base.
|
21
23
|
|
22
24
|
Using ScopeChain, you do:
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
25
|
+
```ruby
|
26
|
+
context "my method" do
|
27
|
+
it "gets the right users" do
|
28
|
+
ScopeChain.for(User).where(active: 1).order("created_at desc")
|
29
|
+
some_method
|
30
|
+
end
|
31
|
+
end
|
32
|
+
```
|
31
33
|
|
32
34
|
What this will do is setup some expectations that make sure those scope methods are called.
|
33
35
|
|
34
36
|
You can do "manual" scopes:
|
37
|
+
```ruby
|
38
|
+
def manual_scopes
|
39
|
+
User.scoped.where("something = else")
|
40
|
+
end
|
35
41
|
|
36
|
-
|
37
|
-
|
38
|
-
|
42
|
+
it "does the right thing" do
|
43
|
+
ScopeChain.for(User).where("something = else")
|
44
|
+
manual_scopes
|
45
|
+
end
|
46
|
+
```
|
39
47
|
|
40
|
-
|
41
|
-
|
48
|
+
You can test return values:
|
49
|
+
```ruby
|
50
|
+
def return_values
|
51
|
+
User.where("thing = 1")
|
52
|
+
end
|
42
53
|
|
43
|
-
|
44
|
-
|
54
|
+
it "returns properly" do
|
55
|
+
ScopeChain.for(User).where("thing = 1").returns(5)
|
45
56
|
|
46
|
-
|
57
|
+
return_values.should eq(5)
|
58
|
+
end
|
59
|
+
```
|
60
|
+
|
61
|
+
You can test associations on individual model instances:
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
class Model < ActiveRecord::Base
|
65
|
+
end
|
66
|
+
|
67
|
+
class Owner < ActiveRecord::Base
|
68
|
+
has_many :models
|
69
|
+
|
70
|
+
def my_method
|
71
|
+
models.create(column: 5)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_my_method
|
76
|
+
owner = Owner.new
|
77
|
+
ScopeChain.on(owner).as(:models).create(column: 5)
|
78
|
+
|
79
|
+
owner.my_method
|
80
|
+
end
|
81
|
+
```
|
82
|
+
|
83
|
+
You can test named scopes (for execution, not for what they do):
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
class Model < ActiveRecord::Base
|
87
|
+
scope :active, where("1 = 1")
|
88
|
+
|
89
|
+
def self.my_method
|
90
|
+
active.order("id")
|
91
|
+
end
|
47
92
|
|
48
|
-
|
49
|
-
|
50
|
-
end
|
93
|
+
it "should be ordered" do
|
94
|
+
ScopeChain.for(Model).active.order("id").returns("abc")
|
51
95
|
|
52
|
-
|
53
|
-
|
96
|
+
Model.active.should eq("abc")
|
97
|
+
end
|
98
|
+
```
|
54
99
|
|
55
|
-
|
56
|
-
|
100
|
+
Note that the named scope support does dirty things to the ActiveRecord namespace,
|
101
|
+
which means you *really should* have "require: false" in your Gemfile.
|
57
102
|
|
103
|
+
Like, for reals.
|
58
104
|
|
59
|
-
Not in order, but called, which is something, right?
|
60
105
|
|
61
106
|
## Known Issues
|
62
107
|
|
data/lib/scope_chain/version.rb
CHANGED
data/lib/scope_chain.rb
CHANGED
@@ -5,14 +5,50 @@ module ScopeChain
|
|
5
5
|
Chain.new klass, &block
|
6
6
|
end
|
7
7
|
|
8
|
+
def self.on(instance)
|
9
|
+
AssociationChain.new instance
|
10
|
+
end
|
11
|
+
|
12
|
+
class AssociationChain
|
13
|
+
attr_accessor :instance, :association
|
14
|
+
|
15
|
+
def initialize(instance)
|
16
|
+
self.instance = instance
|
17
|
+
end
|
18
|
+
|
19
|
+
def as(association_name)
|
20
|
+
self.association = instance.class.reflect_on_association(association_name)
|
21
|
+
|
22
|
+
chain
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def association_name(klass)
|
27
|
+
klass.name.underscore.pluralize
|
28
|
+
end
|
29
|
+
|
30
|
+
def chain
|
31
|
+
ScopeChain::Chain.new(association.klass).tap do |chain|
|
32
|
+
instance.stubs(association.name => association.klass)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
8
37
|
class Chain
|
9
|
-
LINKS = [:select, :where, :includes, :order]
|
38
|
+
LINKS = [:select, :where, :includes, :order, :find, :sum, :new, :create, :create!]
|
39
|
+
ALIASES = {}
|
40
|
+
|
41
|
+
class ConflictedExistenceError < StandardError
|
42
|
+
end
|
10
43
|
|
11
|
-
attr_accessor :klass
|
44
|
+
attr_accessor :klass
|
12
45
|
def initialize(klass, &block)
|
13
46
|
self.klass = klass
|
14
|
-
|
15
|
-
|
47
|
+
@expectations = []
|
48
|
+
@exists_condition = false
|
49
|
+
|
50
|
+
link_manual_scopes
|
51
|
+
link_named_scopes
|
16
52
|
|
17
53
|
yield self if block_given?
|
18
54
|
end
|
@@ -23,6 +59,12 @@ module ScopeChain
|
|
23
59
|
end
|
24
60
|
end
|
25
61
|
|
62
|
+
ALIASES.each do |source, dest|
|
63
|
+
define_method(source) do |*arguments|
|
64
|
+
add_link dest, *arguments
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
26
68
|
def all(returned)
|
27
69
|
add_link :all
|
28
70
|
returns returned
|
@@ -30,20 +72,68 @@ module ScopeChain
|
|
30
72
|
|
31
73
|
def returns(object)
|
32
74
|
# DON'T LOOK
|
33
|
-
expectations.last.instance_variable_set(:@return_values, Mocha::ReturnValues.build(object))
|
75
|
+
@expectations.last.instance_variable_set(:@return_values, Mocha::ReturnValues.build(object))
|
34
76
|
|
35
77
|
self
|
36
78
|
end
|
37
79
|
|
80
|
+
def exists!
|
81
|
+
set_exists true
|
82
|
+
end
|
83
|
+
|
84
|
+
def missing!
|
85
|
+
set_exists false
|
86
|
+
end
|
87
|
+
|
38
88
|
private
|
39
89
|
def add_link(name, *arguments)
|
40
90
|
expectation = klass.expects(name)
|
41
91
|
expectation.with(*arguments) if arguments.size > 0
|
42
92
|
expectation.returns(klass)
|
43
93
|
|
44
|
-
expectations << expectation
|
94
|
+
@expectations << expectation
|
45
95
|
|
46
96
|
self
|
47
97
|
end
|
98
|
+
|
99
|
+
def set_exists(value)
|
100
|
+
if @exists_condition
|
101
|
+
raise ConflictedExistenceError.new("Can only set one 'exists' conditions, #missing! or #exists!")
|
102
|
+
end
|
103
|
+
|
104
|
+
klass.expects(exists?: value)
|
105
|
+
@exists_condition = true
|
106
|
+
|
107
|
+
self
|
108
|
+
end
|
109
|
+
|
110
|
+
def link_manual_scopes
|
111
|
+
klass.stubs(scoped: klass) # Handle manual scope building
|
112
|
+
end
|
113
|
+
|
114
|
+
def link_named_scopes
|
115
|
+
return unless klass.respond_to?(:scopes)
|
116
|
+
|
117
|
+
klass.scopes[klass.name].each do |named|
|
118
|
+
self.define_singleton_method(named) do |*arguments|
|
119
|
+
add_link named, *arguments
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
48
124
|
end
|
49
125
|
end
|
126
|
+
|
127
|
+
# Hooks so named scopes are sane
|
128
|
+
class ActiveRecord::Base
|
129
|
+
cattr_reader :scopes
|
130
|
+
def self.scope_with_tracking(*args, &block)
|
131
|
+
(@@scopes ||= Hash.new {|hash, key| hash[key] = [] })[self.name].push args.first
|
132
|
+
scope_without_tracking *args, &block
|
133
|
+
end
|
134
|
+
|
135
|
+
class << self
|
136
|
+
alias_method_chain :scope, :tracking
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
data/scope_chain.gemspec
CHANGED
@@ -18,7 +18,8 @@ Gem::Specification.new do |gem|
|
|
18
18
|
gem.require_paths = ["lib"]
|
19
19
|
|
20
20
|
gem.add_dependency 'mocha'
|
21
|
+
gem.add_dependency 'activerecord', '~> 3.1.0'
|
21
22
|
|
22
23
|
gem.add_development_dependency 'rspec'
|
23
|
-
gem.add_development_dependency '
|
24
|
+
gem.add_development_dependency 'sqlite3'
|
24
25
|
end
|
data/spec/scope_chain_spec.rb
CHANGED
@@ -5,10 +5,27 @@ require 'active_record'
|
|
5
5
|
require 'mocha/api'
|
6
6
|
require 'scope_chain'
|
7
7
|
|
8
|
+
ActiveRecord::Base.establish_connection({
|
9
|
+
adapter: 'sqlite3',
|
10
|
+
database: ":memory:",
|
11
|
+
verbosity: 'quiet'
|
12
|
+
})
|
13
|
+
|
14
|
+
ActiveRecord::Base.connection.create_table :models do |table|
|
15
|
+
table.belongs_to :owners
|
16
|
+
table.integer :column
|
17
|
+
end
|
18
|
+
|
19
|
+
ActiveRecord::Base.connection.create_table :owners
|
20
|
+
|
8
21
|
class Model < ActiveRecord::Base
|
9
22
|
scope :active, where("column = value")
|
10
23
|
end
|
11
24
|
|
25
|
+
class Owner < ActiveRecord::Base
|
26
|
+
has_many :models
|
27
|
+
end
|
28
|
+
|
12
29
|
describe ScopeChain do
|
13
30
|
let(:klass) { Class.new }
|
14
31
|
|
@@ -54,6 +71,12 @@ describe ScopeChain::Chain do
|
|
54
71
|
klass.scoped.select("id")
|
55
72
|
end
|
56
73
|
|
74
|
+
it "works with the alias scopes" do
|
75
|
+
subject.new(5)
|
76
|
+
|
77
|
+
klass.new(5)
|
78
|
+
end
|
79
|
+
|
57
80
|
describe "#returns" do
|
58
81
|
it "modifies the last expectation" do
|
59
82
|
subject.select("id").where("5").returns("9")
|
@@ -83,6 +106,22 @@ describe ScopeChain::Chain do
|
|
83
106
|
end
|
84
107
|
end
|
85
108
|
|
109
|
+
describe "#exists!" do
|
110
|
+
it "exists" do
|
111
|
+
subject.exists!
|
112
|
+
|
113
|
+
klass.should be_exists
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe "#missing!" do
|
118
|
+
it "does not exist" do
|
119
|
+
subject.missing!
|
120
|
+
|
121
|
+
klass.should_not be_exists
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
86
125
|
describe "with a custom scopes" do
|
87
126
|
it "fails properly" do
|
88
127
|
pending
|
@@ -99,6 +138,23 @@ describe ScopeChain::Chain do
|
|
99
138
|
|
100
139
|
Model.active.to_a
|
101
140
|
end
|
141
|
+
|
142
|
+
it "sets expectations on the named scope" do
|
143
|
+
ScopeChain.for(Model).active.returns(:active)
|
144
|
+
|
145
|
+
Model.active.should eq(:active)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
context "with associations" do
|
150
|
+
describe "has_many" do
|
151
|
+
it "properly sets stuff up" do
|
152
|
+
source = Owner.new
|
153
|
+
ScopeChain.on(source).as(:models).new(column: 5).returns(:abc)
|
154
|
+
|
155
|
+
source.models.new(column: 5).should eq(:abc)
|
156
|
+
end
|
157
|
+
end
|
102
158
|
end
|
103
159
|
|
104
160
|
end
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: scope_chain
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.0.
|
5
|
+
version: 0.0.6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Jon Moses
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2013-03-
|
13
|
+
date: 2013-03-18 00:00:00 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: mocha
|
@@ -24,27 +24,38 @@ dependencies:
|
|
24
24
|
type: :runtime
|
25
25
|
version_requirements: *id001
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
|
-
name:
|
27
|
+
name: activerecord
|
28
28
|
prerelease: false
|
29
29
|
requirement: &id002 !ruby/object:Gem::Requirement
|
30
30
|
none: false
|
31
31
|
requirements:
|
32
|
-
- -
|
32
|
+
- - ~>
|
33
33
|
- !ruby/object:Gem::Version
|
34
|
-
version:
|
35
|
-
type: :
|
34
|
+
version: 3.1.0
|
35
|
+
type: :runtime
|
36
36
|
version_requirements: *id002
|
37
37
|
- !ruby/object:Gem::Dependency
|
38
|
-
name:
|
38
|
+
name: rspec
|
39
39
|
prerelease: false
|
40
40
|
requirement: &id003 !ruby/object:Gem::Requirement
|
41
41
|
none: false
|
42
42
|
requirements:
|
43
|
-
- -
|
43
|
+
- - ">="
|
44
44
|
- !ruby/object:Gem::Version
|
45
|
-
version:
|
45
|
+
version: "0"
|
46
46
|
type: :development
|
47
47
|
version_requirements: *id003
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: sqlite3
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
type: :development
|
58
|
+
version_requirements: *id004
|
48
59
|
description: Easy testing of scope usage
|
49
60
|
email:
|
50
61
|
- jon@burningbush.us
|