aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/test/cases/scoping/default_scoping_test.rb
blob: cd7d91ff856607de7436caf52a93616ec0c81bee (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
require 'cases/helper'
require 'models/post'
require 'models/developer'

class DefaultScopingTest < ActiveRecord::TestCase
  fixtures :developers, :posts

  def test_default_scope
    expected = Developer.all.merge!(:order => 'salary DESC').to_a.collect { |dev| dev.salary }
    received = DeveloperOrderedBySalary.all.collect { |dev| dev.salary }
    assert_equal expected, received
  end

  def test_default_scope_as_class_method
    assert_equal [developers(:david).becomes(ClassMethodDeveloperCalledDavid)], ClassMethodDeveloperCalledDavid.all
  end

  def test_default_scope_as_class_method_referencing_scope
    assert_equal [developers(:david).becomes(ClassMethodReferencingScopeDeveloperCalledDavid)], ClassMethodReferencingScopeDeveloperCalledDavid.all
  end

  def test_default_scope_as_block_referencing_scope
    assert_equal [developers(:david).becomes(LazyBlockReferencingScopeDeveloperCalledDavid)], LazyBlockReferencingScopeDeveloperCalledDavid.all
  end

  def test_default_scope_with_lambda
    assert_equal [developers(:david).becomes(LazyLambdaDeveloperCalledDavid)], LazyLambdaDeveloperCalledDavid.all
  end

  def test_default_scope_with_block
    assert_equal [developers(:david).becomes(LazyBlockDeveloperCalledDavid)], LazyBlockDeveloperCalledDavid.all
  end

  def test_default_scope_with_callable
    assert_equal [developers(:david).becomes(CallableDeveloperCalledDavid)], CallableDeveloperCalledDavid.all
  end

  def test_default_scope_is_unscoped_on_find
    assert_equal 1, DeveloperCalledDavid.count
    assert_equal 11, DeveloperCalledDavid.unscoped.count
  end

  def test_default_scope_is_unscoped_on_create
    assert_nil DeveloperCalledJamis.unscoped.create!.name
  end

  def test_default_scope_with_conditions_string
    assert_equal Developer.where(name: 'David').map(&:id).sort, DeveloperCalledDavid.all.map(&:id).sort
    assert_equal nil, DeveloperCalledDavid.create!.name
  end

  def test_default_scope_with_conditions_hash
    assert_equal Developer.where(name: 'Jamis').map(&:id).sort, DeveloperCalledJamis.all.map(&:id).sort
    assert_equal 'Jamis', DeveloperCalledJamis.create!.name
  end

  def test_default_scoping_with_threads
    skip "in-memory database mustn't disconnect" if in_memory_db?

    2.times do
      Thread.new {
        assert DeveloperOrderedBySalary.all.to_sql.include?('salary DESC')
        DeveloperOrderedBySalary.connection.close
      }.join
    end
  end

  def test_default_scope_with_inheritance
    wheres = InheritedPoorDeveloperCalledJamis.all.where_values_hash
    assert_equal "Jamis", wheres['name']
    assert_equal 50000,   wheres['salary']
  end

  def test_default_scope_with_module_includes
    wheres = ModuleIncludedPoorDeveloperCalledJamis.all.where_values_hash
    assert_equal "Jamis", wheres['name']
    assert_equal 50000,   wheres['salary']
  end

  def test_default_scope_with_multiple_calls
    wheres = MultiplePoorDeveloperCalledJamis.all.where_values_hash
    assert_equal "Jamis", wheres['name']
    assert_equal 50000,   wheres['salary']
  end

  def test_scope_overwrites_default
    expected = Developer.all.merge!(order: 'salary DESC, name DESC').to_a.collect { |dev| dev.name }
    received = DeveloperOrderedBySalary.by_name.to_a.collect { |dev| dev.name }
    assert_equal expected, received
  end

  def test_reorder_overrides_default_scope_order
    expected = Developer.order('name DESC').collect { |dev| dev.name }
    received = DeveloperOrderedBySalary.reorder('name DESC').collect { |dev| dev.name }
    assert_equal expected, received
  end

  def test_order_after_reorder_combines_orders
    expected = Developer.order('name DESC, id DESC').collect { |dev| [dev.name, dev.id] }
    received = Developer.order('name ASC').reorder('name DESC').order('id DESC').collect { |dev| [dev.name, dev.id] }
    assert_equal expected, received
  end

  def test_unscope_overrides_default_scope
    expected = Developer.all.collect { |dev| [dev.name, dev.id] }
    received = Developer.order('name ASC, id DESC').unscope(:order).collect { |dev| [dev.name, dev.id] }
    assert_equal expected, received
  end

  def test_unscope_after_reordering_and_combining
    expected = Developer.order('id DESC, name DESC').collect { |dev| [dev.name, dev.id] }
    received = DeveloperOrderedBySalary.reorder('name DESC').unscope(:order).order('id DESC, name DESC').collect { |dev| [dev.name, dev.id] }
    assert_equal expected, received

    expected_2 = Developer.all.collect { |dev| [dev.name, dev.id] }
    received_2 = Developer.order('id DESC, name DESC').unscope(:order).collect { |dev| [dev.name, dev.id] }
    assert_equal expected_2, received_2

    expected_3 = Developer.all.collect { |dev| [dev.name, dev.id] }
    received_3 = Developer.reorder('name DESC').unscope(:order).collect { |dev| [dev.name, dev.id] }
    assert_equal expected_3, received_3
  end

  def test_unscope_with_where_attributes
    expected = Developer.order('salary DESC').collect { |dev| dev.name }
    received = DeveloperOrderedBySalary.where(name: 'David').unscope(where: :name).collect { |dev| dev.name }
    assert_equal expected, received

    expected_2 = Developer.order('salary DESC').collect { |dev| dev.name }
    received_2 = DeveloperOrderedBySalary.select("id").where("name" => "Jamis").unscope({:where => :name}, :select).collect { |dev| dev.name }
    assert_equal expected_2, received_2

    expected_3 = Developer.order('salary DESC').collect { |dev| dev.name }
    received_3 = DeveloperOrderedBySalary.select("id").where("name" => "Jamis").unscope(:select, :where).collect { |dev| dev.name }
    assert_equal expected_3, received_3
  end

  def test_unscope_multiple_where_clauses
    expected = Developer.order('salary DESC').collect { |dev| dev.name }
    received = DeveloperOrderedBySalary.where(name: 'Jamis').where(id: 1).unscope(where: [:name, :id]).collect { |dev| dev.name }
    assert_equal expected, received
  end

  def test_unscope_with_grouping_attributes
    expected = Developer.order('salary DESC').collect { |dev| dev.name }
    received = DeveloperOrderedBySalary.group(:name).unscope(:group).collect { |dev| dev.name }
    assert_equal expected, received

    expected_2 = Developer.order('salary DESC').collect { |dev| dev.name }
    received_2 = DeveloperOrderedBySalary.group("name").unscope(:group).collect { |dev| dev.name }
    assert_equal expected_2, received_2
  end

  def test_unscope_with_limit_in_query
    expected = Developer.order('salary DESC').collect { |dev| dev.name }
    received = DeveloperOrderedBySalary.limit(1).unscope(:limit).collect { |dev| dev.name }
    assert_equal expected, received
  end

  def test_order_to_unscope_reordering
    scope = DeveloperOrderedBySalary.order('salary DESC, name ASC').reverse_order.unscope(:order)
    assert !(scope.to_sql =~ /order/i)
  end

  def test_unscope_reverse_order
    expected = Developer.all.collect { |dev| dev.name }
    received = Developer.order('salary DESC').reverse_order.unscope(:order).collect { |dev| dev.name }
    assert_equal expected, received
  end

  def test_unscope_select
    expected = Developer.order('salary ASC').collect { |dev| dev.name }
    received = Developer.order('salary DESC').reverse_order.select(:name => "Jamis").unscope(:select).collect { |dev| dev.name }
    assert_equal expected, received

    expected_2 = Developer.all.collect { |dev| dev.id }
    received_2 = Developer.select(:name).unscope(:select).collect { |dev| dev.id }
    assert_equal expected_2, received_2
  end

  def test_unscope_offset
    expected = Developer.all.collect { |dev| dev.name }
    received = Developer.offset(5).unscope(:offset).collect { |dev| dev.name }
    assert_equal expected, received
  end

  def test_unscope_joins_and_select_on_developers_projects
    expected = Developer.all.collect { |dev| dev.name }
    received = Developer.joins('JOIN developers_projects ON id = developer_id').select(:id).unscope(:joins, :select).collect { |dev| dev.name }
    assert_equal expected, received
  end

  def test_unscope_includes
    expected = Developer.all.collect { |dev| dev.name }
    received = Developer.includes(:projects).select(:id).unscope(:includes, :select).collect { |dev| dev.name }
    assert_equal expected, received
  end

  def test_unscope_having
    expected = DeveloperOrderedBySalary.all.collect { |dev| dev.name }
    received = DeveloperOrderedBySalary.having("name IN ('Jamis', 'David')").unscope(:having).collect { |dev| dev.name }
    assert_equal expected, received
  end

  def test_unscope_and_scope
    developer_klass = Class.new(Developer) do
      scope :by_name, -> name { unscope(where: :name).where(name: name) }
    end

    expected = developer_klass.where(name: 'Jamis').collect { |dev| [dev.name, dev.id] }
    received = developer_klass.where(name: 'David').by_name('Jamis').collect { |dev| [dev.name, dev.id] }
    assert_equal expected, received
  end

  def test_unscope_errors_with_invalid_value
    assert_raises(ArgumentError) do
      Developer.includes(:projects).where(name: "Jamis").unscope(:stupidly_incorrect_value)
    end

    assert_raises(ArgumentError) do
      Developer.all.unscope(:includes, :select, :some_broken_value)
    end

    assert_raises(ArgumentError) do
      Developer.order('name DESC').reverse_order.unscope(:reverse_order)
    end

    assert_raises(ArgumentError) do
      Developer.order('name DESC').where(name: "Jamis").unscope()
    end
  end

  def test_unscope_errors_with_non_where_hash_keys
    assert_raises(ArgumentError) do
      Developer.where(name: "Jamis").limit(4).unscope(limit: 4)
    end

    assert_raises(ArgumentError) do
      Developer.where(name: "Jamis").unscope("where" => :name)
    end
  end

  def test_unscope_errors_with_non_symbol_or_hash_arguments
    assert_raises(ArgumentError) do
      Developer.where(name: "Jamis").limit(3).unscope("limit")
    end

    assert_raises(ArgumentError) do
      Developer.select("id").unscope("select")
    end

    assert_raises(ArgumentError) do
      Developer.select("id").unscope(5)
    end
  end

  def test_order_in_default_scope_should_not_prevail
    expected = Developer.all.merge!(order: 'salary desc').to_a.collect { |dev| dev.salary }
    received = DeveloperOrderedBySalary.all.merge!(order: 'salary').to_a.collect { |dev| dev.salary }
    assert_equal expected, received
  end

  def test_create_attribute_overwrites_default_scoping
    assert_equal 'David', PoorDeveloperCalledJamis.create!(:name => 'David').name
    assert_equal 200000, PoorDeveloperCalledJamis.create!(:name => 'David', :salary => 200000).salary
  end

  def test_create_attribute_overwrites_default_values
    assert_equal nil, PoorDeveloperCalledJamis.create!(:salary => nil).salary
    assert_equal 50000, PoorDeveloperCalledJamis.create!(:name => 'David').salary
  end

  def test_default_scope_attribute
    jamis = PoorDeveloperCalledJamis.new(:name => 'David')
    assert_equal 50000, jamis.salary
  end

  def test_where_attribute
    aaron = PoorDeveloperCalledJamis.where(:salary => 20).new(:name => 'Aaron')
    assert_equal 20, aaron.salary
    assert_equal 'Aaron', aaron.name
  end

  def test_where_attribute_merge
    aaron = PoorDeveloperCalledJamis.where(:name => 'foo').new(:name => 'Aaron')
    assert_equal 'Aaron', aaron.name
  end

  def test_scope_composed_by_limit_and_then_offset_is_equal_to_scope_composed_by_offset_and_then_limit
    posts_limit_offset = Post.limit(3).offset(2)
    posts_offset_limit = Post.offset(2).limit(3)
    assert_equal posts_limit_offset, posts_offset_limit
  end

  def test_create_with_merge
    aaron = PoorDeveloperCalledJamis.create_with(:name => 'foo', :salary => 20).merge(
              PoorDeveloperCalledJamis.create_with(:name => 'Aaron')).new
    assert_equal 20, aaron.salary
    assert_equal 'Aaron', aaron.name

    aaron = PoorDeveloperCalledJamis.create_with(:name => 'foo', :salary => 20).
                                     create_with(:name => 'Aaron').new
    assert_equal 20, aaron.salary
    assert_equal 'Aaron', aaron.name
  end

  def test_create_with_reset
    jamis = PoorDeveloperCalledJamis.create_with(:name => 'Aaron').create_with(nil).new
    assert_equal 'Jamis', jamis.name
  end

  # FIXME: I don't know if this is *desired* behavior, but it is *today's*
  # behavior.
  def test_create_with_empty_hash_will_not_reset
    jamis = PoorDeveloperCalledJamis.create_with(:name => 'Aaron').create_with({}).new
    assert_equal 'Aaron', jamis.name
  end

  def test_unscoped_with_named_scope_should_not_have_default_scope
    assert_equal [DeveloperCalledJamis.find(developers(:poor_jamis).id)], DeveloperCalledJamis.poor

    assert DeveloperCalledJamis.unscoped.poor.include?(developers(:david).becomes(DeveloperCalledJamis))

    assert_equal 11, DeveloperCalledJamis.unscoped.length
    assert_equal 1,  DeveloperCalledJamis.poor.length
    assert_equal 10, DeveloperCalledJamis.unscoped.poor.length
    assert_equal 10, DeveloperCalledJamis.unscoped { DeveloperCalledJamis.poor }.length
  end

  def test_default_scope_select_ignored_by_aggregations
    assert_equal DeveloperWithSelect.all.to_a.count, DeveloperWithSelect.count
  end

  def test_default_scope_select_ignored_by_grouped_aggregations
    assert_equal Hash[Developer.all.group_by(&:salary).map { |s, d| [s, d.count] }],
                 DeveloperWithSelect.group(:salary).count
  end

  def test_default_scope_order_ignored_by_aggregations
    assert_equal DeveloperOrderedBySalary.all.count, DeveloperOrderedBySalary.count
  end

  def test_default_scope_find_last
    assert DeveloperOrderedBySalary.count > 1, "need more than one row for test"

    lowest_salary_dev = DeveloperOrderedBySalary.find(developers(:poor_jamis).id)
    assert_equal lowest_salary_dev, DeveloperOrderedBySalary.last
  end

  def test_default_scope_include_with_count
    d = DeveloperWithIncludes.create!
    d.audit_logs.create! :message => 'foo'

    assert_equal 1, DeveloperWithIncludes.where(:audit_logs => { :message => 'foo' }).count
  end

  def test_default_scope_is_threadsafe
    if in_memory_db?
      skip "in memory db can't share a db between threads"
    end

    threads = []
    assert_not_equal 1, ThreadsafeDeveloper.unscoped.count

    threads << Thread.new do
      Thread.current[:long_default_scope] = true
      assert_equal 1, ThreadsafeDeveloper.all.to_a.count
      ThreadsafeDeveloper.connection.close
    end
    threads << Thread.new do
      assert_equal 1, ThreadsafeDeveloper.all.to_a.count
      ThreadsafeDeveloper.connection.close
    end
    threads.each(&:join)
  end
end