Skip to content

Commit b7a2b4d

Browse files
committed
wip
1 parent e5dfd14 commit b7a2b4d

File tree

3 files changed

+110
-8
lines changed

3 files changed

+110
-8
lines changed

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

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ def initialize(owner_class, macro, name, options = {})
4444
@owner_class = owner_class
4545
@macro = macro
4646
@options = options
47-
@klass_name = options[:class_name] || (collection? && name.camelize.singularize) || name.camelize
47+
unless options[:polymorphic]
48+
@klass_name = options[:class_name] || (collection? && name.camelize.singularize) || name.camelize
49+
end
4850
@association_foreign_key = options[:foreign_key] || (macro == :belongs_to && "#{name}_id") || "#{@owner_class.name.underscore}_id"
4951
@source = options[:source] || @klass_name.underscore if options[:through]
5052
@attribute = name
@@ -81,16 +83,24 @@ def source_associations
8183
end.flatten
8284
end
8385

84-
def inverse
85-
@inverse ||=
86-
through_association ? through_association.inverse : find_inverse
86+
def polymorphic?
87+
!@klass_name
88+
end
89+
90+
def inverse(model)
91+
return @inverse if @inverse
92+
found = through_association ? through_association.inverse(model) : find_inverse(model)
93+
@inverse = found unless polymorphic?
94+
found
8795
end
8896

89-
def inverse_of
90-
@inverse_of ||= inverse.attribute
97+
def inverse_of(model)
98+
inverse(model)&.attribute
9199
end
92100

93-
def find_inverse
101+
def find_inverse(model)
102+
the_klass = klass || model.class
103+
return nil unless the_klass
94104
klass.reflect_on_all_associations.each do |association|
95105
next if association.association_foreign_key != @association_foreign_key
96106
next if association.klass != @owner_class

ruby/hyper-model/lib/reactive_record/scope_description.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ def map_joins_path(paths)
9393
vector = []
9494
path.split('.').inject(@model) do |model, attribute|
9595
association = model.reflect_on_association(attribute)
96-
raise build_error(path, model, attribute) unless association
96+
inverse_of = association.inverse_of(nil) if association
97+
raise build_error(path, model, attribute) unless inverse_of
9798
vector = [association.inverse_of, *vector]
9899
@joins[association.klass] << vector
99100
association.klass
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
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.

0 commit comments

Comments
 (0)