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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0583cd6276013eed8abad6c7e12454455c6915e9773eb195b51977c8d748217f'
4
- data.tar.gz: a3e3f3e0aa5065281103566c6178bd2e4045a09863e1697d7587a5d2321f17dc
3
+ metadata.gz: a2458d824d9c7121f8e628dca72a9df1f6a87ecfc947361046887ea1eca56657
4
+ data.tar.gz: 4e6d8aa0a03d07c0080d73aa8bddbc876c1fa56eaebca3f7615425914f8fee07
5
5
  SHA512:
6
- metadata.gz: 7d21d5c7def5a3217d00757c611042da17efcaf639eba5fd5c021f79ffaa7e9f98334186d51c5c5353188c56b528286b91cc3d48ed4e8ecadf73cdae40cae04e
7
- data.tar.gz: 5aeaf368b37b2f4224ec0d7d3af83f44708f9093e97f7261d49d65e78af6ac0e5078c055d4fb974b4c2041491911087175e12e5988fc6873bec17ed388dee8ff
6
+ metadata.gz: 2e0e89354d96b059f62b51023a568e2b334ac2b47bc5940bc8ac6621940259e0b03ec90a61e5e9e135a07954d0cd75a93c6f765486412f80470aedb5a5dc96d0
7
+ data.tar.gz: 6b1642506f04f6fc00fd6e6be388632e1157e6405c629928a61d5a8d525fac8666b0f6611d4e261060573b2dc3b184ea3fe0c5640f1a955d2193899b6223202e
@@ -255,7 +255,7 @@ module ActiveRecord
255
255
  pre_syncromesh_has_many name, *args, opts.except(:regulate), &block
256
256
  end
257
257
 
258
- %i[belongs_to has_one].each do |macro|
258
+ %i[belongs_to has_one composed_of].each do |macro|
259
259
  alias_method :"pre_syncromesh_#{macro}", macro
260
260
  define_method(macro) do |name, *aargs, &block|
261
261
  define_method(:"__secure_remote_access_to_#{name}") do |this, _acting_user, *args|
data/lib/hyper-model.rb CHANGED
@@ -20,6 +20,7 @@ if RUBY_ENGINE == 'opal'
20
20
  require "reactive_record/active_record/reactive_record/isomorphic_base"
21
21
  require 'reactive_record/active_record/reactive_record/dummy_value'
22
22
  require 'reactive_record/active_record/reactive_record/column_types'
23
+ require 'reactive_record/active_record/reactive_record/dummy_polymorph'
23
24
  require "reactive_record/active_record/aggregations"
24
25
  require "reactive_record/active_record/associations"
25
26
  require "reactive_record/active_record/reactive_record/backing_record_inspector"
@@ -1,3 +1,3 @@
1
1
  module HyperModel
2
- VERSION = '1.0.alpha1.4'
2
+ VERSION = '1.0.alpha1.5'
3
3
  end
@@ -42,7 +42,8 @@ module Hyperstack
42
42
  Hyperstack::Internal::Component::RenderingContext.render(tag, opts) { children.each(&:render) }
43
43
  end
44
44
  end
45
- const_set component, klass
45
+
46
+ Object.const_set component, klass
46
47
  end
47
48
  end
48
49
  end
@@ -38,16 +38,45 @@ module ActiveRecord
38
38
  attr_reader :macro
39
39
  attr_reader :owner_class
40
40
  attr_reader :source
41
+ attr_reader :source_type
42
+ attr_reader :options
43
+ attr_reader :polymorphic_type_attribute
41
44
 
42
45
  def initialize(owner_class, macro, name, options = {})
43
46
  owner_class.reflect_on_all_associations << self
44
47
  @owner_class = owner_class
45
48
  @macro = macro
46
49
  @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
+
54
+ if @klass_name < ActiveRecord::Base
55
+ @klass = @klass_name
56
+ @klass_name = @klass_name.name
57
+ end rescue nil
58
+
59
+ @association_foreign_key =
60
+ options[:foreign_key] ||
61
+ (macro == :belongs_to && "#{name}_id") ||
62
+ (options[:as] && "#{options[:as]}_id") ||
63
+ (options[:polymorphic] && "#{name}_id") ||
64
+ "#{@owner_class.name.underscore}_id"
65
+ if options[:through]
66
+ @source = options[:source] || @klass_name.underscore
67
+ @source_type = options[:source_type] || @klass_name
68
+ end
69
+ @polymorphic_type_attribute = "#{name}_type" if options[:polymorphic]
50
70
  @attribute = name
71
+ @through_associations = Hash.new { |_h, k| [] unless k }
72
+ end
73
+
74
+ def collection?
75
+ @macro == :has_many
76
+ end
77
+
78
+ def singular?
79
+ @macro != :has_many
51
80
  end
52
81
 
53
82
  def through_association
@@ -62,63 +91,119 @@ module ActiveRecord
62
91
 
63
92
  alias through_association? through_association
64
93
 
65
- def through_associations
94
+ # class Membership < ActiveRecord::Base
95
+ # belongs_to :uzer
96
+ # belongs_to :memerable, polymorphic: true
97
+ # end
98
+ #
99
+ # class Project < ActiveRecord::Base
100
+ # has_many :memberships, as: :memerable, dependent: :destroy
101
+ # has_many :uzers, through: :memberships
102
+ # end
103
+ #
104
+ # class Group < ActiveRecord::Base
105
+ # has_many :memberships, as: :memerable, dependent: :destroy
106
+ # has_many :uzers, through: :memberships
107
+ # end
108
+ #
109
+ # class Uzer < ActiveRecord::Base
110
+ # has_many :memberships
111
+ # has_many :groups, through: :memberships, source: :memerable, source_type: 'Group'
112
+ # has_many :projects, through: :memberships, source: :memerable, source_type: 'Project'
113
+ # end
114
+
115
+ # so find the belongs_to relationship whose attribute == ta.source
116
+ # now find the inverse of that relationship using source_value as the model
117
+ # now find any has many through relationships that use that relationship as there source.
118
+ # each of those attributes in the source_value have to be updated.
119
+
120
+ # self is the through association
121
+
122
+
123
+ def through_associations(model)
124
+ # given self is a belongs_to association currently pointing to model
66
125
  # find all associations that use the inverse association as the through association
67
126
  # 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
127
+ the_klass = klass(model)
128
+ @through_associations[the_klass] ||= the_klass.reflect_on_all_associations.select do |assoc|
129
+ assoc.through_association&.inverse == self
70
130
  end
71
131
  end
72
132
 
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
133
+ def source_belongs_to_association # private
134
+ # given self is a has_many_through association return the corresponding belongs_to association
135
+ # for the source
136
+ @source_belongs_to_association ||=
137
+ through_association.inverse.owner_class.reflect_on_all_associations.detect do |sibling|
138
+ sibling.attribute == source
80
139
  end
81
- end.flatten
82
140
  end
83
141
 
84
- def inverse
85
- @inverse ||=
86
- through_association ? through_association.inverse : find_inverse
142
+ def source_associations(model)
143
+ # given self is a has_many_through association find the source_association for the given model
144
+ source_belongs_to_association.through_associations(model)
87
145
  end
88
146
 
89
- def inverse_of
90
- @inverse_of ||= inverse.attribute
147
+ alias :polymorphic? polymorphic_type_attribute
148
+
149
+ def inverse(model = nil)
150
+ return @inverse if @inverse
151
+ ta = through_association
152
+ found = ta ? ta.inverse : find_inverse(model)
153
+ @inverse = found unless polymorphic?
154
+ found
91
155
  end
92
156
 
93
- def find_inverse
94
- klass.reflect_on_all_associations.each do |association|
157
+ def inverse_of(model = nil)
158
+ inverse(model).attribute
159
+ end
160
+
161
+ def find_inverse(model) # private
162
+ the_klass = klass(model)
163
+ the_klass.reflect_on_all_associations.each do |association|
95
164
  next if association.association_foreign_key != @association_foreign_key
96
- next if association.klass != @owner_class
97
165
  next if association.attribute == attribute
98
- return association if klass == association.owner_class
166
+ return association if association.polymorphic? || association.klass == owner_class
99
167
  end
168
+ raise "could not find inverse of polymorphic belongs_to: #{model.inspect} #{self.inspect}" if options[:polymorphic]
100
169
  # instead of raising an error go ahead and create the inverse relationship if it does not exist.
101
170
  # https://github.com/hyperstack-org/hyperstack/issues/89
102
171
  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
172
+ Hyperstack::Component::IsomorphicHelpers.log "**** warning dynamically adding relationship: #{the_klass}.has_many :#{@owner_class.name.underscore.pluralize}, foreign_key: #{@association_foreign_key}", :warning
173
+ the_klass.has_many @owner_class.name.underscore.pluralize, foreign_key: @association_foreign_key
174
+ elsif options[:as]
175
+ Hyperstack::Component::IsomorphicHelpers.log "**** warning dynamically adding relationship: #{the_klass}.belongs_to :#{options[:as]}, polymorphic: true", :warning
176
+ the_klass.belongs_to options[:as], polymorphic: true
105
177
  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
178
+ Hyperstack::Component::IsomorphicHelpers.log "**** warning dynamically adding relationship: #{the_klass}.belongs_to :#{@owner_class.name.underscore}, foreign_key: #{@association_foreign_key}", :warning
179
+ the_klass.belongs_to @owner_class.name.underscore, foreign_key: @association_foreign_key
108
180
  end
109
181
  end
110
182
 
111
- def klass
112
- @klass ||= Object.const_get(@klass_name)
183
+ def klass(model = nil)
184
+ @klass ||= Object.const_get(@klass_name) if @klass_name
185
+ if @klass && model && !(model.class <= @klass || @klass <= model.class)
186
+ # TODO: added || @klass <= model.class can both cases really happen I guess so
187
+ raise "internal error: provided model #{model} is not subclass of #{@klass}"
188
+ end
189
+ raise 'no model supplied for polymorphic relationship' unless @klass || model
190
+ @klass || model.class
113
191
  end
114
192
 
115
193
  def collection?
116
194
  [:has_many].include? @macro
117
195
  end
118
196
 
119
- end
197
+ def remove_member(member, owner)
198
+ collection = owner.attributes[attribute]
199
+ return if collection.nil?
200
+ collection.delete(member)
201
+ end
120
202
 
203
+ def add_member(member, owner)
204
+ owner.attributes[attribute] ||= ReactiveRecord::Collection.new(owner_class, owner, self)
205
+ owner.attributes[attribute]._internal_push member
206
+ end
207
+ end
121
208
  end
122
-
123
-
124
209
  end
@@ -172,7 +172,7 @@ module ActiveRecord
172
172
  :full_table_name_prefix, :full_table_name_suffix, :reset_sequence_name, :sequence_name=, :next_sequence_value, :column_defaults, :content_columns,
173
173
  :readonly_attributes, :attr_readonly, :create, :create!, :instantiate, :find, :type_caster, :arel_table, :find_by, :find_by!, :initialize_find_by_cache,
174
174
  :generated_association_methods, :arel_engine, :arel_attribute, :predicate_builder, :collection_cache_key, :relation_delegate_class,
175
- :initialize_relation_delegate_cache, :enum, :collecting_queries_for_explain, :exec_explain, :i18n_scope, :lookup_ancestors, :human_attribute_name,
175
+ :initialize_relation_delegate_cache, :enum, :collecting_queries_for_explain, :exec_explain, :i18n_scope, :lookup_ancestors,
176
176
  :references, :uniq, :maximum, :none, :exists?, :second, :limit, :order, :eager_load, :update, :delete_all, :destroy, :ids, :many?, :pluck, :third,
177
177
  :delete, :fourth, :fifth, :forty_two, :second_to_last, :third_to_last, :preload, :sum, :take!, :first!, :last!, :second!, :offset, :select, :fourth!,
178
178
  :third!, :third_to_last!, :fifth!, :where, :first_or_create, :second_to_last!, :forty_two!, :first, :having, :any?, :one?, :none?, :find_or_create_by,
@@ -185,7 +185,10 @@ module ActiveRecord
185
185
  ]
186
186
 
187
187
  def method_missing(name, *args, &block)
188
- if args.count == 1 && name.start_with?("find_by_") && !block
188
+ if name == 'human_attribute_name'
189
+ opts = args[1] || {}
190
+ opts[:default] || args[0]
191
+ elsif args.count == 1 && name.start_with?("find_by_") && !block
189
192
  find_by(name.sub(/^find_by_/, '') => args[0])
190
193
  elsif [].respond_to?(name)
191
194
  all.send(name, *args, &block)
@@ -336,14 +339,34 @@ module ActiveRecord
336
339
  end
337
340
  end
338
341
 
342
+ # def define_attribute_methods
343
+ # columns_hash.each do |name, column_hash|
344
+ # next if name == primary_key
345
+ # column_hash[:serialized?] = true if ReactiveRecord::Base.serialized?[self][name]
346
+ #
347
+ # define_method(name) { @backing_record.get_attr_value(name, nil) } unless method_defined?(name)
348
+ # define_method("#{name}!") { @backing_record.get_attr_value(name, true) } unless method_defined?("#{name}!")
349
+ # define_method("#{name}=") { |val| @backing_record.set_attr_value(name, val) } unless method_defined?("#{name}=")
350
+ # define_method("#{name}_changed?") { @backing_record.changed?(name) } unless method_defined?("#{name}_changed?")
351
+ # define_method("#{name}?") { @backing_record.get_attr_value(name, nil).present? } unless method_defined?("#{name}?")
352
+ # end
353
+ # self.inheritance_column = nil if inheritance_column && !columns_hash.key?(inheritance_column)
354
+ # end
355
+
356
+
339
357
  def define_attribute_methods
340
358
  columns_hash.each do |name, column_hash|
341
359
  next if name == primary_key
342
- define_method(name) { @backing_record.get_attr_value(name, nil) }
343
- define_method("#{name}!") { @backing_record.get_attr_value(name, true) }
344
- define_method("#{name}=") { |val| @backing_record.set_attr_value(name, val) }
345
- define_method("#{name}_changed?") { @backing_record.changed?(name) }
346
- define_method("#{name}?") { @backing_record.get_attr_value(name, nil).present? }
360
+ # only add serialized key if its serialized. This just makes testing a bit
361
+ # easier by keeping the columns_hash the same if there are no seralized strings
362
+ # see rspec ./spec/batch1/column_types/column_type_spec.rb:100
363
+ column_hash[:serialized?] = true if ReactiveRecord::Base.serialized?[self][name]
364
+
365
+ define_method(name) { @backing_record.get_attr_value(name, nil) } unless method_defined?(name)
366
+ define_method("#{name}!") { @backing_record.get_attr_value(name, true) } unless method_defined?("#{name}!")
367
+ define_method("#{name}=") { |val| @backing_record.set_attr_value(name, val) } unless method_defined?("#{name}=")
368
+ define_method("#{name}_changed?") { @backing_record.changed?(name) } unless method_defined?("#{name}_changed?")
369
+ define_method("#{name}?") { @backing_record.get_attr_value(name, nil).present? } unless method_defined?("#{name}?")
347
370
  end
348
371
  self.inheritance_column = nil if inheritance_column && !columns_hash.key?(inheritance_column)
349
372
  end
@@ -373,22 +396,53 @@ module ActiveRecord
373
396
 
374
397
  associations = reflect_on_all_associations
375
398
 
399
+ already_processed_keys = Set.new
400
+ old_param = param.dup
401
+
376
402
  param = param.collect do |key, value|
377
- assoc = associations.detect do |association|
378
- association.association_foreign_key == key
403
+ next if already_processed_keys.include? key
404
+
405
+ model_name = model_id = nil
406
+
407
+ assoc = associations.detect do |poly_assoc|
408
+ if key == poly_assoc.polymorphic_type_attribute
409
+ model_name = value
410
+ already_processed_keys << poly_assoc.association_foreign_key
411
+ elsif key == poly_assoc.association_foreign_key
412
+ model_id = value
413
+ already_processed_keys << poly_assoc.polymorphic_type_attribute
414
+ end
379
415
  end
380
416
 
381
417
  if assoc
382
- if value
383
- [assoc.attribute, { id: [value] }]
384
- else
418
+ if !value
385
419
  [assoc.attribute, [nil]]
420
+ elsif assoc.polymorphic?
421
+ model_id ||= param.detect { |k, *| k == assoc.association_foreign_key }&.last
422
+ model_id ||= target.send(assoc.attribute)&.id
423
+ if model_id.nil?
424
+ raise "Error in #{self.name}._react_param_conversion. \n"\
425
+ "Could not determine the id of #{assoc.attribute} of #{target.inspect}.\n"\
426
+ "It was not provided in the conversion data, "\
427
+ "and it is unknown on the client"
428
+ end
429
+ model_name ||= param.detect { |k, *| k == assoc.polymorphic_type_attribute }&.last
430
+ model_name ||= target.send(assoc.polymorphic_type_attribute)
431
+ unless Object.const_defined?(model_name)
432
+ raise "Error in #{self.name}._react_param_conversion. \n"\
433
+ "Could not determine the type of #{assoc.attribute} of #{target.inspect}.\n"\
434
+ "It was not provided in the conversion data, "\
435
+ "and it is unknown on the client"
436
+ end
437
+
438
+ [assoc.attribute, { id: [model_id], model_name: [model_name] }]
439
+ else
440
+ [assoc.attribute, { id: [value]}]
386
441
  end
387
442
  else
388
443
  [key, [value]]
389
444
  end
390
- end
391
- # TODO: verify wrapping with load_data was added so broadcasting works in 1.0.0.lap28
445
+ end.compact
392
446
  ReactiveRecord::Base.load_data do
393
447
  ReactiveRecord::ServerDataCache.load_from_json(Hash[param], target)
394
448
  end
@@ -24,6 +24,8 @@ module ActiveModel
24
24
  @messages.clear
25
25
  end
26
26
 
27
+ alias non_reactive_clear clear
28
+
27
29
  def add(attribute, message = :invalid, _options = {})
28
30
  @messages[attribute] << message unless @messages[attribute].include? message
29
31
  end
@@ -89,7 +89,7 @@ module ActiveModel
89
89
  # person.errors.values # => [["cannot be nil", "must be specified"]]
90
90
  def values
91
91
  messages.select do |key, value|
92
- !value.empty?
92
+ !value&.empty?
93
93
  end.values
94
94
  end
95
95
 
@@ -105,9 +105,9 @@ module ActiveModel
105
105
  attr_name =
106
106
  attribute.to_s.tr('.', '_').tr('_', ' ').gsub(/_id$/, '').capitalize
107
107
  # attr_name = @base.class.human_attribute_name(attribute, default: attr_name)
108
- # if @base.class.respond_to?(:human_attribute_name)
108
+ if @base.class.respond_to?(:human_attribute_name)
109
109
  attr_name = @base.class.human_attribute_name(attribute, default: attr_name)
110
- # end
110
+ end
111
111
  # I18n.t(:"errors.format",
112
112
  # default: "%{attribute} %{message}",
113
113
  # attribute: attr_name,
@@ -219,8 +219,12 @@ module ActiveModel
219
219
  # person.errors.clear
220
220
  # person.errors.full_messages # => []
221
221
  def clear
222
+ non_reactive_clear.tap { reactive_empty! true }
223
+ end
224
+
225
+ def non_reactive_clear
222
226
  messages.clear
223
- details.clear.tap { reactive_empty! true }
227
+ details.clear
224
228
  end
225
229
 
226
230
  # Merges the errors from <tt>other</tt>.
@@ -1,5 +1,22 @@
1
1
  module ActiveRecord
2
2
  module InstanceMethods
3
+
4
+ def method_missing(missing, *args, &block)
5
+ column = self.class.columns_hash.detect { |name, *| missing =~ /^#{name}/ }
6
+ if column
7
+ name = column[0]
8
+ case missing
9
+ when /\!\z/ then @backing_record.get_attr_value(name, true)
10
+ when /\=\z/ then @backing_record.set_attr_value(name, *args)
11
+ when /\_changed\?\z/ then @backing_record.changed?(name)
12
+ when /\?/ then @backing_record.get_attr_value(name, nil).present?
13
+ else @backing_record.get_attr_value(name, nil)
14
+ end
15
+ else
16
+ super
17
+ end
18
+ end
19
+
3
20
  def inspect
4
21
  "<#{model_name}:#{ReactiveRecord::Operations::Base::FORMAT % to_key} "\
5
22
  "(#{ReactiveRecord::Operations::Base::FORMAT % object_id}) "\
@@ -81,7 +98,11 @@ module ActiveRecord
81
98
  end
82
99
 
83
100
  def ==(ar_instance)
84
- @backing_record == ar_instance.instance_eval { @backing_record }
101
+ return true if @backing_record == ar_instance.instance_eval { @backing_record }
102
+ return false unless ar_instance.is_a?(ActiveRecord::Base)
103
+ return false if ar_instance.new_record?
104
+ return false unless self.class.base_class == ar_instance.class.base_class
105
+ id == ar_instance.id
85
106
  end
86
107
 
87
108
  def [](attr)