hyper-model 1.0.alpha1.4 → 1.0.alpha1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/active_record_base.rb +1 -1
- data/lib/hyper-model.rb +1 -0
- data/lib/hyper_model/version.rb +1 -1
- data/lib/hyper_react/input_tags.rb +2 -1
- data/lib/reactive_record/active_record/associations.rb +117 -32
- data/lib/reactive_record/active_record/class_methods.rb +68 -14
- data/lib/reactive_record/active_record/error.rb +2 -0
- data/lib/reactive_record/active_record/errors.rb +8 -4
- data/lib/reactive_record/active_record/instance_methods.rb +22 -1
- data/lib/reactive_record/active_record/reactive_record/backing_record_inspector.rb +4 -4
- data/lib/reactive_record/active_record/reactive_record/base.rb +16 -5
- data/lib/reactive_record/active_record/reactive_record/collection.rb +53 -29
- data/lib/reactive_record/active_record/reactive_record/dummy_polymorph.rb +22 -0
- data/lib/reactive_record/active_record/reactive_record/dummy_value.rb +1 -0
- data/lib/reactive_record/active_record/reactive_record/getters.rb +19 -7
- data/lib/reactive_record/active_record/reactive_record/isomorphic_base.rb +28 -11
- data/lib/reactive_record/active_record/reactive_record/lookup_tables.rb +5 -5
- data/lib/reactive_record/active_record/reactive_record/setters.rb +104 -66
- data/lib/reactive_record/broadcast.rb +19 -9
- data/lib/reactive_record/interval.rb +3 -3
- data/lib/reactive_record/scope_description.rb +3 -2
- data/lib/reactive_record/server_data_cache.rb +12 -3
- data/polymorph-notes.md +143 -0
- metadata +9 -7
@@ -14,15 +14,15 @@ module ReactiveRecord
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def waiting_for_save(model)
|
17
|
-
@waiting_for_save[model]
|
17
|
+
@waiting_for_save[model.base_class]
|
18
18
|
end
|
19
19
|
|
20
20
|
def wait_for_save(model, &block)
|
21
|
-
@waiting_for_save[model] << block
|
21
|
+
@waiting_for_save[model.base_class] << block
|
22
22
|
end
|
23
23
|
|
24
24
|
def clear_waiting_for_save(model)
|
25
|
-
@waiting_for_save[model] = []
|
25
|
+
@waiting_for_save[model.base_class] = []
|
26
26
|
end
|
27
27
|
|
28
28
|
def lookup_by_object_id(object_id)
|
@@ -33,8 +33,8 @@ module ReactiveRecord
|
|
33
33
|
`#{@records_by_object_id}[#{record.object_id}] = #{record}`
|
34
34
|
end
|
35
35
|
|
36
|
-
def lookup_by_id(
|
37
|
-
`#{@records_by_id}[#{
|
36
|
+
def lookup_by_id(model, id) # model and id
|
37
|
+
`#{@records_by_id}[#{[model.base_class, id]}]` || nil
|
38
38
|
end
|
39
39
|
|
40
40
|
def set_id_lookup(record)
|
@@ -43,17 +43,26 @@ module ReactiveRecord
|
|
43
43
|
|
44
44
|
def set_belongs_to(assoc, raw_value)
|
45
45
|
set_common(assoc.attribute, raw_value) do |value, attr|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
# itself will just reactively read the value (a model instance) by doing a .id
|
53
|
-
update_belongs_to attr, value.itself
|
46
|
+
current_value = @attributes[assoc.attribute]
|
47
|
+
update_has_many_through_associations assoc, nil, current_value, :remove_member
|
48
|
+
update_has_many_through_associations assoc, nil, value, :add_member
|
49
|
+
remove_current_inverse_attribute assoc, nil, current_value
|
50
|
+
add_new_inverse_attribute assoc, nil, value
|
51
|
+
update_belongs_to attr, value.itself
|
54
52
|
end
|
55
53
|
end
|
56
54
|
|
55
|
+
def set_belongs_to_via_has_many(orig, value)
|
56
|
+
assoc = orig.inverse
|
57
|
+
attr = assoc.attribute
|
58
|
+
current_value = @attributes[attr]
|
59
|
+
update_has_many_through_associations assoc, orig, current_value, :remove_member
|
60
|
+
update_has_many_through_associations assoc, orig, value, :add_member
|
61
|
+
remove_current_inverse_attribute assoc, orig, current_value
|
62
|
+
add_new_inverse_attribute assoc, orig, value
|
63
|
+
update_belongs_to attr, value.itself
|
64
|
+
end
|
65
|
+
|
57
66
|
def sync_has_many(attr)
|
58
67
|
set_change_status_and_notify_only attr, @attributes[attr] != @synced_attributes[attr]
|
59
68
|
end
|
@@ -106,9 +115,8 @@ module ReactiveRecord
|
|
106
115
|
end
|
107
116
|
|
108
117
|
def change_status_and_notify_helper(attr, changed)
|
118
|
+
return if @being_destroyed
|
109
119
|
empty_before = changed_attributes.empty?
|
110
|
-
# TODO: confirm this works:
|
111
|
-
# || data_loading? added so that model.new can be wrapped in a ReactiveRecord.load_data
|
112
120
|
if !changed || data_loading?
|
113
121
|
changed_attributes.delete(attr)
|
114
122
|
elsif !changed_attributes.include?(attr)
|
@@ -125,70 +133,100 @@ module ReactiveRecord
|
|
125
133
|
)
|
126
134
|
end
|
127
135
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
current_value.attributes[inverse_attr].delete(@ar_instance) unless current_value.nil?
|
136
|
+
# when updating the inverse attribute of a belongs_to that is itself a belongs_to
|
137
|
+
# (i.e. 1-1 relationship) we clear the existing inverse value and then
|
138
|
+
# write the current record to the new value
|
139
|
+
|
140
|
+
# when updating an inverse attribute of a belongs_to that is a has_many (i.e. a collection)
|
141
|
+
# we need to first remove the current associated value (if non-nil), then add the new
|
142
|
+
# value to the collection. If the inverse collection is not yet initialized we do it here.
|
143
|
+
|
144
|
+
# the above is split into three methods, because the inverse of apolymorphic belongs to may
|
145
|
+
# change from has_one to has_many. So we first deal with the current value, then
|
146
|
+
# update the new value which uses the push_onto_collection helper
|
147
|
+
|
148
|
+
def remove_current_inverse_attribute(association, orig, model)
|
149
|
+
return if model.nil?
|
150
|
+
inverse_association = association.inverse(model)
|
151
|
+
return if inverse_association == orig
|
152
|
+
if inverse_association.collection?
|
153
|
+
# note we don't have to check if the collection exists, since it must
|
154
|
+
# exist as at this ar_instance is already part of it.
|
155
|
+
model.attributes[inverse_association.attribute].delete(@ar_instance)
|
149
156
|
else
|
150
|
-
|
157
|
+
model.attributes[inverse_association.attribute] = nil
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def add_new_inverse_attribute(association, orig, model)
|
162
|
+
return if model.nil?
|
163
|
+
inverse_association = association.inverse(model)
|
164
|
+
return if inverse_association == orig
|
165
|
+
if inverse_association.collection?
|
166
|
+
model.backing_record.push_onto_collection(@model, inverse_association, @ar_instance)
|
167
|
+
else
|
168
|
+
inverse_attr = inverse_association.attribute
|
169
|
+
model.attributes[inverse_attr] = @ar_instance
|
170
|
+
return if data_loading?
|
171
|
+
Hyperstack::Internal::State::Variable.set(model.backing_record, inverse_attr, @ar_instance)
|
151
172
|
end
|
152
173
|
end
|
153
174
|
|
154
175
|
def push_onto_collection(model, association, ar_instance)
|
155
176
|
@attributes[association.attribute] ||= Collection.new(model, @ar_instance, association)
|
156
|
-
@attributes[association.attribute]
|
177
|
+
@attributes[association.attribute]._internal_push ar_instance
|
178
|
+
end
|
179
|
+
|
180
|
+
# class Membership < ActiveRecord::Base
|
181
|
+
# belongs_to :uzer
|
182
|
+
# belongs_to :memerable, polymorphic: true
|
183
|
+
# end
|
184
|
+
#
|
185
|
+
# class Project < ActiveRecord::Base
|
186
|
+
# has_many :memberships, as: :memerable, dependent: :destroy
|
187
|
+
# has_many :uzers, through: :memberships
|
188
|
+
# end
|
189
|
+
#
|
190
|
+
# class Group < ActiveRecord::Base
|
191
|
+
# has_many :memberships, as: :memerable, dependent: :destroy
|
192
|
+
# has_many :uzers, through: :memberships
|
193
|
+
# end
|
194
|
+
#
|
195
|
+
# class Uzer < ActiveRecord::Base
|
196
|
+
# has_many :memberships
|
197
|
+
# has_many :groups, through: :memberships, source: :memerable, source_type: 'Group'
|
198
|
+
# has_many :projects, through: :memberships, source: :memerable, source_type: 'Project'
|
199
|
+
# end
|
200
|
+
|
201
|
+
# membership.uzer = some_new_uzer (i.e. through association is changing)
|
202
|
+
# means membership.some_new_uzer.(groups OR projects) << uzer.memberable (depending on type of memberable)
|
203
|
+
# and we have to remove the current value of the source association (memerable) from the current uzer group or project
|
204
|
+
# and we have to then find any inverse has_many_through association (i.e. group or projects.uzers) and delete the
|
205
|
+
# current value from those collections and push the new value on
|
206
|
+
|
207
|
+
def update_has_many_through_associations(assoc, orig, value, method)
|
208
|
+
return if value.nil?
|
209
|
+
assoc.through_associations(value).each do |ta|
|
210
|
+
next if orig == ta
|
211
|
+
source_value = @attributes[ta.source]
|
212
|
+
# skip if source value is nil or if type of the association does not match type of source
|
213
|
+
next unless source_value.class.to_s == ta.source_type
|
214
|
+
ta.send method, source_value, value
|
215
|
+
ta.source_associations(source_value).each do |sa|
|
216
|
+
sa.send method, value, source_value
|
217
|
+
end
|
218
|
+
end
|
157
219
|
end
|
158
220
|
|
159
|
-
def
|
160
|
-
|
161
|
-
|
162
|
-
end
|
221
|
+
# def remove_src_assoc(sa, source_value, current_value)
|
222
|
+
# source_inverse_collection = source_value.attributes[sa.attribute]
|
223
|
+
# source_inverse_collection.delete(current_value) if source_inverse_collection
|
224
|
+
# end
|
225
|
+
#
|
226
|
+
# def add_src_assoc(sa, source_value, new_value)
|
227
|
+
# source_value.attributes[sa.attribute] ||= Collection.new(sa.owner_class, source_value, sa)
|
228
|
+
# source_value.attributes[sa.attribute] << new_value
|
229
|
+
# end
|
163
230
|
|
164
|
-
def update_through_association(ta, new_belongs_to_value)
|
165
|
-
# appointment.doctor = doctor_new_value (i.e. through association is changing)
|
166
|
-
# means appointment.doctor_new_value.patients << appointment.patient
|
167
|
-
# and we have to appointment.doctor_current_value.patients.delete(appointment.patient)
|
168
|
-
source_value = @attributes[ta.source]
|
169
|
-
current_belongs_to_value = @attributes[ta.inverse.attribute]
|
170
|
-
return unless source_value
|
171
|
-
unless current_belongs_to_value.nil? || current_belongs_to_value.attributes[ta.attribute].nil?
|
172
|
-
current_belongs_to_value.attributes[ta.attribute].delete(source_value)
|
173
|
-
end
|
174
|
-
return unless new_belongs_to_value
|
175
|
-
new_belongs_to_value.attributes[ta.attribute] ||= Collection.new(ta.klass, new_belongs_to_value, ta)
|
176
|
-
new_belongs_to_value.attributes[ta.attribute] << source_value
|
177
|
-
end
|
178
|
-
|
179
|
-
def update_source_association(sa, new_source_value)
|
180
|
-
# appointment.patient = patient_value (i.e. source is changing)
|
181
|
-
# means appointment.doctor.patients.delete(appointment.patient)
|
182
|
-
# means appointment.doctor.patients << patient_value
|
183
|
-
belongs_to_value = @attributes[sa.inverse.attribute]
|
184
|
-
current_source_value = @attributes[sa.source]
|
185
|
-
return unless belongs_to_value
|
186
|
-
unless belongs_to_value.attributes[sa.attribute].nil? || current_source_value.nil?
|
187
|
-
belongs_to_value.attributes[sa.attribute].delete(current_source_value)
|
188
|
-
end
|
189
|
-
return unless new_source_value
|
190
|
-
belongs_to_value.attributes[sa.attribute] ||= Collection.new(sa.klass, belongs_to_value, sa)
|
191
|
-
belongs_to_value.attributes[sa.attribute] << new_source_value
|
192
|
-
end
|
193
231
|
end
|
194
232
|
end
|
@@ -20,13 +20,18 @@ module ReactiveRecord
|
|
20
20
|
salt = SecureRandom.hex
|
21
21
|
authorization = Hyperstack.authorization(salt, data[:channel], data[:broadcast_id])
|
22
22
|
raise 'no server running' unless Hyperstack::Connection.root_path
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
23
|
+
Timeout::timeout(Hyperstack.send_to_server_timeout) do
|
24
|
+
SendPacket.remote(
|
25
|
+
Hyperstack::Connection.root_path,
|
26
|
+
data,
|
27
|
+
operation: operation,
|
28
|
+
salt: salt,
|
29
|
+
authorization: authorization
|
30
|
+
).tap { |p| raise p.error if p.rejected? }
|
31
|
+
end
|
32
|
+
rescue Timeout::Error
|
33
|
+
puts "\n********* FAILED TO RECEIVE RESPONSE FROM SERVER WITHIN #{Hyperstack.send_to_server_timeout} SECONDS. CHANGES WILL NOT BE SYNCED ************\n"
|
34
|
+
raise 'no server running'
|
30
35
|
end unless RUBY_ENGINE == 'opal'
|
31
36
|
|
32
37
|
class SendPacket < Hyperstack::ServerOp
|
@@ -67,7 +72,7 @@ module ReactiveRecord
|
|
67
72
|
|
68
73
|
def self.to_self(record, data = {})
|
69
74
|
# simulate incoming packet after a local save
|
70
|
-
operation = if record.
|
75
|
+
operation = if record.new_record?
|
71
76
|
:create
|
72
77
|
elsif record.destroyed?
|
73
78
|
:destroy
|
@@ -177,7 +182,12 @@ module ReactiveRecord
|
|
177
182
|
|
178
183
|
# once we have received all the data from all the channels (applies to create and update only)
|
179
184
|
# we yield and process the record
|
180
|
-
|
185
|
+
|
186
|
+
# pusher fake can send duplicate records which will result in a nil broadcast
|
187
|
+
# so we also check that before yielding
|
188
|
+
if @channels == @received && (broadcast = complete!)
|
189
|
+
yield broadcast
|
190
|
+
end
|
181
191
|
end
|
182
192
|
end
|
183
193
|
|
@@ -168,12 +168,12 @@ end
|
|
168
168
|
module Kernel
|
169
169
|
# (see Browser::Window#after)
|
170
170
|
def after(time, &block)
|
171
|
-
|
171
|
+
$window.after(time, &block)
|
172
172
|
end
|
173
173
|
|
174
174
|
# (see Browser::Window#after!)
|
175
175
|
def after!(time, &block)
|
176
|
-
|
176
|
+
$window.after!(time, &block)
|
177
177
|
end
|
178
178
|
end
|
179
179
|
|
@@ -187,4 +187,4 @@ class Proc
|
|
187
187
|
def after!(time)
|
188
188
|
$window.after!(time, &self)
|
189
189
|
end
|
190
|
-
end
|
190
|
+
end
|
@@ -93,8 +93,9 @@ module ReactiveRecord
|
|
93
93
|
vector = []
|
94
94
|
path.split('.').inject(@model) do |model, attribute|
|
95
95
|
association = model.reflect_on_association(attribute)
|
96
|
-
|
97
|
-
|
96
|
+
inverse_of = association.inverse_of if association
|
97
|
+
raise build_error(path, model, attribute) unless inverse_of
|
98
|
+
vector = [inverse_of, *vector]
|
98
99
|
@joins[association.klass] << vector
|
99
100
|
association.klass
|
100
101
|
end
|
@@ -280,6 +280,8 @@ module ReactiveRecord
|
|
280
280
|
if !cache_item.value || cache_item.value.is_a?(Array)
|
281
281
|
# seeing as we just returning representative, no check is needed (its already checked)
|
282
282
|
representative
|
283
|
+
elsif method == 'model_name'
|
284
|
+
cache_item.build_new_cache_item(timing(:active_record) { cache_item.value.model_name }, method, method)
|
283
285
|
else
|
284
286
|
begin
|
285
287
|
secured_method = "__secure_remote_access_to_#{[*method].first}"
|
@@ -449,6 +451,13 @@ keys:
|
|
449
451
|
loaded_collection[0].backing_record.sync_attributes(attrs)
|
450
452
|
end
|
451
453
|
target.replace loaded_collection
|
454
|
+
# we need to notify any observers of the collection. collection#replace
|
455
|
+
# will not notify if we are data_loading (which we are) so we will do it
|
456
|
+
# here. BUT we want the notification to occur after the current event
|
457
|
+
# completes so we wrap it a bulk_update
|
458
|
+
Hyperstack::Internal::State::Mapper.bulk_update do
|
459
|
+
Hyperstack::Internal::State::Variable.set(target, :collection, target.collection)
|
460
|
+
end
|
452
461
|
end
|
453
462
|
|
454
463
|
if id_value = tree["id"] and id_value.is_a? Array
|
@@ -486,9 +495,10 @@ keys:
|
|
486
495
|
# we cannot use target.send "#{method}=" here because it might be a server method, which does not have a setter
|
487
496
|
# a better fix might be something like target._internal_attribute_hash[method] = ...
|
488
497
|
target.backing_record.set_attr_value(method, value.first) unless method == :id
|
489
|
-
elsif value.is_a?
|
498
|
+
elsif value.is_a?(Hash) && value[:id] && value[:id].first && (association = target.class.reflect_on_association(method))
|
490
499
|
# not sure if its necessary to check the id above... is it possible to for the method to be an association but not have an id?
|
491
|
-
|
500
|
+
klass = value[:model_name] ? Object.const_get(value[:model_name].first) : association.klass
|
501
|
+
new_target = ReactiveRecord::Base.find_by_id(klass, value[:id].first)
|
492
502
|
target.send "#{method}=", new_target
|
493
503
|
elsif !(target.class < ActiveRecord::Base)
|
494
504
|
new_target = target.send(*method)
|
@@ -500,7 +510,6 @@ keys:
|
|
500
510
|
load_from_json(value, new_target) if new_target
|
501
511
|
end
|
502
512
|
rescue Exception => e
|
503
|
-
# debugger
|
504
513
|
raise e
|
505
514
|
end
|
506
515
|
end
|
data/polymorph-notes.md
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
```ruby
|
2
|
+
class Picture < ApplicationRecord
|
3
|
+
belongs_to :imageable, polymorphic: true
|
4
|
+
end
|
5
|
+
|
6
|
+
class Employee < ApplicationRecord
|
7
|
+
has_many :pictures, as: :imageable
|
8
|
+
end
|
9
|
+
|
10
|
+
class Product < ApplicationRecord
|
11
|
+
has_many :pictures, as: :imageable
|
12
|
+
end
|
13
|
+
```
|
14
|
+
|
15
|
+
product|employee.pictures -> works almost as normal has_many as far as Hyperstack client is concerned
|
16
|
+
imageable is the "alias" of product|employee. Its as if there is a class Imageable that is the superclass
|
17
|
+
of Product and Employee.
|
18
|
+
|
19
|
+
so has_many :pictures means the usual thing (i.e. there is a belongs_to relationship on Picture) its just that
|
20
|
+
the belongs_to will be belonging to :imageable instead of :employee or :product.
|
21
|
+
|
22
|
+
okay fine
|
23
|
+
|
24
|
+
the other way:
|
25
|
+
|
26
|
+
the problem is that picture.imageable while loading is pointing to a dummy class (sure call it Imageable)
|
27
|
+
so if we say picture.imageable.foo.bar.blat what we get is a dummy value that responds to all methods, and returns itself:
|
28
|
+
|
29
|
+
picture.imageable -> imageable123 .foo -> imageable123 .bar -> ... etc. but it is a dummy value that will cause a fetch of the actual imageable record (or nil).
|
30
|
+
|
31
|
+
.imageable should be able to leverage off of server_method.
|
32
|
+
|
33
|
+
server_method(:imageable, PolymorphicDummy.new(:imageable))
|
34
|
+
|
35
|
+
hmmmm....
|
36
|
+
|
37
|
+
really its like doing a picture.imageable.itself (?) (that may work Juuuust fine)
|
38
|
+
|
39
|
+
so picture.imageable returns this funky dummy value but does an across the wire request for picture.imageable (which should get imageable_id per a normal relationship) and also get picture.imageable_type.
|
40
|
+
|
41
|
+
|
42
|
+
start again....
|
43
|
+
|
44
|
+
what happens if we ignore (on the client) the polymorphic: and as: keys?
|
45
|
+
|
46
|
+
belongs_to :imageable
|
47
|
+
|
48
|
+
means there is a class Imageable, okay so we make one, and add has_many :pictures to it.
|
49
|
+
|
50
|
+
|
51
|
+
and again....
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
def imageable
|
55
|
+
if imageable_type.loaded? && imageable_id.loaded?
|
56
|
+
const_get(imageable_type).find(imageable_id)
|
57
|
+
else
|
58
|
+
DummyImageable.new(self)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
```
|
62
|
+
|
63
|
+
very close but will not work for cases like this:
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
pic = Picture.new
|
67
|
+
employee.pictures << pic
|
68
|
+
pic.imageable # FAIL... (until its been saved)
|
69
|
+
...
|
70
|
+
```
|
71
|
+
|
72
|
+
but still it may be as simple as overriding `<<` so that it sets type on imageable. But we still to have a proper belongs to relationship.
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
def imageable
|
76
|
+
if we already have the attribute set
|
77
|
+
return the attribute
|
78
|
+
else
|
79
|
+
set attribute to DummyPolyClass.new(self, 'imageable')
|
80
|
+
# DummyPolyClass init will set up a fetch of the actual imageable value
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def imageable=(x)
|
85
|
+
# will it just work ?
|
86
|
+
end
|
87
|
+
```
|
88
|
+
|
89
|
+
its all about the collection inverse. The inverse class of the has_many is the class containing the polymorphic belongs to. But the inverse of a polymorphic belongs to depends on the value. If the value is nil or a DummyPolyClass object then there is no inverse.
|
90
|
+
|
91
|
+
I think if inverse takes this into account then `<<` and `=` should just "work" (well almost) and probably everything else will to.
|
92
|
+
|
93
|
+
### NOTES on the DummyPolyClass...
|
94
|
+
|
95
|
+
it needs to respond to reflect_on_all_associations, but just return an empty array. This way when we search for matching inverse attribute we won't find it.
|
96
|
+
|
97
|
+
### Status
|
98
|
+
|
99
|
+
added model to inverse, inverse_of, find_inverse
|
100
|
+
|
101
|
+
if the relationship is a collection then we will always know the inverse.
|
102
|
+
|
103
|
+
The only time we might no know the inverse is if its NOT a collection (i.e. belongs_to)
|
104
|
+
|
105
|
+
So only places that are applying inverse to an association that is NOT a collection do we have to pass the model in.
|
106
|
+
|
107
|
+
All inverse_of method calls have been checked and updated
|
108
|
+
|
109
|
+
that leaves inverse which is only used in SETTERS hurray!
|
110
|
+
|
111
|
+
|
112
|
+
### Latest thinking
|
113
|
+
|
114
|
+
going from `has_many / has_one as: ...` is easy its essentially setting the association foreign_key using the name supplied to the as:
|
115
|
+
|
116
|
+
The problem is going from the polymorphic belongs_to side.
|
117
|
+
|
118
|
+
We don't know the actual type we are loading which presents two problems.
|
119
|
+
|
120
|
+
First we just don't know the type. So if I say `Picture.find(1).imageable.foo.bar` I really can't do anything with foo and bar. This is solved by having a DummyPolymorph class, which responds to all missing methods with itself, and on creation sets up a vector to pull it the id, and type of the record being fetched. This will cause a second fetch to actually get `foo.bar` because we don't know what they are yet. (Its cool beacuse this is like Type inference actually, and I think we could eventually use a type inference system to get rid of the second fetch!!!)
|
121
|
+
|
122
|
+
Second we don't know the inverse of the relationship (since we don't know the type)
|
123
|
+
|
124
|
+
We can solve this by aliasing the inverse relationship (the one with the `as: SOMENAME` option) to be `has_many #{__hyperstack_polymorphic_inverse_of_#{SOMENAME}` and then defining method(s) against the relationship name. This way regardless of what the polymorphic relationship points to we know the inverse is `__hyperstack_polymorphic_inverse_of_#{SOMENAME}`.
|
125
|
+
|
126
|
+
If the inverse relationship is a has_many then we define
|
127
|
+
```ruby
|
128
|
+
def #{RELATIONSHIP_NAME}
|
129
|
+
__hyperstack_polymorphic_inverse_of_#{SOMENAME}
|
130
|
+
end
|
131
|
+
```
|
132
|
+
|
133
|
+
If the inverse relationship is a has_one we have to work a bit harder:
|
134
|
+
```ruby
|
135
|
+
def #{RELATIONSHIP_NAME}
|
136
|
+
__hyperstack_polymorphic_inverse_of_#{SOMENAME}[0]
|
137
|
+
end
|
138
|
+
def #{RELATIONSHIP_NAME}=(x)
|
139
|
+
__hyperstack_polymorphic_inverse_of_#{SOMENAME}[0] = x # or perhaps we have to replace the array using the internal method in collection for that purpose.
|
140
|
+
end
|
141
|
+
```
|
142
|
+
|
143
|
+
The remaining problem is that the server side will have no such relationships defined so we need to add the `has_many __hyperstack_polymorphic_inverse_of_#{SOMENAME} as: SOMENAME` server side.
|