hyperstack-org / hyperstack-org/hyperstack

foreign_keys should track relationship before save

Open
#279 0 comments 0 reactions 0 assignees View on GitHub
enhancement
Dominant language
JavaScript
Stars
538
Forks
41
PR merge metrics
No merged PRs in 30d

Description

Currently until a model is saved the foreign_key and the relationships will not be synchronized.

Instead the foreign_key and the relationships should be synchronized on the fly.

For example `some_model.parent_id = 12` should result in `some_model.parent == OtherModel.find(12)`

Likewise `some_model.parent = OtherModel.find(17)` should result in `some_model.parent_id == 17`

I did a little of playing with how to get this done but got distracted... here is some notes a diff...

```text
=begin

setting the foreign key attribute should also set the belongs_to
setting the belongs_to value should also set the foreign key value

problem is making the correspondence without searching the associations each time.

be great to override the setters in each case. The setter for the foreign key is already done
in the _react_param_conversion method. How to override the foreign key setter without messing
up the ability to override the setter and use super?

=end

diff --git a/ruby/hyper-model/lib/reactive_record/active_record/associations.rb b/ruby/hyper-model/lib/reactive_record/active_record/associations.rb
index c9a85459..bcde4c18 100644
--- a/ruby/hyper-model/lib/reactive_record/active_record/associations.rb
+++ b/ruby/hyper-model/lib/reactive_record/active_record/associations.rb
@@ -11,7 +11,7 @@ module ActiveRecord
end

def self.reflect_on_association_by_foreign_key(key)
- reflection_finder { |assoc| assoc.association_foreign_key == key }
+ reflection_finder { |assoc| assoc.association_foreign_key == key && assoc.macro != :has_many }
end

def self.reflection_finder(&block)
@@ -161,6 +161,7 @@ module ActiveRecord
def find_inverse(model) # private
the_klass = klass(model)
the_klass.reflect_on_all_associations.each do |association|
+ next if association == self
next if association.association_foreign_key != @association_foreign_key
next if association.attribute == attribute
return association if association.polymorphic? || association.klass == owner_class
diff --git a/ruby/hyper-model/lib/reactive_record/active_record/class_methods.rb b/ruby/hyper-model/lib/reactive_record/active_record/class_methods.rb
index e0459863..3fd4dfee 100644
--- a/ruby/hyper-model/lib/reactive_record/active_record/class_methods.rb
+++ b/ruby/hyper-model/lib/reactive_record/active_record/class_methods.rb
@@ -301,6 +301,16 @@ module ActiveRecord
else
define_method(name) { @backing_record.get_belongs_to(assoc, nil) }
define_method("#{name}=") { |val| @backing_record.set_belongs_to(assoc, val) }
+ # if macro == :belongs_to
+ # define_method("#_hyperstack_foreign_key_setter_#{assoc.association_foreign_key}") do |val|
+ # puts "#_hyperstack_foreign_key_setter_#{assoc.association_foreign_key}(#{val})"
+ # @backing_record.set_belongs_to(assoc, self.class.find(val))
+ # end
+ # define_method("#_hyperstack_foreign_key_getter_#{assoc.association_foreign_key}") do
+ # puts "#_hyperstack_foreign_key_getter_#{assoc.association_foreign_key}"
+ # send(name)&.id
+ # end
+ # end
end
assoc
end
@@ -367,6 +377,7 @@ module ActiveRecord
define_method(name) { @backing_record.get_attr_value(name, nil) } unless method_defined?(name)
define_method("#{name}!") { @backing_record.get_attr_value(name, true) } unless method_defined?("#{name}!")
define_method("#{name}=") { |val| @backing_record.set_attr_value(name, val) } unless method_defined?("#{name}=")
+ #define_method("#_hyperstack_foreign_key_setter_#{name}") { |*args| } unless method_defined?("#_hyperstack_foreign_key_setter_#{name}")
define_method("#{name}_changed?") { @backing_record.changed?(name) } unless method_defined?("#{name}_changed?")
define_method("#{name}?") { @backing_record.get_attr_value(name, nil).present? } unless method_defined?("#{name}?")
end
@@ -384,6 +395,7 @@ module ActiveRecord
klass = ReactiveRecord::Base.infer_type_from_hash(self, param)
klass == self || klass < self
else
+ #debugger if param[:duplicate_of_id]
# TODO: investigate saving .changes here and then replacing the
# TODO: changes after the load is complete. In other words preserve the
# TODO: changed values as changes while just updating the synced values.
@@ -415,7 +427,7 @@ module ActiveRecord
if key == poly_assoc.polymorphic_type_attribute
model_name = value
already_processed_keys << poly_assoc.association_foreign_key
- elsif key == poly_assoc.association_foreign_key # && poly_assoc.polymorphic_type_attribute #poly_assoc.macro != :has_many
+ elsif key == poly_assoc.association_foreign_key && poly_assoc.macro != :has_many
model_id = value
already_processed_keys << poly_assoc.polymorphic_type_attribute
end
diff --git a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/getters.rb b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/getters.rb
index b60cba33..47915bc1 100644
--- a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/getters.rb
+++ b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/getters.rb
@@ -26,8 +26,14 @@ module ReactiveRecord
end

def get_attr_value(attr, reload = nil)
- non_relationship_getter_common(attr, reload) do
- sync_attribute attr, convert(attr, model.columns_hash[attr][:default])
+ assoc = model.reflect_on_all_associations.detect { |a| a.association_foreign_key == attr && a.macro == :belongs_to }
+ if assoc
+ puts "get_attr_value converting to get_belongs_to"
+ get_belongs_to(assoc, reload)&.id
+ else
+ non_relationship_getter_common(attr, reload) do
+ sync_attribute attr, convert(attr, model.columns_hash[attr][:default])
+ end
end
end

diff --git a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/setters.rb b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/setters.rb
index a3458e12..d20a7e2a 100644
--- a/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/setters.rb
+++ b/ruby/hyper-model/lib/reactive_record/active_record/reactive_record/setters.rb
@@ -1,7 +1,21 @@
module ReactiveRecord
module Setters
def set_attr_value(attr, raw_value)
- set_common(attr, raw_value) { |value| update_simple_attribute(attr, value) }
+ puts "set_attr_value(#{attr}, #{raw_value})"
+ assoc = model.reflect_on_all_associations.detect { |assoc| assoc.association_foreign_key == attr && assoc.macro == :belongs_to }
+ if assoc
+ set_belongs_to(assoc, model.find(convert(attr, raw_value))) # TODO if raw_value is loading then this should be some kind of delayed find
+ raw_value
+ else
+
+ # @ar_instance.send "#_hyperstack_foreign_key_setter_#{attr}", raw_value
+ # set_attr_value_helper(attr, raw_value)
+ # end
+ #
+ # def set_attr_value_helper(attr, raw_value)
+ # puts "set_attr_value_helper(#{attr}, #{raw_value})"
+ set_common(attr, raw_value) { |value| update_simple_attribute(attr, value) }
+ end
end

def set_ar_aggregate(aggr, raw_value)
@@ -42,6 +56,11 @@ module ReactiveRecord
end

def set_belongs_to(assoc, raw_value)
+ #set_attr_value_helper(assoc.association_foreign_key, raw_value&.id)
+ # set_belongs_to_helper(assoc, raw_value)
+ # end
+ #
+ # def set_belongs_to_helper(assoc, raw_value)
set_common(assoc.attribute, raw_value) do |value, attr|
current_value = @attributes[assoc.attribute]
update_has_many_through_associations assoc, nil, current_value, :remove_member
@@ -49,6 +68,7 @@ module ReactiveRecord
remove_current_inverse_attribute assoc, nil, current_value
add_new_inverse_attribute assoc, nil, value
update_belongs_to attr, value.itself
+ @attributes[assoc.association_foreign_key] = value&.id
end
end

@@ -73,6 +93,7 @@ module ReactiveRecord
else
changed = !@synced_attributes.key?(attr) || @synced_attributes[attr] != value
end
+ puts "update_simple_attribute(#{attr}, #{value}) changed: #{changed} data_loading? #{data_loading?}"
set_attribute_change_status_and_notify attr, changed, value
end

@@ -105,6 +126,7 @@ module ReactiveRecord
end
end
end
+ puts "set_attribute_change_status_and_notify #{attr} = #{@attributes[attr]}"
end

def set_change_status_and_notify_only(attr, changed)
diff --git a/ruby/hyper-model/lib/reactive_record/server_data_cache.rb b/ruby/hyper-model/lib/reactive_record/server_data_cache.rb
index 62cb4e12..7519f312 100644
--- a/ruby/hyper-model/lib/reactive_record/server_data_cache.rb
+++ b/ruby/hyper-model/lib/reactive_record/server_data_cache.rb
@@ -499,6 +499,7 @@ keys:
# 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?
klass = value[:model_name] ? Object.const_get(value[:model_name].first) : association.klass
new_target = ReactiveRecord::Base.find_by_id(klass, value[:id].first)
+ puts "I think I am about to set #{target}.#{method}= #{new_target}"
target.send "#{method}=", new_target
elsif !(target.class < ActiveRecord::Base)
new_target = target.send(*method)
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.