Class: ViewModel::ActiveRecord::AssociationData

Inherits:
Object
  • Object
show all
Defined in:
lib/view_model/active_record/association_data.rb

Defined Under Namespace

Classes: InvalidAssociation

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(owner:, association_name:, direct_association_name:, indirect_association_name:, target_viewmodels:, external:, through_order_attr:, read_only:) ⇒ AssociationData

Returns a new instance of AssociationData.



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
# File 'lib/view_model/active_record/association_data.rb', line 8

def initialize(owner:,
               association_name:,
               direct_association_name:,
               indirect_association_name:,
               target_viewmodels:,
               external:,
               through_order_attr:,
               read_only:)
  @association_name = association_name

  @direct_reflection = owner.model_class.reflect_on_association(direct_association_name)
  if @direct_reflection.nil?
    raise InvalidAssociation.new("Association '#{direct_association_name}' not found in model '#{owner.model_class.name}'")
  end

  @indirect_association_name = indirect_association_name

  @read_only           = read_only
  @external            = external
  @through_order_attr  = through_order_attr
  @target_viewmodels   = target_viewmodels

  # Target models/reflections/viewmodels are lazily evaluated so that we can
  # safely express cycles.
  @initialized         = false
  @mutex               = Mutex.new
end

Instance Attribute Details

#association_nameObject (readonly)

Returns the value of attribute association_name.



6
7
8
# File 'lib/view_model/active_record/association_data.rb', line 6

def association_name
  @association_name
end

#direct_reflectionObject (readonly)

Returns the value of attribute direct_reflection.



6
7
8
# File 'lib/view_model/active_record/association_data.rb', line 6

def direct_reflection
  @direct_reflection
end

Instance Method Details

#accepts?(viewmodel_class) ⇒ Boolean

Returns:

  • (Boolean)


189
190
191
# File 'lib/view_model/active_record/association_data.rb', line 189

def accepts?(viewmodel_class)
  viewmodel_classes.include?(viewmodel_class)
end

#association?Boolean

Returns:

  • (Boolean)


90
91
92
# File 'lib/view_model/active_record/association_data.rb', line 90

def association?
  true
end

#collection?Boolean

Returns:

  • (Boolean)


227
228
229
# File 'lib/view_model/active_record/association_data.rb', line 227

def collection?
  through? || direct_reflection.collection?
end

#direct_reflection_inverse(foreign_class = nil) ⇒ Object



150
151
152
153
154
155
156
# File 'lib/view_model/active_record/association_data.rb', line 150

def direct_reflection_inverse(foreign_class = nil)
  if direct_reflection.polymorphic?
    direct_reflection.polymorphic_inverse_of(foreign_class)
  else
    direct_reflection.inverse_of
  end
end

#direct_viewmodelObject

Raises:

  • (ArgumentError)


220
221
222
223
224
225
# File 'lib/view_model/active_record/association_data.rb', line 220

def direct_viewmodel
  raise ArgumentError.new('not a through association') unless through?

  lazy_initialize! unless @initialized
  @direct_viewmodel
end

#external?Boolean

Returns:

  • (Boolean)


112
113
114
# File 'lib/view_model/active_record/association_data.rb', line 112

def external?
  @external
end

#indirect_association_dataObject



231
232
233
# File 'lib/view_model/active_record/association_data.rb', line 231

def indirect_association_data
  direct_viewmodel._association_data(indirect_reflection.name)
end

#indirect_reflectionObject



145
146
147
148
# File 'lib/view_model/active_record/association_data.rb', line 145

def indirect_reflection
  lazy_initialize! unless @initialized
  @indirect_reflection
end

#lazy_initialize!Object



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
# File 'lib/view_model/active_record/association_data.rb', line 36

def lazy_initialize!
  @mutex.synchronize do
    return if @initialized

    if through?
      intermediate_model   = @direct_reflection.klass
      @indirect_reflection = load_indirect_reflection(intermediate_model, @indirect_association_name)
      target_reflection    = @indirect_reflection
    else
      target_reflection = @direct_reflection
    end

    @viewmodel_classes =
      if @target_viewmodels.present?
        # Explicitly named
        @target_viewmodels.map { |v| resolve_viewmodel_class(v) }
      else
        # Infer name from name of model
        if target_reflection.polymorphic?
          raise InvalidAssociation.new(
                  'Cannot automatically infer target viewmodels from polymorphic association')
        end
        infer_viewmodel_class(target_reflection.klass)
      end

    @referenced = @viewmodel_classes.first.root?

    # Non-referenced viewmodels must be owned. For referenced viewmodels, we
    # own it if it points to us. Through associations aren't considered
    # `owned?`: while we do own the implicit direct viewmodel, we don't own
    # the target of the association.
    @owned = !@referenced || (target_reflection.macro != :belongs_to)

    unless @viewmodel_classes.all? { |v| v.root? == @referenced }
      raise InvalidAssociation.new('Invalid association target: mixed root and non-root viewmodels')
    end

    if external? && !@referenced
      raise InvalidAssociation.new('External associations must be to root viewmodels')
    end

    if through?
      unless @referenced
        raise InvalidAssociation.new('Through associations must be to root viewmodels')
      end

      @direct_viewmodel = build_direct_viewmodel(@direct_reflection, @indirect_reflection,
                                                 @viewmodel_classes, @through_order_attr)
    end

    @initialized = true
  end
end

#nested?Boolean

Returns:

  • (Boolean)


99
100
101
# File 'lib/view_model/active_record/association_data.rb', line 99

def nested?
  !referenced?
end

#ordered?Boolean

Returns:

  • (Boolean)


201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/view_model/active_record/association_data.rb', line 201

def ordered?
  @ordered ||=
    if through?
      direct_viewmodel._list_member?
    else
      list_members = viewmodel_classes.map { |c| c._list_member? }.uniq

      if list_members.size > 1
        raise ArgumentError.new('Inconsistent associated views: mixed list membership')
      end

      list_members[0]
    end
end

#owned?Boolean

Returns:

  • (Boolean)


103
104
105
106
# File 'lib/view_model/active_record/association_data.rb', line 103

def owned?
  lazy_initialize! unless @initialized
  @owned
end

#pointer_locationObject

The side of the immediate association that holds the pointer.



136
137
138
139
140
141
142
143
# File 'lib/view_model/active_record/association_data.rb', line 136

def pointer_location
  case direct_reflection.macro
  when :belongs_to
    :local
  when :has_one, :has_many
    :remote
  end
end

#polymorphic?Boolean

Returns:

  • (Boolean)


129
130
131
132
133
# File 'lib/view_model/active_record/association_data.rb', line 129

def polymorphic?
  # STI polymorphism isn't shown on the association reflection, so in that
  # case we have to infer it by having multiple target viewmodel types.
  target_reflection.polymorphic? || viewmodel_classes.size > 1
end

#read_only?Boolean

Returns:

  • (Boolean)


116
117
118
# File 'lib/view_model/active_record/association_data.rb', line 116

def read_only?
  @read_only
end

#referenced?Boolean

Returns:

  • (Boolean)


94
95
96
97
# File 'lib/view_model/active_record/association_data.rb', line 94

def referenced?
  lazy_initialize! unless @initialized
  @referenced
end

#shared?Boolean

Returns:

  • (Boolean)


108
109
110
# File 'lib/view_model/active_record/association_data.rb', line 108

def shared?
  !owned?
end

#target_reflectionObject

reflection for the target of this association: indirect if through, direct otherwise



121
122
123
124
125
126
127
# File 'lib/view_model/active_record/association_data.rb', line 121

def target_reflection
  if through?
    indirect_reflection
  else
    direct_reflection
  end
end

#through?Boolean

Returns:

  • (Boolean)


216
217
218
# File 'lib/view_model/active_record/association_data.rb', line 216

def through?
  @indirect_association_name.present?
end

#viewmodel_classObject



193
194
195
196
197
198
199
# File 'lib/view_model/active_record/association_data.rb', line 193

def viewmodel_class
  unless viewmodel_classes.size == 1
    raise ArgumentError.new("More than one possible class for association '#{target_reflection.name}'")
  end

  viewmodel_classes.first
end

#viewmodel_class_for_model(model_class) ⇒ Object



163
164
165
# File 'lib/view_model/active_record/association_data.rb', line 163

def viewmodel_class_for_model(model_class)
  model_to_viewmodel[model_class]
end

#viewmodel_class_for_model!(model_class) ⇒ Object



167
168
169
170
171
172
173
174
# File 'lib/view_model/active_record/association_data.rb', line 167

def viewmodel_class_for_model!(model_class)
  vm_class = viewmodel_class_for_model(model_class)
  if vm_class.nil?
    raise ArgumentError.new(
            "Invalid viewmodel model for association '#{target_reflection.name}': '#{model_class.name}'")
  end
  vm_class
end

#viewmodel_class_for_name(name) ⇒ Object



176
177
178
# File 'lib/view_model/active_record/association_data.rb', line 176

def viewmodel_class_for_name(name)
  name_to_viewmodel[name]
end

#viewmodel_class_for_name!(name) ⇒ Object



180
181
182
183
184
185
186
187
# File 'lib/view_model/active_record/association_data.rb', line 180

def viewmodel_class_for_name!(name)
  vm_class = viewmodel_class_for_name(name)
  if vm_class.nil?
    raise ArgumentError.new(
            "Invalid viewmodel name for association '#{target_reflection.name}': '#{name}'")
  end
  vm_class
end

#viewmodel_classesObject



158
159
160
161
# File 'lib/view_model/active_record/association_data.rb', line 158

def viewmodel_classes
  lazy_initialize! unless @initialized
  @viewmodel_classes
end