relation_test.rb 9.0 KB
Newer Older
1
require "cases/helper"
2 3
require 'models/post'
require 'models/comment'
4 5
require 'models/author'
require 'models/rating'
6 7 8

module ActiveRecord
  class RelationTest < ActiveRecord::TestCase
9
    fixtures :posts, :comments, :authors
10

11
    class FakeKlass < Struct.new(:table_name, :name)
12 13 14 15
      extend ActiveRecord::Delegation::DelegateCache

      inherited self

16 17 18
      def self.connection
        Post.connection
      end
19 20 21 22

      def self.table_name
        'fake_table'
      end
A
Aaron Patterson 已提交
23 24
    end

25
    def test_construction
26
      relation = Relation.new(FakeKlass, :b, nil)
27
      assert_equal FakeKlass, relation.klass
28 29 30 31
      assert_equal :b, relation.table
      assert !relation.loaded, 'relation is not loaded'
    end

32
    def test_responds_to_model_and_returns_klass
33
      relation = Relation.new(FakeKlass, :b, nil)
34
      assert_equal FakeKlass, relation.model
35 36
    end

37
    def test_initialize_single_values
38
      relation = Relation.new(FakeKlass, :b, nil)
39
      (Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |method|
40 41
        assert_nil relation.send("#{method}_value"), method.to_s
      end
42
      assert_equal({}, relation.create_with_value)
43 44 45
    end

    def test_multi_value_initialize
46
      relation = Relation.new(FakeKlass, :b, nil)
47 48 49 50 51 52
      Relation::MULTI_VALUE_METHODS.each do |method|
        assert_equal [], relation.send("#{method}_values"), method.to_s
      end
    end

    def test_extensions
53
      relation = Relation.new(FakeKlass, :b, nil)
54 55
      assert_equal [], relation.extensions
    end
A
Aaron Patterson 已提交
56

57
    def test_empty_where_values_hash
58
      relation = Relation.new(FakeKlass, :b, nil)
A
Aaron Patterson 已提交
59 60
      assert_equal({}, relation.where_values_hash)

61
      relation.where! :hello
A
Aaron Patterson 已提交
62 63 64
      assert_equal({}, relation.where_values_hash)
    end

65
    def test_has_values
66
      relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
67
      relation.where! relation.table[:id].eq(10)
68 69 70 71
      assert_equal({:id => 10}, relation.where_values_hash)
    end

    def test_values_wrong_table
72
      relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
73
      relation.where! Comment.arel_table[:id].eq(10)
74 75 76
      assert_equal({}, relation.where_values_hash)
    end

77
    def test_tree_is_not_traversed
78
      relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
79
      # FIXME: Remove the Arel::Nodes::Quoted in Rails 5.1
80 81
      left     = relation.table[:id].eq(10)
      right    = relation.table[:id].eq(10)
82
      combine  = left.and right
83
      relation.where! combine
84 85 86
      assert_equal({}, relation.where_values_hash)
    end

A
Aaron Patterson 已提交
87
    def test_table_name_delegates_to_klass
88
      relation = Relation.new(FakeKlass.new('posts'), :b, Post.predicate_builder)
89
      assert_equal 'posts', relation.table_name
A
Aaron Patterson 已提交
90 91 92
    end

    def test_scope_for_create
93
      relation = Relation.new(FakeKlass, :b, nil)
A
Aaron Patterson 已提交
94 95
      assert_equal({}, relation.scope_for_create)
    end
96 97

    def test_create_with_value
98
      relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
99 100 101 102 103 104
      hash = { :hello => 'world' }
      relation.create_with_value = hash
      assert_equal hash, relation.scope_for_create
    end

    def test_create_with_value_with_wheres
105
      relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
106
      # FIXME: Remove the Arel::Nodes::Quoted in Rails 5.1
107
      relation.where! relation.table[:id].eq(10)
108 109 110
      relation.create_with_value = {:hello => 'world'}
      assert_equal({:hello => 'world', :id => 10}, relation.scope_for_create)
    end
111 112 113

    # FIXME: is this really wanted or expected behavior?
    def test_scope_for_create_is_cached
114
      relation = Relation.new(Post, Post.arel_table, Post.predicate_builder)
115 116
      assert_equal({}, relation.scope_for_create)

117
      # FIXME: Remove the Arel::Nodes::Quoted in Rails 5.1
118
      relation.where! relation.table[:id].eq(10)
119 120 121 122 123
      assert_equal({}, relation.scope_for_create)

      relation.create_with_value = {:hello => 'world'}
      assert_equal({}, relation.scope_for_create)
    end
A
Aaron Patterson 已提交
124

125 126 127 128 129 130
    def test_bad_constants_raise_errors
      assert_raises(NameError) do
        ActiveRecord::Relation::HelloWorld
      end
    end

A
Aaron Patterson 已提交
131
    def test_empty_eager_loading?
132
      relation = Relation.new(FakeKlass, :b, nil)
A
Aaron Patterson 已提交
133 134 135 136
      assert !relation.eager_loading?
    end

    def test_eager_load_values
137
      relation = Relation.new(FakeKlass, :b, nil)
138
      relation.eager_load! :b
A
Aaron Patterson 已提交
139 140
      assert relation.eager_loading?
    end
141 142

    def test_references_values
143
      relation = Relation.new(FakeKlass, :b, nil)
144 145
      assert_equal [], relation.references_values
      relation = relation.references(:foo).references(:omg, :lol)
J
Jon Leighton 已提交
146
      assert_equal ['foo', 'omg', 'lol'], relation.references_values
147 148 149
    end

    def test_references_values_dont_duplicate
150
      relation = Relation.new(FakeKlass, :b, nil)
151
      relation = relation.references(:foo).references(:foo)
J
Jon Leighton 已提交
152
      assert_equal ['foo'], relation.references_values
153
    end
154

155
    test 'merging a hash into a relation' do
156
      relation = Relation.new(FakeKlass, :b, nil)
157
      relation = relation.merge where: :lol, readonly: true
158

159
      assert_equal Relation::WhereClause.new([:lol], []), relation.where_clause
160 161 162 163
      assert_equal true, relation.readonly_value
    end

    test 'merging an empty hash into a relation' do
164
      assert_equal Relation::WhereClause.empty, Relation.new(FakeKlass, :b, nil).merge({}).where_clause
165
    end
J
Jon Leighton 已提交
166 167 168 169

    test 'merging a hash with unknown keys raises' do
      assert_raises(ArgumentError) { Relation::HashMerger.new(nil, omg: 'lol') }
    end
170 171

    test '#values returns a dup of the values' do
172
      relation = Relation.new(FakeKlass, :b, nil).where! :foo
173 174 175
      values   = relation.values

      values[:where] = nil
176
      assert_not_nil relation.where_clause
177 178 179
    end

    test 'relations can be created with a values hash' do
S
Sean Griffin 已提交
180 181
      relation = Relation.new(FakeKlass, :b, nil, select: [:foo])
      assert_equal [:foo], relation.select_values
182
    end
183 184

    test 'merging a hash interpolates conditions' do
185 186 187 188 189 190
      klass = Class.new(FakeKlass) do
        def self.sanitize_sql(args)
          raise unless args == ['foo = ?', 'bar']
          'foo = bar'
        end
      end
191

192
      relation = Relation.new(klass, :b, nil)
193
      relation.merge!(where: ['foo = ?', 'bar'])
194
      assert_equal Relation::WhereClause.new(['foo = bar'], []), relation.where_clause
195
    end
196

197
    def test_merging_readonly_false
198
      relation = Relation.new(FakeKlass, :b, nil)
199 200 201 202 203 204
      readonly_false_relation = relation.readonly(false)
      # test merging in both directions
      assert_equal false, relation.merge(readonly_false_relation).readonly_value
      assert_equal false, readonly_false_relation.merge(relation).readonly_value
    end

205
    def test_relation_merging_with_merged_joins_as_symbols
206 207
      special_comments_with_ratings = SpecialComment.joins(:ratings)
      posts_with_special_comments_with_ratings = Post.group("posts.id").joins(:special_comments).merge(special_comments_with_ratings)
208
      assert_equal 3, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count.length
209 210
    end

211 212 213 214 215
    def test_relation_merging_with_joins_as_join_dependency_pick_proper_parent
      post = Post.create!(title: "haha", body: "huhu")
      comment = post.comments.create!(body: "hu")
      3.times { comment.ratings.create! }

216
      relation = Post.joins(:comments).merge Comment.joins(:ratings)
217

218
      assert_equal 3, relation.where(id: post.id).pluck(:id).size
219 220
    end

221
    def test_respond_to_for_non_selected_element
222 223 224
      post = Post.select(:title).first
      assert_equal false, post.respond_to?(:body), "post should not respond_to?(:body) since invoking it raises exception"

225
      silence_warnings { post = Post.select("'title' as post_title").first }
226
      assert_equal false, post.respond_to?(:title), "post should not respond_to?(:body) since invoking it raises exception"
227 228
    end

229 230 231 232 233 234
    def test_relation_merging_with_merged_joins_as_strings
      join_string = "LEFT OUTER JOIN #{Rating.quoted_table_name} ON #{SpecialComment.quoted_table_name}.id = #{Rating.quoted_table_name}.comment_id"
      special_comments_with_ratings = SpecialComment.joins join_string
      posts_with_special_comments_with_ratings = Post.group("posts.id").joins(:special_comments).merge(special_comments_with_ratings)
      assert_equal 3, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count.length
    end
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

    class EnsureRoundTripTypeCasting < ActiveRecord::Type::Value
      def type
        :string
      end

      def type_cast_from_database(value)
        raise value unless value == "type cast for database"
        "type cast from database"
      end

      def type_cast_for_database(value)
        raise value unless value == "value from user"
        "type cast for database"
      end
    end

    class UpdateAllTestModel < ActiveRecord::Base
      self.table_name = 'posts'

      attribute :body, EnsureRoundTripTypeCasting.new
    end

    def test_update_all_goes_through_normal_type_casting
      UpdateAllTestModel.update_all(body: "value from user", type: nil) # No STI

      assert_equal "type cast from database", UpdateAllTestModel.first.body
    end
263 264
  end
end