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