Skip to content

Commit 276a30f

Browse files
committed
wip refactored associations through and source methods
1 parent e550488 commit 276a30f

File tree

2 files changed

+99
-46
lines changed

2 files changed

+99
-46
lines changed

ruby/hyper-model/lib/reactive_record/active_record/associations.rb

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ def initialize(owner_class, macro, name, options = {})
6161
end
6262
@attribute = name
6363
@through_associations = Hash.new { |_h, k| [] unless k }
64-
@source_associations = Hash.new { |_h, k| [] unless k }
6564
end
6665

6766
def collection?
@@ -84,35 +83,69 @@ def through_association
8483

8584
alias through_association? through_association
8685

86+
# class Membership < ActiveRecord::Base
87+
# belongs_to :uzer
88+
# belongs_to :memerable, polymorphic: true
89+
# end
90+
#
91+
# class Project < ActiveRecord::Base
92+
# has_many :memberships, as: :memerable, dependent: :destroy
93+
# has_many :uzers, through: :memberships
94+
# end
95+
#
96+
# class Group < ActiveRecord::Base
97+
# has_many :memberships, as: :memerable, dependent: :destroy
98+
# has_many :uzers, through: :memberships
99+
# end
100+
#
101+
# class Uzer < ActiveRecord::Base
102+
# has_many :memberships
103+
# has_many :groups, through: :memberships, source: :memerable, source_type: 'Group'
104+
# has_many :projects, through: :memberships, source: :memerable, source_type: 'Project'
105+
# end
106+
107+
# so find the belongs_to relationship whose attribute == ta.source
108+
# now find the inverse of that relationship using source_value as the model
109+
# now find any has many through relationships that use that relationship as there source.
110+
# each of those attributes in the source_value have to be updated.
111+
112+
# self is the through association
113+
114+
87115
def through_associations(model)
116+
# given self is a belongs_to association currently pointing to model
88117
# find all associations that use the inverse association as the through association
89118
# that is find all associations that are using this association in a through relationship
90119
the_klass = klass(model)
91120
@through_associations[the_klass] ||= the_klass.reflect_on_all_associations.select do |assoc|
92-
assoc.through_association && assoc.inverse(nil) == self
121+
assoc.through_association&.inverse == self
93122
end
94123
end
95124

125+
def source_belongs_to_association # private
126+
# given self is a has_many_through association return the corresponding belongs_to association
127+
# for the source
128+
@source_belongs_to_association ||=
129+
through_association.inverse.owner_class.detect do |sibling|
130+
sibling.attribute == source
131+
end
132+
end
133+
96134
def source_associations(model)
97-
# find all associations that use this association as the source
98-
# that is find all associations that are using this association as the source in a
99-
# through relationship
100-
the_klass = klass(model)
101-
@source_associations[the_klass] ||= owner_class.reflect_on_all_associations.collect do |sibling|
102-
sibling.klass.reflect_on_all_associations.select do |assoc|
103-
assoc.source == attribute && assoc.source_type == the_klass.name
104-
end unless sibling.polymorphic?
105-
end.flatten
135+
# given self is a has_many_through association find the source_association for the given model
136+
source_belongs_to_association.through_associations(model)
106137
end
107138

108139
def polymorphic?
109140
!@klass_name
110141
end
111142

112-
def inverse(model)
143+
def inverse(model = nil)
144+
raise "internal assertion failure: #{self}.inverse called without a model, "\
145+
"and #{self} is not a collection" unless model || collection?
113146
return @inverse if @inverse
114-
ta = through_association(model)
115-
found = ta ? ta.inverse(model) : find_inverse(model)
147+
ta = through_association
148+
found = ta ? ta.inverse : find_inverse(model)
116149
@inverse = found unless polymorphic?
117150
found
118151
end

ruby/hyper-model/lib/reactive_record/active_record/reactive_record/setters.rb

Lines changed: 52 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -164,43 +164,63 @@ def push_onto_collection(model, association, ar_instance)
164164
@attributes[association.attribute] << ar_instance
165165
end
166166

167+
# class Membership < ActiveRecord::Base
168+
# belongs_to :uzer
169+
# belongs_to :memerable, polymorphic: true
170+
# end
171+
#
172+
# class Project < ActiveRecord::Base
173+
# has_many :memberships, as: :memerable, dependent: :destroy
174+
# has_many :uzers, through: :memberships
175+
# end
176+
#
177+
# class Group < ActiveRecord::Base
178+
# has_many :memberships, as: :memerable, dependent: :destroy
179+
# has_many :uzers, through: :memberships
180+
# end
181+
#
182+
# class Uzer < ActiveRecord::Base
183+
# has_many :memberships
184+
# has_many :groups, through: :memberships, source: :memerable, source_type: 'Group'
185+
# has_many :projects, through: :memberships, source: :memerable, source_type: 'Project'
186+
# end
187+
188+
# membership.uzer = some_new_uzer (i.e. through association is changing)
189+
# means membership.some_new_uzer.(groups OR projects) << uzer.memberable (depending on type of memberable)
190+
# and we have to remove the current value of the source association (memerable) from the current uzer group or project
191+
# and we have to then find any inverse has_many_through association (i.e. group or projects.uzers) and delete the
192+
# current value from those collections and push the new value on
193+
167194
def update_has_many_through_associations(assoc, value)
168195
# note that through and source_associations returns an empty set of
169196
# the provided ar_instance does not belong to a has_many_through association
170-
assoc.through_associations(value)
171-
.each { |ta| update_through_association(assoc, ta, value) }
172-
assoc.source_associations(value)
173-
.each { |sa| update_source_association(assoc, sa, value) }
174-
end
175-
176-
def update_through_association(assoc, ta, new_belongs_to_value)
177-
# appointment.doctor = doctor_new_value (i.e. through association is changing)
178-
# means appointment.doctor_new_value.patients << appointment.patient
179-
# and we have to appointment.doctor_current_value.patients.delete(appointment.patient)
180-
source_value = @attributes[ta.source]
181-
current_belongs_to_value = @attributes[assoc.attribute]
182-
return unless source_value.class.to_s == ta.source_type
183-
unless current_belongs_to_value.nil? || current_belongs_to_value.attributes[ta.attribute].nil?
184-
current_belongs_to_value.attributes[ta.attribute].delete(source_value)
197+
assoc.through_associations(value).each do |ta|
198+
source_value = @attributes[ta.source]
199+
current_value = @attributes[assoc.attribute]
200+
# skip if source value is nil or if type of the association does not match type of source
201+
next unless source_value.class.to_s == ta.source_type
202+
update_through_association(ta, source_value, current_value, value)
203+
ta.source_associations(source_value).each do |sa|
204+
update_source_association(sa, source_value, current_value, value)
205+
end
185206
end
186-
return unless new_belongs_to_value
187-
new_belongs_to_value.attributes[ta.attribute] ||= Collection.new(assoc.owner_class, new_belongs_to_value, ta)
188-
new_belongs_to_value.attributes[ta.attribute] << source_value
189-
end
190-
191-
def update_source_association(assoc, sa, new_source_value)
192-
# appointment.patient = patient_value (i.e. source is changing)
193-
# means appointment.doctor.patients.delete(appointment.patient)
194-
# means appointment.doctor.patients << patient_value
195-
belongs_to_value = @attributes[assoc.attribute]
196-
current_source_value = @attributes[sa.source]
197-
return unless belongs_to_value
198-
unless belongs_to_value.attributes[sa.attribute].nil? || current_source_value.nil?
199-
belongs_to_value.attributes[sa.attribute].delete(current_source_value)
207+
end
208+
209+
def update_through_association(ta, source_value, current_value, new_value)
210+
unless current_value.nil? || current_value.attributes[ta.attribute].nil?
211+
current_value.attributes[ta.attribute].delete(source_value)
200212
end
201-
return unless new_source_value
202-
belongs_to_value.attributes[sa.attribute] ||= Collection.new(sa.klass(new_source_value), belongs_to_value, sa)
203-
belongs_to_value.attributes[sa.attribute] << new_source_value
213+
return unless new_value
214+
new_value.attributes[ta.attribute] ||= Collection.new(ta.owner_class, new_value, ta)
215+
new_value.attributes[ta.attribute] << source_value
216+
end
217+
218+
def update_source_association(sa, source_value, current_value, new_value)
219+
source_inverse_collection = source_value.attributes[sa.attribute]
220+
source_inverse_collection.delete(current_value) if source_inverse_collection
221+
return unless new_value
222+
source_value.attributes[sa.attribute] ||= Collection.new(sa.owner_class, source_value, sa)
223+
source_value.attributes[sa.attribute] << new_value
204224
end
205225
end
206226
end

0 commit comments

Comments
 (0)