scope_chain 0.0.3 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,62 +1,107 @@
1
1
  # ScopeChain
2
2
 
3
- ScopeChain is a tiny help class useful for testing ActiveRecord model scope usage
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
- class User < ActiveRecord::Base
11
- end
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
- def some_method
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
- context "my method" do
25
- it "gets the right users" do
26
- ScopeChain.for(User).where(active: 1).order("created_at desc")
27
-
28
- some_method
29
- end
30
- end
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
- def manual_scopes
37
- User.scoped.where("somethign = else")
38
- end
42
+ it "does the right thing" do
43
+ ScopeChain.for(User).where("something = else")
44
+ manual_scopes
45
+ end
46
+ ```
39
47
 
40
- it "does the right thing" do
41
- ScopeChain.for(User).where("something = else")
48
+ You can test return values:
49
+ ```ruby
50
+ def return_values
51
+ User.where("thing = 1")
52
+ end
42
53
 
43
- manual_scopes
44
- end
54
+ it "returns properly" do
55
+ ScopeChain.for(User).where("thing = 1").returns(5)
45
56
 
46
- You can test return values:
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
- def return_values
49
- User.where("thing = 1")
50
- end
93
+ it "should be ordered" do
94
+ ScopeChain.for(Model).active.order("id").returns("abc")
51
95
 
52
- it "returns properly" do
53
- ScopeChain.for(User).where("thing = 1").returns(5)
96
+ Model.active.should eq("abc")
97
+ end
98
+ ```
54
99
 
55
- return_values.should eq(5)
56
- end
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
 
@@ -1,3 +1,3 @@
1
1
  module ScopeChain
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.6"
3
3
  end
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, :expectations
44
+ attr_accessor :klass
12
45
  def initialize(klass, &block)
13
46
  self.klass = klass
14
- self.expectations = []
15
- self.klass.stubs(scoped: klass) # Handle manual scope building
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 'activerecord', '~> 3.1.11'
24
+ gem.add_development_dependency 'sqlite3'
24
25
  end
@@ -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.3
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-15 00:00:00 Z
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: rspec
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: "0"
35
- type: :development
34
+ version: 3.1.0
35
+ type: :runtime
36
36
  version_requirements: *id002
37
37
  - !ruby/object:Gem::Dependency
38
- name: activerecord
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: 3.1.11
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