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)


187
188
189
# File 'lib/view_model/active_record/association_data.rb', line 187

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)


210
211
212
# File 'lib/view_model/active_record/association_data.rb', line 210

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

#direct_reflection_inverse(foreign_class = nil) ⇒ Object



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

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)


203
204
205
206
207
208
# File 'lib/view_model/active_record/association_data.rb', line 203

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



214
215
216
# File 'lib/view_model/active_record/association_data.rb', line 214

def indirect_association_data
  direct_viewmodel._association_data(indirect_reflection.name)
end

#indirect_reflectionObject



143
144
145
146
# File 'lib/view_model/active_record/association_data.rb', line 143

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

#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.



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

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

def polymorphic?
  target_reflection.polymorphic?
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)


199
200
201
# File 'lib/view_model/active_record/association_data.rb', line 199

def through?
  @indirect_association_name.present?
end

#viewmodel_classObject



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

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



161
162
163
# File 'lib/view_model/active_record/association_data.rb', line 161

def viewmodel_class_for_model(model_class)
  model_to_viewmodel[model_class]
end

#viewmodel_class_for_model!(model_class) ⇒ Object



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

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



174
175
176
# File 'lib/view_model/active_record/association_data.rb', line 174

def viewmodel_class_for_name(name)
  name_to_viewmodel[name]
end

#viewmodel_class_for_name!(name) ⇒ Object



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

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



156
157
158
159
# File 'lib/view_model/active_record/association_data.rb', line 156

def viewmodel_classes
  lazy_initialize! unless @initialized
  @viewmodel_classes
end