aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/fixture_set/table_rows.rb
blob: 3e3c0bc7ab15de34fc9132f9422923dbb73fffa8 (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
# frozen_string_literal: true

require "active_record/fixture_set/table_row"
require "active_record/fixture_set/model_metadata"

module ActiveRecord
  class FixtureSet
    class TableRows # :nodoc:
      class ReflectionProxy # :nodoc:
        def initialize(association)
          @association = association
        end

        def join_table
          @association.join_table
        end

        def name
          @association.name
        end

        def primary_key_type
          @association.klass.type_for_attribute(@association.klass.primary_key).type
        end
      end

      class HasManyThroughProxy < ReflectionProxy # :nodoc:
        def rhs_key
          @association.foreign_key
        end

        def lhs_key
          @association.through_reflection.foreign_key
        end

        def join_table
          @association.through_reflection.table_name
        end
      end

      def initialize(table_name, model_class:, fixtures:, config:)
        @table_name  = table_name
        @model_class = model_class

        # track any join tables we need to insert later
        @tables = Hash.new { |h, table| h[table] = [] }

        # ensure this table is loaded before any HABTM associations
        @tables[table_name] = nil

        build_table_rows_from(fixtures, config)
      end

      attr_reader :table_name, :model_class

      def to_hash
        @tables.transform_values { |rows| rows.map(&:to_hash) }
      end

      def model_metadata
        @model_metadata ||= ModelMetadata.new(model_class, table_name)
      end

      def resolve_sti_reflections(row)
        # If STI is used, find the correct subclass for association reflection
        reflection_class = reflection_class_for(row)

        reflection_class._reflections.each_value do |association|
          case association.macro
          when :belongs_to
            # Do not replace association name with association foreign key if they are named the same
            fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s

            if association.name.to_s != fk_name && value = row.delete(association.name.to_s)
              if association.polymorphic? && value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
                # support polymorphic belongs_to as "label (Type)"
                row[association.foreign_type] = $1
              end

              fk_type = reflection_class.type_for_attribute(fk_name).type
              row[fk_name] = ActiveRecord::FixtureSet.identify(value, fk_type)
            end
          when :has_many
            if association.options[:through]
              add_join_records(row, HasManyThroughProxy.new(association))
            end
          end
        end
      end

      private

        def build_table_rows_from(fixtures, config)
          now = config.default_timezone == :utc ? Time.now.utc : Time.now

          @tables[table_name] = fixtures.map do |label, fixture|
            TableRow.new(
              fixture,
              table_rows: self,
              label: label,
              now: now,
            )
          end
        end

        def reflection_class_for(row)
          if row.include?(model_metadata.inheritance_column_name)
            row[model_metadata.inheritance_column_name].constantize rescue model_class
          else
            model_class
          end
        end

        def add_join_records(row, association)
          # This is the case when the join table has no fixtures file
          if (targets = row.delete(association.name.to_s))
            table_name  = association.join_table
            column_type = association.primary_key_type
            lhs_key     = association.lhs_key
            rhs_key     = association.rhs_key

            targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
            joins   = targets.map do |target|
              { lhs_key => row[model_metadata.primary_key_name],
                rhs_key => ActiveRecord::FixtureSet.identify(target, column_type) }
            end
            @tables[table_name].concat(joins)
          end
        end
    end
  end
end