Skip to content

Commit 657cd77

Browse files
committed
Merge branch 'poly-assoc3' into catprint-edge-poly-assoc
2 parents c5f4dc9 + 285a7e6 commit 657cd77

File tree

14 files changed

+833
-152
lines changed

14 files changed

+833
-152
lines changed

.travis.yml

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -54,32 +54,32 @@ _deploy_gem: &_deploy_gem
5454

5555
jobs:
5656
include:
57-
- <<: *_test_gem
58-
env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1
59-
- <<: *_test_gem
60-
env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1
61-
- <<: *_test_gem
62-
env: COMPONENT=hyper-state RUBY_VERSION=2.5.1
63-
- <<: *_test_gem
64-
env: COMPONENT=hyper-component RUBY_VERSION=2.5.1
57+
# - <<: *_test_gem
58+
# env: COMPONENT=hyper-i18n RUBY_VERSION=2.5.1
59+
# - <<: *_test_gem
60+
# env: COMPONENT=hyper-trace RUBY_VERSION=2.5.1
61+
# - <<: *_test_gem
62+
# env: COMPONENT=hyper-state RUBY_VERSION=2.5.1
63+
# - <<: *_test_gem
64+
# env: COMPONENT=hyper-component RUBY_VERSION=2.5.1
6565
- <<: *_test_gem
6666
env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part1
6767
- <<: *_test_gem
6868
env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part2
6969
- <<: *_test_gem
7070
env: COMPONENT=hyper-model RUBY_VERSION=2.5.1 TASK=part3
71-
- <<: *_test_gem
72-
env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1
73-
- <<: *_test_gem
74-
env: COMPONENT=hyper-router RUBY_VERSION=2.5.1
75-
- <<: *_test_gem
76-
env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1
77-
- <<: *_test_gem
78-
env: COMPONENT=hyper-store RUBY_VERSION=2.5.1
79-
- <<: *_test_gem
80-
env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1
81-
- <<: *_test_gem
82-
env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1
71+
# - <<: *_test_gem
72+
# env: COMPONENT=hyper-operation RUBY_VERSION=2.5.1
73+
# - <<: *_test_gem
74+
# env: COMPONENT=hyper-router RUBY_VERSION=2.5.1
75+
# - <<: *_test_gem
76+
# env: COMPONENT=hyper-spec RUBY_VERSION=2.5.1
77+
# - <<: *_test_gem
78+
# env: COMPONENT=hyper-store RUBY_VERSION=2.5.1
79+
# - <<: *_test_gem
80+
# env: COMPONENT=rails-hyperstack RUBY_VERSION=2.5.1
81+
# - <<: *_test_gem
82+
# env: COMPONENT=hyperstack-config RUBY_VERSION=2.5.1
8383

8484
- <<: *_deploy_gem
8585
env: COMPONENT=hyper-i18n

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
require "reactive_record/active_record/reactive_record/isomorphic_base"
2121
require 'reactive_record/active_record/reactive_record/dummy_value'
2222
require 'reactive_record/active_record/reactive_record/column_types'
23+
require 'reactive_record/active_record/reactive_record/dummy_polymorph'
2324
require "reactive_record/active_record/aggregations"
2425
require "reactive_record/active_record/associations"
2526
require "reactive_record/active_record/reactive_record/backing_record_inspector"

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

Lines changed: 108 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,39 @@ class AssociationReflection
3838
attr_reader :macro
3939
attr_reader :owner_class
4040
attr_reader :source
41+
attr_reader :source_type
42+
attr_reader :options
43+
attr_reader :polymorphic_type_attribute
4144

4245
def initialize(owner_class, macro, name, options = {})
4346
owner_class.reflect_on_all_associations << self
4447
@owner_class = owner_class
4548
@macro = macro
4649
@options = options
47-
@klass_name = options[:class_name] || (collection? && name.camelize.singularize) || name.camelize
48-
@association_foreign_key = options[:foreign_key] || (macro == :belongs_to && "#{name}_id") || "#{@owner_class.name.underscore}_id"
49-
@source = options[:source] || @klass_name.underscore if options[:through]
50+
unless options[:polymorphic]
51+
@klass_name = options[:class_name] || (collection? && name.camelize.singularize) || name.camelize
52+
end
53+
@association_foreign_key =
54+
options[:foreign_key] ||
55+
(macro == :belongs_to && "#{name}_id") ||
56+
(options[:as] && "#{options[:as]}_id") ||
57+
(options[:polymorphic] && "#{name}_id") ||
58+
"#{@owner_class.name.underscore}_id"
59+
if options[:through]
60+
@source = options[:source] || @klass_name.underscore
61+
@source_type = options[:source_type] || @klass_name
62+
end
63+
@polymorphic_type_attribute = "#{name}_type" if options[:polymorphic]
5064
@attribute = name
65+
@through_associations = Hash.new { |_h, k| [] unless k }
66+
end
67+
68+
def collection?
69+
@macro == :has_many
70+
end
71+
72+
def singular?
73+
@macro != :has_many
5174
end
5275

5376
def through_association
@@ -62,63 +85,116 @@ def through_association
6285

6386
alias through_association? through_association
6487

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

73-
def source_associations
74-
# find all associations that use this association as the source
75-
# that is final all associations that are using this association as the source in a
76-
# through relationship
77-
@source_associations ||= owner_class.reflect_on_all_associations.collect do |sibling|
78-
sibling.klass.reflect_on_all_associations.select do |assoc|
79-
assoc.source == attribute
127+
def source_belongs_to_association # private
128+
# given self is a has_many_through association return the corresponding belongs_to association
129+
# for the source
130+
@source_belongs_to_association ||=
131+
through_association.inverse.owner_class.reflect_on_all_associations.detect do |sibling|
132+
sibling.attribute == source
80133
end
81-
end.flatten
82134
end
83135

84-
def inverse
85-
@inverse ||=
86-
through_association ? through_association.inverse : find_inverse
136+
def source_associations(model)
137+
# given self is a has_many_through association find the source_association for the given model
138+
source_belongs_to_association.through_associations(model)
87139
end
88140

89-
def inverse_of
90-
@inverse_of ||= inverse.attribute
141+
alias :polymorphic? polymorphic_type_attribute
142+
143+
def inverse(model = nil)
144+
return @inverse if @inverse
145+
ta = through_association
146+
found = ta ? ta.inverse : find_inverse(model)
147+
@inverse = found unless polymorphic?
148+
found
91149
end
92150

93-
def find_inverse
94-
klass.reflect_on_all_associations.each do |association|
151+
def inverse_of(model = nil)
152+
inverse(model).attribute
153+
end
154+
155+
def find_inverse(model) # private
156+
the_klass = klass(model)
157+
the_klass.reflect_on_all_associations.each do |association|
95158
next if association.association_foreign_key != @association_foreign_key
96-
next if association.klass != @owner_class
97159
next if association.attribute == attribute
98-
return association if klass == association.owner_class
160+
return association if the_klass == association.owner_class
99161
end
162+
raise "could not find inverse of polymorphic belongs_to: #{model.inspect} #{self.inspect}" if options[:polymorphic]
100163
# instead of raising an error go ahead and create the inverse relationship if it does not exist.
101164
# https://github.com/hyperstack-org/hyperstack/issues/89
102165
if macro == :belongs_to
103-
Hyperstack::Component::IsomorphicHelpers.log "**** warning dynamically adding relationship: #{klass}.has_many :#{@owner_class.name.underscore.pluralize}, foreign_key: #{@association_foreign_key}", :warning
104-
klass.has_many @owner_class.name.underscore.pluralize, foreign_key: @association_foreign_key
166+
Hyperstack::Component::IsomorphicHelpers.log "**** warning dynamically adding relationship: #{the_klass}.has_many :#{@owner_class.name.underscore.pluralize}, foreign_key: #{@association_foreign_key}", :warning
167+
the_klass.has_many @owner_class.name.underscore.pluralize, foreign_key: @association_foreign_key
168+
elsif options[:as]
169+
Hyperstack::Component::IsomorphicHelpers.log "**** warning dynamically adding relationship: #{the_klass}.belongs_to :#{options[:as]}, polymorphic: true", :warning
170+
the_klass.belongs_to options[:as], polymorphic: true
105171
else
106-
Hyperstack::Component::IsomorphicHelpers.log "**** warning dynamically adding relationship: #{klass}.belongs_to :#{@owner_class.name.underscore}, foreign_key: #{@association_foreign_key}", :warning
107-
klass.belongs_to @owner_class.name.underscore, foreign_key: @association_foreign_key
172+
Hyperstack::Component::IsomorphicHelpers.log "**** warning dynamically adding relationship: #{the_klass}.belongs_to :#{@owner_class.name.underscore}, foreign_key: #{@association_foreign_key}", :warning
173+
the_klass.belongs_to @owner_class.name.underscore, foreign_key: @association_foreign_key
108174
end
109175
end
110176

111-
def klass
112-
@klass ||= Object.const_get(@klass_name)
177+
def klass(model = nil)
178+
@klass ||= Object.const_get(@klass_name) if @klass_name
179+
raise "model is not correct class" if @klass && model && model.class != @klass
180+
raise "no model supplied for polymorphic relationship" unless @klass || model
181+
@klass || model.class
113182
end
114183

115184
def collection?
116185
[:has_many].include? @macro
117186
end
118187

119-
end
188+
def remove_member(member, owner)
189+
collection = owner.attributes[attribute]
190+
return if collection.nil?
191+
collection.delete(member)
192+
end
120193

194+
def add_member(member, owner)
195+
owner.attributes[attribute] ||= ReactiveRecord::Collection.new(owner_class, owner, self)
196+
owner.attributes[attribute]._internal_push member
197+
end
198+
end
121199
end
122-
123-
124200
end

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

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -373,22 +373,53 @@ def _react_param_conversion(param, opt = nil)
373373

374374
associations = reflect_on_all_associations
375375

376+
already_processed_keys = Set.new
377+
old_param = param.dup
378+
376379
param = param.collect do |key, value|
377-
assoc = associations.detect do |association|
378-
association.association_foreign_key == key
380+
next if already_processed_keys.include? key
381+
382+
model_name = model_id = nil
383+
384+
assoc = associations.detect do |poly_assoc|
385+
if key == poly_assoc.polymorphic_type_attribute
386+
model_name = value
387+
already_processed_keys << poly_assoc.association_foreign_key
388+
elsif key == poly_assoc.association_foreign_key
389+
model_id = value
390+
already_processed_keys << poly_assoc.polymorphic_type_attribute
391+
end
379392
end
380393

381394
if assoc
382-
if value
383-
[assoc.attribute, { id: [value] }]
384-
else
395+
if !value
385396
[assoc.attribute, [nil]]
397+
elsif assoc.polymorphic?
398+
model_id ||= param.detect { |k, *| k == assoc.association_foreign_key }&.last
399+
model_id ||= target.send(assoc.attribute)&.id
400+
if model_id.nil?
401+
raise "Error in #{self.name}._react_param_conversion. \n"\
402+
"Could not determine the id of #{assoc.attribute} of #{target.inspect}.\n"\
403+
"It was not provided in the conversion data, "\
404+
"and it is unknown on the client"
405+
end
406+
model_name ||= param.detect { |k, *| k == assoc.polymorphic_type_attribute }&.last
407+
model_name ||= target.send(assoc.polymorphic_type_attribute)
408+
unless Object.const_defined?(model_name)
409+
raise "Error in #{self.name}._react_param_conversion. \n"\
410+
"Could not determine the type of #{assoc.attribute} of #{target.inspect}.\n"\
411+
"It was not provided in the conversion data, "\
412+
"and it is unknown on the client"
413+
end
414+
415+
[assoc.attribute, { id: [model_id], model_name: [model_name] }]
416+
else
417+
[assoc.attribute, { id: [value]}]
386418
end
387419
else
388420
[key, [value]]
389421
end
390-
end
391-
# TODO: verify wrapping with load_data was added so broadcasting works in 1.0.0.lap28
422+
end.compact
392423
ReactiveRecord::Base.load_data do
393424
ReactiveRecord::ServerDataCache.load_from_json(Hash[param], target)
394425
end

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@ def self.new_from_vector(model, aggregate_owner, *vector)
118118
# this is the equivilent of find but for associations and aggregations
119119
# because we are not fetching a specific attribute yet, there is NO communication with the
120120
# server. That only happens during find.
121+
122+
return DummyPolymorph.new(vector) unless model
123+
121124
model = model.base_class
122125

123126
# do we already have a record with this vector? If so return it, otherwise make a new one.
@@ -250,7 +253,7 @@ def sync_unscoped_collection!
250253
return if @create_sync
251254
@create_sync = true
252255
end
253-
model.unscoped << ar_instance
256+
model.unscoped._internal_push ar_instance
254257
@synced_with_unscoped = !@synced_with_unscoped
255258
end
256259

0 commit comments

Comments
 (0)