hyper-model 1.0.alpha1.4 → 1.0.alpha1.5
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|