ar_attr_lazy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,107 @@
1
+ require 'rubygems'
2
+
3
+ require 'pp'
4
+
5
+ if ENV["AR_VERSION"]
6
+ gem 'activerecord', "= #{ENV["AR_VERSION"]}"
7
+ end
8
+ require 'activerecord'
9
+ require 'active_record/version'
10
+ ActiveRecord::Base.establish_connection(
11
+ "adapter" => "sqlite3",
12
+ "database" => ":memory:"
13
+ )
14
+
15
+ gem 'mcmire-protest'
16
+ require 'protest'
17
+ gem 'mcmire-matchy'
18
+ require 'matchy'
19
+ gem 'mcmire-mocha'
20
+ require 'mocha'
21
+ require 'mocha-protest-integration'
22
+
23
+ Protest.report_with :documentation
24
+ #Protest::Utils::BacktraceFilter::ESCAPE_PATHS << %r|test/unit| << %r|matchy| << %r|mocha-protest-integration|
25
+ Protest::Utils::BacktraceFilter::ESCAPE_PATHS.clear
26
+
27
+ #------------------------
28
+
29
+ module Protest
30
+ class TestCase
31
+ def full_name
32
+ self.class.description + " " + self.name
33
+ end
34
+
35
+ class TestWrapper #:nodoc:
36
+ attr_reader :name
37
+
38
+ def initialize(type, test_case)
39
+ @type = type
40
+ @test = test_case
41
+ @name = "Global #{@type} for #{test_case.description}"
42
+ end
43
+
44
+ def run(report)
45
+ @test.send("do_global_#{@type}")
46
+ end
47
+
48
+ def full_name
49
+ @name
50
+ end
51
+ end
52
+ end
53
+
54
+ module TestWithErrors
55
+ def file
56
+ file_and_line[0]
57
+ end
58
+
59
+ def line
60
+ file_and_line[1]
61
+ end
62
+
63
+ def file_and_line
64
+ backtrace.find {|x| x =~ %r{^.*/test/(.*_test|test_.*)\.rb} }.split(":")[0..1]
65
+ end
66
+ end
67
+
68
+ module Utils
69
+ module Summaries
70
+ def summarize_errors
71
+ return if failures_and_errors.empty?
72
+
73
+ puts "Failures:"
74
+ puts
75
+
76
+ pad_indexes = failures_and_errors.size.to_s.size
77
+ failures_and_errors.each_with_index do |error, index|
78
+ colorize_as = ErroredTest === error ? :errored : :failed
79
+ # PATCH: test.full_name
80
+ puts " #{pad(index+1, pad_indexes)}) #{test_type(error)} in `#{error.test.full_name}' (on line #{error.line} of `#{error.file}')", colorize_as
81
+ # If error message has line breaks, indent the message
82
+ prefix = "with"
83
+ unless error.error.is_a?(Protest::AssertionFailed) ||
84
+ ((RUBY_VERSION =~ /^1\.9/) ? error.error.is_a?(MiniTest::Assertion) : error.error.is_a?(::Test::Unit::AssertionFailedError))
85
+ prefix << " #{error.error.class}"
86
+ end
87
+ if error.error_message =~ /\n/
88
+ puts indent("#{prefix}: <<", 6 + pad_indexes), colorize_as
89
+ puts indent(error.error_message, 6 + pad_indexes + 2), colorize_as
90
+ puts indent(">>", 6 + pad_indexes), colorize_as
91
+ else
92
+ puts indent("#{prefix} `#{error.error_message}'", 6 + pad_indexes), colorize_as
93
+ end
94
+ indent(error.backtrace, 6 + pad_indexes).each {|backtrace| puts backtrace, colorize_as }
95
+ puts
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+
102
+ #------------------------
103
+
104
+ require 'matchers'
105
+ require 'factories'
106
+
107
+ require 'mcmire/ar_attr_lazy'
@@ -0,0 +1,172 @@
1
+ module MatchyMatchers
2
+ # Ported from an RSpec matcher
3
+ # from https://rspec.lighthouseapp.com/projects/5645/tickets/896-lambda-should-query-matcher
4
+ # with a few tweaks
5
+ class ArQuery #:nodoc:
6
+ cattr_accessor :executed
7
+
8
+ @@recording_queries = false
9
+ def self.recording_queries?
10
+ @@recording_queries
11
+ end
12
+
13
+ def initialize(test_case, expecteds, &block)
14
+ @test_case = test_case
15
+ @expecteds = expecteds
16
+ @expecteds = [1] if @expecteds.empty?
17
+ @block = block
18
+ end
19
+
20
+ def matches?(given_proc)
21
+ @eval_block = false
22
+ @eval_error = nil
23
+ ArQuery.executed = []
24
+ @@recording_queries = true
25
+
26
+ given_proc.call
27
+
28
+ if @expecteds[0].is_a?(Fixnum)
29
+ @expecteds = @expecteds[0]
30
+ @actuals = ArQuery.executed.length
31
+ @matched = (@actuals == @expecteds)
32
+ else
33
+ # assume that a block was not given
34
+ # PATCH: accept multiple queries
35
+ @expecteds = Array(@expecteds)
36
+ @actuals = @expecteds.map {|query| [query, ArQuery.executed.detect {|sql| query === sql }] }
37
+ @matched = @actuals.all? {|e,a| a }
38
+ end
39
+
40
+ eval_block if @block && @matched && !negative_expectation?
41
+
42
+ @matched && @eval_error.nil?
43
+
44
+ ensure
45
+ #ArQuery.executed = nil
46
+ @@recording_queries = false
47
+ end
48
+
49
+ # This is necessary for interoperability with Matchy
50
+ def fail!(which)
51
+ @test_case.flunk(which ? failure_message_for_should : failure_message_for_should_not)
52
+ end
53
+
54
+ # This is necessary for interoperability with Matchy
55
+ def pass!(which)
56
+ @test_case.assert true
57
+ end
58
+
59
+ def eval_block
60
+ @eval_block = true
61
+ begin
62
+ @block.call(ArQuery.executed)
63
+ rescue Exception => err
64
+ @eval_error = err
65
+ end
66
+ end
67
+
68
+ def failure_message_for_should
69
+ if @eval_error
70
+ @eval_error.message
71
+ elsif @expecteds.is_a?(Fixnum)
72
+ "expected #{@expecteds} to be executed, when in fact #{@actuals} were"
73
+ else
74
+ # PATCH: better error message
75
+ msg = ""
76
+ @actuals.select {|e,a| !a }.each do |expected, _|
77
+ msg << "expected a query with pattern #{expected.inspect} to be executed, but it wasn't\n"
78
+ end
79
+ msg << "All queries executed:\n"
80
+ ArQuery.executed.each do |query|
81
+ msg << " - #{query}\n"
82
+ end
83
+ msg
84
+ end
85
+ end
86
+
87
+ def failure_message_for_should_not
88
+ if @expecteds.is_a?(Fixnum)
89
+ "did not expect #{@expecteds} queries to be executed, but they were"
90
+ else
91
+ # PATCH: better error message
92
+ msg = ""
93
+ @actuals.select {|e,a| a }.each do |_, actual|
94
+ msg << "expected a query with pattern #{actual.inspect} not to be executed, but it was\n"
95
+ end
96
+ msg << "All queries executed:\n"
97
+ ArQuery.executed.each do |query|
98
+ msg << " - #{query}\n"
99
+ end
100
+ msg
101
+ end
102
+ end
103
+
104
+ #def description
105
+ # if @expecteds.is_a?(Fixnum)
106
+ # @expecteds == 1 ? "execute 1 query" : "execute #{@expecteds} queries"
107
+ # else
108
+ # "execute query with pattern #{@expecteds.inspect}"
109
+ # end
110
+ #end
111
+
112
+ # Copied from raise_error
113
+ def negative_expectation?
114
+ @negative_expectation ||= !caller.first(3).find { |s| s =~ /should_not/ }.nil?
115
+ end
116
+ end
117
+
118
+ # :call-seq:
119
+ # response.should query
120
+ # response.should query(expected)
121
+ # response.should query(expected1, expected2)
122
+ # response.should query(expected) { |sql| ... }
123
+ # response.should_not query
124
+ # response.should_not query(expected)
125
+ # response.should_not query(expected1, expected2)
126
+ #
127
+ # Accepts a Fixnum, a String, a Regexp, or an array of Strings or Regexps as arguments.
128
+ #
129
+ # With no args, matches if exactly 1 query is executed.
130
+ # With a Fixnum arg, matches if the number of queries executed equals the given number.
131
+ # With a Regexp arg, matches if any query is executed with the given pattern.
132
+ # With multiple args, matches if all given patterns are matched by all queries executed.
133
+ #
134
+ # Pass an optional block to perform extra verifications of the queries matched.
135
+ # The argument of the block will receive an array of query strings that were executed.
136
+ #
137
+ # == Examples
138
+ #
139
+ # lambda { @object.posts }.should query # same as `should query(1)`
140
+ # lambda { @object.valid? }.should query(0)
141
+ # lambda { @object.save }.should query(3)
142
+ # lambda { @object.line_items }.should query("SELECT DISTINCT")
143
+ # lambda { @object.line_items }.should query(/SELECT DISTINCT/)
144
+ # lambda { @object.line_items }.should query(/SELECT DISTINCT/, /SELECT COUNT\(\*\)/)
145
+ # lambda { @object.line_items }.should query(1) { |sql| sql[0].should =~ /SELECT DISTINCT/ }
146
+ #
147
+ # lambda { @object.posts }.should_not query # same as `should_not query(1)`
148
+ # lambda { @object.valid? }.should_not query(0)
149
+ # lambda { @object.save }.should_not query(3)
150
+ # lambda { @object.line_items }.should_not query(/SELECT DISTINCT/)
151
+ # lambda { @object.line_items }.should_not query(/SELECT DISTINCT/, /SELECT COUNT\(\*\)/)
152
+ #
153
+ def query(*expecteds, &block)
154
+ ArQuery.new(self, expecteds, &block)
155
+ end
156
+
157
+ unless defined?(IGNORED_SQL)
158
+ # From active_record/test/cases/helper.rb :
159
+ ::ActiveRecord::Base.connection.class.class_eval do
160
+ IGNORED_SQL = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /SHOW FIELDS/]
161
+ def execute_with_query_record(sql, name = nil, &block)
162
+ if ArQuery.recording_queries?
163
+ # PATCH: squeeze and strip
164
+ ArQuery.executed << sql.squeeze(" ").strip unless IGNORED_SQL.any? { |ignore| sql =~ ignore }
165
+ end
166
+ execute_without_query_record(sql, name, &block)
167
+ end
168
+ alias_method_chain :execute, :query_record
169
+ end
170
+ end
171
+ end
172
+ Protest::TestCase.class_eval { include MatchyMatchers }
@@ -0,0 +1,324 @@
1
+ require 'helper'
2
+
3
+ # what about STI? (do the lazy attributes carry over?)
4
+
5
+ Protest.context "for a model that doesn't have lazy attributes" do
6
+ global_setup do
7
+ load File.dirname(__FILE__) + '/setup_migration.rb'
8
+ load File.dirname(__FILE__) + '/setup_tables_for_not_using.rb'
9
+ Account.make! do |account|
10
+ User.make!(:account => account) do |user|
11
+ Avatar.make!(:user => user)
12
+ Post.make!(:author => user) do |post|
13
+ Comment.make!(:post => post)
14
+ post.tags << Tag.make
15
+ post.categories << Category.make
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ global_teardown do
22
+ ObjectSpace.each_object(Class) do |klass|
23
+ Object.remove_class(klass) if klass < ActiveRecord::Base
24
+ end
25
+ end
26
+
27
+ def regex(str)
28
+ Regexp.new(Regexp.escape(str))
29
+ end
30
+
31
+ context "with no associations involved" do
32
+ test "find selects all attributes by default" do
33
+ lambda { Account.find(:first) }.should query(regex(%|SELECT * FROM "accounts"|))
34
+ end
35
+ test "accessing any one attribute doesn't do a query" do
36
+ account = Account.first
37
+ lambda { account.name }.should_not query
38
+ end
39
+ test "find still honors an explicit select option" do
40
+ lambda { Account.find(:first, :select => "name") }.should query(regex(%|SELECT name FROM "accounts"|))
41
+ end
42
+ test "find still honors a select option in a parent scope" do
43
+ lambda {
44
+ Account.send(:with_scope, :find => {:select => "name"}) do
45
+ Account.find(:first)
46
+ end
47
+ }.should query(regex(%|SELECT name FROM "accounts"|))
48
+ end
49
+ if Mcmire::ArAttrLazy.ar_version >= 2.3
50
+ test "find still honors a select option in a default scope" do
51
+ lambda { AccountWithDefaultScope.find(:first) }.should query(regex(%|SELECT name FROM "accounts"|))
52
+ end
53
+ end
54
+ end
55
+
56
+ context "accessing a has_many association" do
57
+ before do
58
+ @post = Post.first
59
+ end
60
+ test "find selects all attributes by default" do
61
+ lambda { @post.comments.find(:first) }.should query(regex(%|SELECT * FROM "comments"|))
62
+ end
63
+ test "find still honors an explicit select option" do
64
+ lambda { @post.comments.find(:first, :select => "name") }.should query(
65
+ regex(%|SELECT name FROM "comments"|)
66
+ )
67
+ end
68
+ test "find still honors a select option in a parent scope" do
69
+ lambda {
70
+ Comment.send(:with_scope, :find => {:select => "name"}) do
71
+ @post.comments.find(:first)
72
+ end
73
+ }.should query(
74
+ regex(%|SELECT name FROM "comments"|)
75
+ )
76
+ end
77
+ if Mcmire::ArAttrLazy.ar_version >= 2.3
78
+ test "find still honors a select option in a default scope" do
79
+ lambda { @post.comments_with_default_scope.find(:first) }.should query(
80
+ regex(%|SELECT name FROM "comments"|)
81
+ )
82
+ end
83
+ end
84
+ test "find still honors a select option in the association definition itself" do
85
+ lambda { @post.comments_with_select.find(:first) }.should query(
86
+ regex(%|SELECT name FROM "comments"|)
87
+ )
88
+ end
89
+ end
90
+
91
+ context "accessing a belongs_to association" do
92
+ test "find selects all attributes by default" do
93
+ post = Post.first
94
+ lambda { post.author }.should query(regex(%|SELECT * FROM "users"|))
95
+ end
96
+ # can't do a find on a belongs_to, so no testing needed for that
97
+ end
98
+
99
+ context "accessing a has_one association" do
100
+ test "find selects all attributes by default" do
101
+ account = Account.first
102
+ lambda { account.user }.should query(regex(%|SELECT * FROM "users"|))
103
+ end
104
+ # can't do a find on a has_one, so no testing needed for that
105
+ end
106
+
107
+ context "accessing a has_and_belongs_to_many association" do
108
+ before do
109
+ @post = Post.first
110
+ end
111
+ test "find selects all attributes by default" do
112
+ lambda { @post.tags.find(:all) }.should query(regex(%|SELECT * FROM "tags"|))
113
+ end
114
+ test "find still honors an explicit select option" do
115
+ lambda { @post.tags.find(:all, :select => "tags.name") }.should query(
116
+ regex(%|SELECT tags.name FROM "tags"|)
117
+ )
118
+ end
119
+ test "find still honors a select option in a parent scope" do
120
+ pending "this fails on Rails 2.3.4"
121
+ lambda {
122
+ Tag.send(:with_scope, :find => {:select => "tags.name"}) do
123
+ @post.tags.find(:all)
124
+ end
125
+ }.should query(
126
+ regex(%|SELECT tags.name FROM "tags"|)
127
+ )
128
+ end
129
+ if Mcmire::ArAttrLazy.ar_version >= 2.3
130
+ test "find still honors a select option in a default scope" do
131
+ pending "this fails on Rails 2.3.4"
132
+ lambda {
133
+ @post.tags_with_default_scope.find(:all)
134
+ }.should query(
135
+ regex(%|SELECT tags.name FROM "tags"|)
136
+ )
137
+ end
138
+ end
139
+ test "find still honors a select option in the association definition itself" do
140
+ lambda {
141
+ @post.tags_with_select.find(:all)
142
+ }.should query(
143
+ regex(%|SELECT tags.name FROM "tags"|)
144
+ )
145
+ end
146
+ end
147
+
148
+ context "accessing a has_many :through association" do
149
+ before do
150
+ @post = Post.first
151
+ end
152
+ test "find selects all attributes by default" do
153
+ lambda { @post.categories.find(:all) }.should query(
154
+ regex(%|SELECT "categories".* FROM "categories"|)
155
+ )
156
+ end
157
+ test "find still honors an explicit select option" do
158
+ lambda { @post.categories.find(:all, :select => "categories.name") }.should query(
159
+ regex(%|SELECT categories.name FROM "categories"|)
160
+ )
161
+ end
162
+ test "find still honors a select option in a parent scope" do
163
+ pending "this fails on Rails 2.3.4"
164
+ lambda {
165
+ Category.send(:with_scope, :find => {:select => "categories.name"}) do
166
+ @post.categories.find(:all)
167
+ end
168
+ }.should query(
169
+ regex(%|SELECT categories.name FROM "categories"|)
170
+ )
171
+ end
172
+ if Mcmire::ArAttrLazy.ar_version >= 2.3
173
+ test "find still honors a select option in a default scope" do
174
+ pending "this fails on Rails 2.3.4"
175
+ lambda {
176
+ @post.categories_with_default_scope.find(:all)
177
+ }.should query(
178
+ regex(%|SELECT categories.name FROM "categories"|)
179
+ )
180
+ end
181
+ end
182
+ test "find still honors a select option in the association definition itself" do
183
+ lambda {
184
+ @post.categories_with_select.find(:all)
185
+ }.should query(
186
+ regex(%|SELECT categories.name FROM "categories"|)
187
+ )
188
+ end
189
+ end
190
+
191
+ # has_one :through didn't work properly prior to 2.3.4 - see LH #2719
192
+ if Mcmire::ArAttrLazy.ar_version >= "2.3.4"
193
+ context "accessing a has_one :through association" do
194
+ test "find selects all attributes by default" do
195
+ account = Account.first
196
+ lambda { account.avatar }.should query(
197
+ regex(%|SELECT "avatars".* FROM "avatars"|)
198
+ )
199
+ end
200
+ # can't do a find on a has_one, so no testing needed for that
201
+ end
202
+ end
203
+
204
+ context "eager loading a has_many association (association preloading)" do
205
+ test "find selects all attributes by default" do
206
+ lambda {
207
+ Post.find(:first, :include => :comments)
208
+ }.should query(regex(%|SELECT * FROM "posts"|), regex(%|SELECT "comments".* FROM "comments"|))
209
+ end
210
+ # can't test for an explicit select since that will force a table join
211
+ # can't test for a scope select since association preloading doesn't honor those
212
+ end
213
+ context "eager loading a has_many association (table join)" do
214
+ test "find selects all attributes by default" do
215
+ lambda {
216
+ Post.find(:first, :include => :comments, :order => "comments.id")
217
+ }.should query(%r{"posts"\."body"}, %r{"comments"\."body"})
218
+ end
219
+ # can't test for an explicit select since that clashes with the table join anyway
220
+ # can't test for a scope for the same reason
221
+ end
222
+
223
+ context "eager loading a has_one association (association preloading)" do
224
+ test "find selects all attributes by default" do
225
+ lambda { Account.find(:first, :include => :user) }.should query(regex(%|SELECT "users".* FROM "users"|))
226
+ end
227
+ # can't test for an explicit select since that will force a table join
228
+ # can't test for a scope select since association preloading doesn't honor those
229
+ end
230
+ context "eager loading a has_one association (table join)" do
231
+ test "find selects all attributes by default" do
232
+ lambda {
233
+ Account.find(:first, :include => :user, :order => "users.id")
234
+ }.should query(%r{"users"\."bio"})
235
+ end
236
+ # can't test for an explicit select since that clashes with the table join anyway
237
+ # can't test for a scope for the same reason
238
+ end
239
+
240
+ context "eager loading a belongs_to association (association preloading)" do
241
+ test "find selects all attributes by default" do
242
+ lambda {
243
+ Post.find(:first, :include => :author)
244
+ }.should query(regex(%|SELECT * FROM "posts"|))
245
+ end
246
+ # can't test for an explicit select since that will force a table join
247
+ # can't test for a scope select since association preloading doesn't honor those
248
+ end
249
+ context "eager loading a belongs_to association (table join)" do
250
+ test "find selects all attributes by default" do
251
+ lambda {
252
+ Post.find(:first, :include => :author, :order => "users.id")
253
+ }.should query(%r{"posts"\."(body|summary)"}, %r{"users"\."bio"})
254
+ end
255
+ # can't test for an explicit select since that clashes with the table join anyway
256
+ # can't test for a scope for the same reason
257
+ end
258
+
259
+ context "eager loading a has_and_belongs_to_many association (association preloading)" do
260
+ test "find selects all attributes by default" do
261
+ lambda {
262
+ Post.find(:first, :include => :tags)
263
+ }.should query(regex(%|SELECT * FROM "posts"|), regex(%|SELECT "tags".*, t0.post_id as the_parent_record_id FROM "tags"|))
264
+ end
265
+ # can't test for an explicit select since that will force a table join
266
+ # can't test for a scope select since association preloading doesn't honor those
267
+ end
268
+ context "eager loading a has_and_belongs_to_many association (table join)" do
269
+ test "find selects all attributes by default" do
270
+ lambda {
271
+ Post.find(:first, :include => :tags, :order => "tags.id")
272
+ }.should query(%r{"posts"\."(body|summary)"}, %r{"tags"\."description"})
273
+ end
274
+ # can't test for an explicit select since that clashes with the table join anyway
275
+ # can't test for a scope for the same reason
276
+ end
277
+
278
+ context "eager loading a has_many :through association (association preloading)" do
279
+ test "find selects all attributes by default" do
280
+ lambda {
281
+ Post.find(:first, :include => :categories)
282
+ }.should query(
283
+ regex(%|SELECT * FROM "categories"|)
284
+ )
285
+ end
286
+ # can't test for an explicit select since that will force a table join
287
+ # can't test for a scope select since association preloading doesn't honor those
288
+ end
289
+ context "eager loading a has_many :through association (table join)" do
290
+ test "find selects all attributes by default" do
291
+ lambda {
292
+ Post.find(:first, :include => :categories, :order => "categories.id")
293
+ }.should query(
294
+ regex(%|SELECT "posts"."id" AS t0_r0, "posts"."type" AS t0_r1, "posts"."author_id" AS t0_r2, "posts"."title" AS t0_r3, "posts"."permalink" AS t0_r4, "posts"."body" AS t0_r5, "posts"."summary" AS t0_r6, "categories"."id" AS t1_r0, "categories"."type" AS t1_r1, "categories"."name" AS t1_r2, "categories"."description" AS t1_r3 FROM "posts"|)
295
+ )
296
+ end
297
+ # can't test for an explicit select since that clashes with the table join anyway
298
+ # can't test for a scope for the same reason
299
+ end
300
+
301
+ # has_one :through didn't work properly prior to 2.3.4 - see LH #2719
302
+ if Mcmire::ArAttrLazy.ar_version >= "2.3.4"
303
+ context "eager loading a has_one :through association (association preloading)" do
304
+ test "find selects all attributes by default" do
305
+ lambda { Account.find(:first, :include => :avatar) }.should query(
306
+ regex(%|SELECT "avatars".* FROM "avatars"|)
307
+ )
308
+ end
309
+ # can't test for an explicit select since that will force a table join
310
+ # can't test for a scope select since association preloading doesn't honor those
311
+ end
312
+ context "eager loading a has_one :through association (table join)" do
313
+ test "find selects all attributes by default" do
314
+ pending "this is failing for some reason!"
315
+ lambda {
316
+ Account.find(:first, :include => :avatar, :order => "avatars.filename")
317
+ }.should query(%|SELECT "accounts"."id" AS t0_r0, "accounts"."name" AS t0_r1, "avatars"."id" AS t1_r0, "avatars"."user_id" AS t1_r1, "avatars"."filename" AS t1_r2, "avatars"."data" AS t1_r3 FROM "accounts"|)
318
+ end
319
+ # can't test for an explicit select since that clashes with the table join anyway
320
+ # can't test for a scope for the same reason
321
+ end
322
+ end
323
+
324
+ end