hyper-model 1.0.alpha1.3 → 1.0.alpha1.8
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 +4 -4
- data/.gitignore +4 -1
- data/.rspec +0 -1
- data/Gemfile +6 -5
- data/Rakefile +18 -6
- data/hyper-model.gemspec +12 -20
- data/lib/active_record_base.rb +95 -28
- data/lib/enumerable/pluck.rb +3 -2
- data/lib/hyper-model.rb +4 -1
- 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 +125 -35
- data/lib/reactive_record/active_record/base.rb +32 -0
- data/lib/reactive_record/active_record/class_methods.rb +125 -53
- 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 +73 -5
- data/lib/reactive_record/active_record/public_columns_hash.rb +25 -26
- data/lib/reactive_record/active_record/reactive_record/backing_record_inspector.rb +22 -5
- data/lib/reactive_record/active_record/reactive_record/base.rb +50 -24
- data/lib/reactive_record/active_record/reactive_record/collection.rb +196 -63
- data/lib/reactive_record/active_record/reactive_record/dummy_polymorph.rb +22 -0
- data/lib/reactive_record/active_record/reactive_record/dummy_value.rb +27 -15
- data/lib/reactive_record/active_record/reactive_record/getters.rb +33 -10
- data/lib/reactive_record/active_record/reactive_record/isomorphic_base.rb +71 -44
- data/lib/reactive_record/active_record/reactive_record/lookup_tables.rb +5 -5
- data/lib/reactive_record/active_record/reactive_record/operations.rb +7 -1
- data/lib/reactive_record/active_record/reactive_record/scoped_collection.rb +3 -6
- data/lib/reactive_record/active_record/reactive_record/setters.rb +105 -68
- data/lib/reactive_record/active_record/reactive_record/while_loading.rb +22 -1
- data/lib/reactive_record/broadcast.rb +59 -25
- data/lib/reactive_record/interval.rb +3 -3
- data/lib/reactive_record/permissions.rb +1 -1
- data/lib/reactive_record/scope_description.rb +3 -2
- data/lib/reactive_record/server_data_cache.rb +78 -48
- data/polymorph-notes.md +143 -0
- metadata +52 -157
- data/Gemfile.lock +0 -440
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: caef9d023cb5294a2d4f95feb2bfea4234adc0b780881da6c011d1d765504203
|
4
|
+
data.tar.gz: c80e5ed0e014d3013250e0b31cd897e086feb89d916c9967e9a08a9ba4ecbce7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 610334f5accd3678c94efa95b50614efd4d12685c0b288a17cae7ffef4688635657a23b97605e747cc3606a36632a8803d8f159f027cf9653fd514647ad657fc
|
7
|
+
data.tar.gz: 7a6c022f86b1f9718bb162822f8bece902c649dfcdd8891936afed03e57ab7da70374c9fbc85e3544b0d1cc8f3bdfcd3a266cebf98f26dca80c9a34784d7dd74
|
data/.gitignore
CHANGED
@@ -11,7 +11,6 @@ spec/test_app/tmp/
|
|
11
11
|
spec/test_app/db/test.sqlite3
|
12
12
|
spec/test_app/log/test.log
|
13
13
|
spec/test_app/log/development.log
|
14
|
-
spec/test_app/Gemfile.lock
|
15
14
|
/synchromesh-simple-poller-store
|
16
15
|
/synchromesh-pusher-channel-store
|
17
16
|
/examples/action-cable/rails_cache_dir/
|
@@ -34,3 +33,7 @@ public/assets/*
|
|
34
33
|
# ingore Idea
|
35
34
|
.idea
|
36
35
|
.vscode
|
36
|
+
|
37
|
+
# ignore Gemfile.locks https://yehudakatz.com/2010/12/16/clarifying-the-roles-of-the-gemspec-and-gemfile/
|
38
|
+
/spec/test_app/Gemfile.lock
|
39
|
+
/Gemfile.lock
|
data/.rspec
CHANGED
data/Gemfile
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
|
-
#gem "opal-jquery", git: "https://github.com/opal/opal-jquery.git", branch: "master"
|
3
|
-
# hyper-model is still using an ancient inlined version of hyper-spec
|
4
|
-
#gem 'hyper-spec', path: '../hyper-spec'
|
5
|
-
gem 'hyperstack-config', path: '../hyperstack-config'
|
6
2
|
gem 'hyper-state', path: '../hyper-state'
|
7
3
|
gem 'hyper-component', path: '../hyper-component'
|
8
4
|
gem 'hyper-operation', path: '../hyper-operation'
|
9
|
-
|
5
|
+
gem 'hyper-spec', path: '../hyper-spec'
|
6
|
+
gem 'hyper-trace', path: '../hyper-trace'
|
7
|
+
gem 'hyperstack-config', path: '../hyperstack-config'
|
8
|
+
unless ENV['OPAL_VERSION']&.match("0.11")
|
9
|
+
gem 'opal-browser', git: 'https://github.com/opal/opal-browser'
|
10
|
+
end
|
10
11
|
gemspec
|
data/Rakefile
CHANGED
@@ -1,29 +1,41 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
2
|
require "rspec/core/rake_task"
|
3
3
|
|
4
|
+
def run_batches(batches)
|
5
|
+
failed = false
|
6
|
+
batches.each do |batch|
|
7
|
+
begin
|
8
|
+
Rake::Task["spec:batch#{batch}"].invoke
|
9
|
+
rescue SystemExit
|
10
|
+
failed = true
|
11
|
+
end
|
12
|
+
end
|
13
|
+
exit 1 if failed
|
14
|
+
end
|
15
|
+
|
16
|
+
|
4
17
|
task :part1 do
|
5
|
-
(1..2)
|
18
|
+
run_batches(1..2)
|
6
19
|
end
|
7
20
|
|
8
21
|
task :part2 do
|
9
|
-
(3..4)
|
22
|
+
run_batches(3..4)
|
10
23
|
end
|
11
24
|
|
12
25
|
task :part3 do
|
13
|
-
(5..7)
|
26
|
+
run_batches(5..7)
|
14
27
|
end
|
15
28
|
|
16
29
|
task :spec do
|
17
|
-
(1..7)
|
30
|
+
run_batches(1..7)
|
18
31
|
end
|
19
32
|
|
20
33
|
namespace :spec do
|
21
34
|
task :prepare do
|
22
|
-
sh %(cd spec/test_app; bundle exec rails db:setup)
|
35
|
+
sh %(cd spec/test_app; rm db/schema.rb; RAILS_ENV=test bundle exec rails db:setup; RAILS_ENV=test bundle exec rails db:migrate)
|
23
36
|
end
|
24
37
|
(1..7).each do |batch|
|
25
38
|
RSpec::Core::RakeTask.new(:"batch#{batch}") do |t|
|
26
|
-
t.fail_on_error = false unless batch == 7
|
27
39
|
t.pattern = "spec/batch#{batch}/**/*_spec.rb"
|
28
40
|
end
|
29
41
|
end
|
data/hyper-model.gemspec
CHANGED
@@ -27,31 +27,24 @@ Gem::Specification.new do |spec|
|
|
27
27
|
|
28
28
|
spec.add_dependency 'activemodel'
|
29
29
|
spec.add_dependency 'activerecord', '>= 4.0.0'
|
30
|
-
spec.add_dependency 'hyper-component', HyperModel::VERSION
|
31
30
|
spec.add_dependency 'hyper-operation', HyperModel::VERSION
|
32
|
-
|
33
|
-
spec.add_development_dependency '
|
34
|
-
spec.add_development_dependency 'chromedriver-helper', '1.2.0'
|
35
|
-
spec.add_development_dependency 'libv8'
|
36
|
-
spec.add_development_dependency 'mini_racer', '~> 0.2.4'
|
37
|
-
spec.add_development_dependency 'selenium-webdriver'
|
31
|
+
|
32
|
+
spec.add_development_dependency 'bundler'
|
38
33
|
spec.add_development_dependency 'database_cleaner'
|
39
34
|
spec.add_development_dependency 'factory_bot_rails'
|
40
|
-
|
41
|
-
spec.add_development_dependency '
|
42
|
-
spec.add_development_dependency '
|
43
|
-
spec.add_development_dependency '
|
44
|
-
spec.add_development_dependency 'opal-rails', '
|
45
|
-
spec.add_development_dependency 'parser'
|
46
|
-
spec.add_development_dependency 'pry'
|
35
|
+
spec.add_development_dependency 'hyper-spec', HyperModel::VERSION
|
36
|
+
spec.add_development_dependency 'hyper-trace', HyperModel::VERSION
|
37
|
+
spec.add_development_dependency 'mini_racer'
|
38
|
+
spec.add_development_dependency 'pg'
|
39
|
+
spec.add_development_dependency 'opal-rails', '>= 0.9.4', '< 2.0'
|
47
40
|
spec.add_development_dependency 'pry-rescue'
|
41
|
+
spec.add_development_dependency 'pry-stack_explorer'
|
48
42
|
spec.add_development_dependency 'puma'
|
49
43
|
spec.add_development_dependency 'pusher'
|
50
44
|
spec.add_development_dependency 'pusher-fake'
|
51
|
-
spec.add_development_dependency 'rails', '>=
|
45
|
+
spec.add_development_dependency 'rails', ENV['RAILS_VERSION'] || '>= 5.0.0', '< 7.0'
|
52
46
|
spec.add_development_dependency 'rake'
|
53
47
|
spec.add_development_dependency 'react-rails', '>= 2.4.0', '< 2.5.0'
|
54
|
-
spec.add_development_dependency 'reactrb-rails-generator'
|
55
48
|
spec.add_development_dependency 'rspec-collection_matchers'
|
56
49
|
spec.add_development_dependency 'rspec-expectations'
|
57
50
|
spec.add_development_dependency 'rspec-its'
|
@@ -59,11 +52,10 @@ Gem::Specification.new do |spec|
|
|
59
52
|
spec.add_development_dependency 'rspec-rails'
|
60
53
|
spec.add_development_dependency 'rspec-steps', '~> 2.1.1'
|
61
54
|
spec.add_development_dependency 'rspec-wait'
|
62
|
-
spec.add_development_dependency 'rubocop'
|
55
|
+
spec.add_development_dependency 'rubocop' #, '~> 0.51.0'
|
63
56
|
spec.add_development_dependency 'shoulda'
|
64
57
|
spec.add_development_dependency 'shoulda-matchers'
|
65
|
-
spec.add_development_dependency 'spring-commands-rspec'
|
66
|
-
spec.add_development_dependency 'sqlite3'
|
58
|
+
spec.add_development_dependency 'spring-commands-rspec', '~> 1.0.4'
|
59
|
+
spec.add_development_dependency 'sqlite3', '~> 1.4.2' # see https://github.com/rails/rails/issues/35153, '~> 1.3.6'
|
67
60
|
spec.add_development_dependency 'timecop', '~> 0.8.1'
|
68
|
-
spec.add_development_dependency 'unparser'
|
69
61
|
end
|
data/lib/active_record_base.rb
CHANGED
@@ -5,17 +5,46 @@ module ActiveRecord
|
|
5
5
|
# processes these arguments, and the will always leave the true server side scoping
|
6
6
|
# proc in the `:server` opts. This method is common to client and server.
|
7
7
|
class Base
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
args[0]
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
8
|
+
class << self
|
9
|
+
def _synchromesh_scope_args_check(args)
|
10
|
+
opts = if args.count == 2 && args[1].is_a?(Hash)
|
11
|
+
args[1].merge(server: args[0])
|
12
|
+
elsif args[0].is_a? Hash
|
13
|
+
args[0]
|
14
|
+
else
|
15
|
+
{ server: args[0] }
|
16
|
+
end
|
17
|
+
return opts if opts[:server].respond_to?(:call) || RUBY_ENGINE == 'opal'
|
18
|
+
raise 'must provide either a proc as the first arg or by the '\
|
19
|
+
'`:server` option to scope and default_scope methods'
|
20
|
+
end
|
21
|
+
|
22
|
+
alias pre_hyperstack_has_and_belongs_to_many has_and_belongs_to_many unless RUBY_ENGINE == 'opal'
|
23
|
+
|
24
|
+
def has_and_belongs_to_many(other, opts = {}, &block)
|
25
|
+
join_table_name = [other.to_s, table_name].sort.join('_')
|
26
|
+
join_model_name = "HyperstackInternalHabtm#{join_table_name.singularize.camelize}"
|
27
|
+
join_model =
|
28
|
+
if Object.const_defined? join_model_name
|
29
|
+
Object.const_get(join_model_name)
|
30
|
+
else
|
31
|
+
Object.const_set(join_model_name, Class.new(ActiveRecord::Base))
|
32
|
+
end
|
33
|
+
|
34
|
+
join_model.class_eval { belongs_to other.to_s.singularize.to_sym }
|
35
|
+
|
36
|
+
has_many join_model_name.underscore.pluralize.to_sym
|
37
|
+
|
38
|
+
if RUBY_ENGINE == 'opal'
|
39
|
+
Object.const_set("HABTM_#{other.to_s.camelize}", join_model)
|
40
|
+
join_model.inheritance_column = nil
|
41
|
+
has_many other, through: join_model_name.underscore.pluralize.to_sym
|
42
|
+
else
|
43
|
+
join_model.table_name = join_table_name
|
44
|
+
join_model.belongs_to other
|
45
|
+
pre_hyperstack_has_and_belongs_to_many(other, opts, &block)
|
46
|
+
end
|
47
|
+
end
|
19
48
|
end
|
20
49
|
end
|
21
50
|
if RUBY_ENGINE != 'opal'
|
@@ -87,7 +116,7 @@ module ActiveRecord
|
|
87
116
|
this.acting_user = acting_user
|
88
117
|
# returns a PsuedoRelationArray which will respond to the
|
89
118
|
# __secure_collection_check method
|
90
|
-
ReactiveRecordPsuedoRelationArray.new([this.instance_exec(*args, &block)])
|
119
|
+
ReactiveRecordPsuedoRelationArray.new([*this.instance_exec(*args, &block)])
|
91
120
|
ensure
|
92
121
|
this.acting_user = old
|
93
122
|
end
|
@@ -255,21 +284,7 @@ module ActiveRecord
|
|
255
284
|
pre_syncromesh_has_many name, *args, opts.except(:regulate), &block
|
256
285
|
end
|
257
286
|
|
258
|
-
|
259
|
-
# No explicit security checks are needed here, as the data returned by these objects
|
260
|
-
# will be further processedand checked before returning. I.e. it is not possible to
|
261
|
-
# simply return `find(1)` but if you try returning `find(1).name` the permission system
|
262
|
-
# will check to see if the name attribute can be legally sent to the current acting user.
|
263
|
-
|
264
|
-
def __secure_remote_access_to_find(_self, _acting_user, *args)
|
265
|
-
find(*args)
|
266
|
-
end
|
267
|
-
|
268
|
-
def __secure_remote_access_to_find_by(_self, _acting_user, *args)
|
269
|
-
find_by(*args)
|
270
|
-
end
|
271
|
-
|
272
|
-
%i[belongs_to has_one].each do |macro|
|
287
|
+
%i[belongs_to has_one composed_of].each do |macro|
|
273
288
|
alias_method :"pre_syncromesh_#{macro}", macro
|
274
289
|
define_method(macro) do |name, *aargs, &block|
|
275
290
|
define_method(:"__secure_remote_access_to_#{name}") do |this, _acting_user, *args|
|
@@ -284,6 +299,12 @@ module ActiveRecord
|
|
284
299
|
Hyperstack::InternalPolicy.raise_operation_access_violation(:scoped_denied, "#{self.class} regulation denies scope access. Called from #{caller_locations(1)}")
|
285
300
|
end
|
286
301
|
|
302
|
+
unless method_defined? :saved_changes # for backwards compatibility to Rails < 5.1.7
|
303
|
+
def saved_changes
|
304
|
+
previous_changes
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
287
308
|
# call do_not_synchronize to block synchronization of a model
|
288
309
|
|
289
310
|
def self.do_not_synchronize
|
@@ -300,17 +321,28 @@ module ActiveRecord
|
|
300
321
|
self.class.do_not_synchronize?
|
301
322
|
end
|
302
323
|
|
324
|
+
before_create :synchromesh_mark_update_time
|
325
|
+
before_update :synchromesh_mark_update_time
|
326
|
+
before_destroy :synchromesh_mark_update_time
|
327
|
+
|
328
|
+
attr_reader :__synchromesh_update_time
|
329
|
+
|
330
|
+
def synchromesh_mark_update_time
|
331
|
+
@__synchromesh_update_time = Time.now.to_f
|
332
|
+
end
|
333
|
+
|
303
334
|
after_commit :synchromesh_after_create, on: [:create]
|
304
335
|
after_commit :synchromesh_after_change, on: [:update]
|
305
336
|
after_commit :synchromesh_after_destroy, on: [:destroy]
|
306
337
|
|
307
338
|
def synchromesh_after_create
|
339
|
+
puts "#{self}.synchromesh_after_create: #{do_not_synchronize?} channels: #{Hyperstack::Connection.active}" if Hyperstack::Connection.show_diagnostics
|
308
340
|
return if do_not_synchronize?
|
309
341
|
ReactiveRecord::Broadcast.after_commit :create, self
|
310
342
|
end
|
311
343
|
|
312
344
|
def synchromesh_after_change
|
313
|
-
return if do_not_synchronize? ||
|
345
|
+
return if do_not_synchronize? || saved_changes.empty?
|
314
346
|
ReactiveRecord::Broadcast.after_commit :change, self
|
315
347
|
end
|
316
348
|
|
@@ -329,6 +361,41 @@ module ActiveRecord
|
|
329
361
|
%i[limit offset].each do |scope|
|
330
362
|
regulate_scope(scope) {}
|
331
363
|
end
|
364
|
+
|
365
|
+
finder_method :__hyperstack_internal_scoped_last do
|
366
|
+
last
|
367
|
+
end
|
368
|
+
|
369
|
+
scope :__hyperstack_internal_scoped_last_n, ->(n) { last(n) }
|
370
|
+
|
371
|
+
# implements find_by inside of scopes. For security reasons we return nil
|
372
|
+
# if we cannot view at least the id of found record. Otherwise a hacker
|
373
|
+
# could tell if a record exists depending on whether an access violation
|
374
|
+
# (i.e. it exists) or nil (it doesn't exist is returned.) Note that
|
375
|
+
# view of id is permitted as long as any attribute of the record is
|
376
|
+
# accessible.
|
377
|
+
finder_method :__hyperstack_internal_scoped_find_by do |attrs|
|
378
|
+
begin
|
379
|
+
found = find_by(attrs)
|
380
|
+
found && found.check_permission_with_acting_user(acting_user, :view_permitted?, :id)
|
381
|
+
rescue Hyperstack::AccessViolation => e
|
382
|
+
message = []
|
383
|
+
message << Pastel.new.red("\n\nHYPERSTACK Access violation during find_by operation.")
|
384
|
+
message << Pastel.new.red("Access to the found record's id is not permitted. nil will be returned")
|
385
|
+
message << " #{self.name}.find_by("
|
386
|
+
message << attrs.collect do |attr, value|
|
387
|
+
" #{attr}: '#{value.inspect.truncate(120, separator: '...')}'"
|
388
|
+
end.join(",\n")
|
389
|
+
message << " )"
|
390
|
+
message << "\n#{e.details}\n"
|
391
|
+
Hyperstack.on_error('find_by', self, attrs, message.join("\n"))
|
392
|
+
nil
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
scope :__hyperstack_internal_where_hash_scope, ->(*args) { where(*args) }
|
397
|
+
|
398
|
+
scope :__hyperstack_internal_where_sql_scope, ->(*args) { where(*args) }
|
332
399
|
end
|
333
400
|
end
|
334
401
|
|
data/lib/enumerable/pluck.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# Add pluck to enumerable... its already done for us in rails 5+
|
2
2
|
module Enumerable
|
3
|
-
def pluck(
|
4
|
-
map { |element| element[key] }
|
3
|
+
def pluck(*keys)
|
4
|
+
map { |element| keys.map { |key| element[key] } }
|
5
|
+
.flatten(keys.count > 1 ? 0 : 1)
|
5
6
|
end
|
6
7
|
end unless Enumerable.method_defined? :pluck
|
data/lib/hyper-model.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'set'
|
2
2
|
require 'hyperstack-config'
|
3
|
-
|
3
|
+
|
4
|
+
Hyperstack.import 'hyper-model'
|
5
|
+
|
4
6
|
if RUBY_ENGINE == 'opal'
|
5
7
|
require 'hyper-operation'
|
6
8
|
require 'active_support'
|
@@ -20,6 +22,7 @@ if RUBY_ENGINE == 'opal'
|
|
20
22
|
require "reactive_record/active_record/reactive_record/isomorphic_base"
|
21
23
|
require 'reactive_record/active_record/reactive_record/dummy_value'
|
22
24
|
require 'reactive_record/active_record/reactive_record/column_types'
|
25
|
+
require 'reactive_record/active_record/reactive_record/dummy_polymorph'
|
23
26
|
require "reactive_record/active_record/aggregations"
|
24
27
|
require "reactive_record/active_record/associations"
|
25
28
|
require "reactive_record/active_record/reactive_record/backing_record_inspector"
|
data/lib/hyper_model/version.rb
CHANGED
@@ -3,7 +3,7 @@ module ActiveRecord
|
|
3
3
|
class Base
|
4
4
|
|
5
5
|
def self.reflect_on_all_associations
|
6
|
-
|
6
|
+
@associations ||= superclass.instance_eval { @associations&.dup || [] }
|
7
7
|
end
|
8
8
|
|
9
9
|
def self.reflect_on_association(attr)
|
@@ -11,7 +11,7 @@ module ActiveRecord
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def self.reflect_on_association_by_foreign_key(key)
|
14
|
-
reflection_finder { |assoc| assoc.association_foreign_key == key }
|
14
|
+
reflection_finder { |assoc| assoc.association_foreign_key == key && assoc.macro != :has_many }
|
15
15
|
end
|
16
16
|
|
17
17
|
def self.reflection_finder(&block)
|
@@ -32,22 +32,55 @@ module ActiveRecord
|
|
32
32
|
module Associations
|
33
33
|
|
34
34
|
class AssociationReflection
|
35
|
-
|
35
|
+
attr_reader :klass_name
|
36
36
|
attr_reader :association_foreign_key
|
37
37
|
attr_reader :attribute
|
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
|
-
|
48
|
-
|
49
|
-
|
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
|
80
|
+
end
|
81
|
+
|
82
|
+
def habtm?
|
83
|
+
through_association&.klass_name =~ /^HyperstackInternalHabtm/
|
51
84
|
end
|
52
85
|
|
53
86
|
def through_association
|
@@ -62,63 +95,120 @@ module ActiveRecord
|
|
62
95
|
|
63
96
|
alias through_association? through_association
|
64
97
|
|
65
|
-
|
98
|
+
# class Membership < ActiveRecord::Base
|
99
|
+
# belongs_to :uzer
|
100
|
+
# belongs_to :memerable, polymorphic: true
|
101
|
+
# end
|
102
|
+
#
|
103
|
+
# class Project < ActiveRecord::Base
|
104
|
+
# has_many :memberships, as: :memerable, dependent: :destroy
|
105
|
+
# has_many :uzers, through: :memberships
|
106
|
+
# end
|
107
|
+
#
|
108
|
+
# class Group < ActiveRecord::Base
|
109
|
+
# has_many :memberships, as: :memerable, dependent: :destroy
|
110
|
+
# has_many :uzers, through: :memberships
|
111
|
+
# end
|
112
|
+
#
|
113
|
+
# class Uzer < ActiveRecord::Base
|
114
|
+
# has_many :memberships
|
115
|
+
# has_many :groups, through: :memberships, source: :memerable, source_type: 'Group'
|
116
|
+
# has_many :projects, through: :memberships, source: :memerable, source_type: 'Project'
|
117
|
+
# end
|
118
|
+
|
119
|
+
# so find the belongs_to relationship whose attribute == ta.source
|
120
|
+
# now find the inverse of that relationship using source_value as the model
|
121
|
+
# now find any has many through relationships that use that relationship as there source.
|
122
|
+
# each of those attributes in the source_value have to be updated.
|
123
|
+
|
124
|
+
# self is the through association
|
125
|
+
|
126
|
+
|
127
|
+
def through_associations(model)
|
128
|
+
# given self is a belongs_to association currently pointing to model
|
66
129
|
# find all associations that use the inverse association as the through association
|
67
130
|
# that is find all associations that are using this association in a through relationship
|
68
|
-
|
69
|
-
|
131
|
+
the_klass = klass(model)
|
132
|
+
@through_associations[the_klass] ||= the_klass.reflect_on_all_associations.select do |assoc|
|
133
|
+
assoc.through_association&.inverse == self
|
70
134
|
end
|
71
135
|
end
|
72
136
|
|
73
|
-
def
|
74
|
-
#
|
75
|
-
#
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
assoc.source == attribute
|
137
|
+
def source_belongs_to_association # private
|
138
|
+
# given self is a has_many_through association return the corresponding belongs_to association
|
139
|
+
# for the source
|
140
|
+
@source_belongs_to_association ||=
|
141
|
+
through_association.inverse.owner_class.reflect_on_all_associations.detect do |sibling|
|
142
|
+
sibling.attribute == source
|
80
143
|
end
|
81
|
-
end.flatten
|
82
144
|
end
|
83
145
|
|
84
|
-
def
|
85
|
-
|
86
|
-
|
146
|
+
def source_associations(model)
|
147
|
+
# given self is a has_many_through association find the source_association for the given model
|
148
|
+
source_belongs_to_association.through_associations(model)
|
87
149
|
end
|
88
150
|
|
89
|
-
|
90
|
-
|
151
|
+
alias :polymorphic? polymorphic_type_attribute
|
152
|
+
|
153
|
+
def inverse(model = nil)
|
154
|
+
return @inverse if @inverse
|
155
|
+
ta = through_association
|
156
|
+
found = ta ? ta.inverse : find_inverse(model)
|
157
|
+
@inverse = found unless polymorphic?
|
158
|
+
found
|
159
|
+
end
|
160
|
+
|
161
|
+
def inverse_of(model = nil)
|
162
|
+
inverse(model).attribute
|
91
163
|
end
|
92
164
|
|
93
|
-
def find_inverse
|
94
|
-
|
165
|
+
def find_inverse(model) # private
|
166
|
+
the_klass = klass(model)
|
167
|
+
the_klass.reflect_on_all_associations.each do |association|
|
168
|
+
next if association == self
|
95
169
|
next if association.association_foreign_key != @association_foreign_key
|
96
|
-
next if association.klass != @owner_class
|
97
170
|
next if association.attribute == attribute
|
98
|
-
return association if klass ==
|
171
|
+
return association if association.polymorphic? || association.klass == owner_class
|
99
172
|
end
|
173
|
+
raise "could not find inverse of polymorphic belongs_to: #{model.inspect} #{self.inspect}" if options[:polymorphic]
|
100
174
|
# instead of raising an error go ahead and create the inverse relationship if it does not exist.
|
101
175
|
# https://github.com/hyperstack-org/hyperstack/issues/89
|
102
176
|
if macro == :belongs_to
|
103
|
-
Hyperstack::Component::IsomorphicHelpers.log "**** warning dynamically adding relationship: #{
|
104
|
-
|
177
|
+
Hyperstack::Component::IsomorphicHelpers.log "**** warning dynamically adding relationship: #{the_klass}.has_many :#{@owner_class.name.underscore.pluralize}, foreign_key: #{@association_foreign_key}", :warning
|
178
|
+
the_klass.has_many @owner_class.name.underscore.pluralize, foreign_key: @association_foreign_key
|
179
|
+
elsif options[:as]
|
180
|
+
Hyperstack::Component::IsomorphicHelpers.log "**** warning dynamically adding relationship: #{the_klass}.belongs_to :#{options[:as]}, polymorphic: true", :warning
|
181
|
+
the_klass.belongs_to options[:as], polymorphic: true
|
105
182
|
else
|
106
|
-
Hyperstack::Component::IsomorphicHelpers.log "**** warning dynamically adding relationship: #{
|
107
|
-
|
183
|
+
Hyperstack::Component::IsomorphicHelpers.log "**** warning dynamically adding relationship: #{the_klass}.belongs_to :#{@owner_class.name.underscore}, foreign_key: #{@association_foreign_key}", :warning
|
184
|
+
the_klass.belongs_to @owner_class.name.underscore, foreign_key: @association_foreign_key
|
108
185
|
end
|
109
186
|
end
|
110
187
|
|
111
|
-
def klass
|
112
|
-
@klass ||= Object.const_get(@klass_name)
|
188
|
+
def klass(model = nil)
|
189
|
+
@klass ||= Object.const_get(@klass_name) if @klass_name
|
190
|
+
if @klass && model && !(model.class <= @klass || @klass <= model.class)
|
191
|
+
# TODO: added || @klass <= model.class can both cases really happen I guess so
|
192
|
+
raise "internal error: provided model #{model} is not subclass of #{@klass}"
|
193
|
+
end
|
194
|
+
raise 'no model supplied for polymorphic relationship' unless @klass || model
|
195
|
+
@klass || model.class
|
113
196
|
end
|
114
197
|
|
115
198
|
def collection?
|
116
199
|
[:has_many].include? @macro
|
117
200
|
end
|
118
201
|
|
119
|
-
|
202
|
+
def remove_member(member, owner)
|
203
|
+
collection = owner.attributes[attribute]
|
204
|
+
return if collection.nil?
|
205
|
+
collection.delete(member)
|
206
|
+
end
|
120
207
|
|
208
|
+
def add_member(member, owner)
|
209
|
+
owner.attributes[attribute] ||= ReactiveRecord::Collection.new(owner_class, owner, self)
|
210
|
+
owner.attributes[attribute]._internal_push member
|
211
|
+
end
|
212
|
+
end
|
121
213
|
end
|
122
|
-
|
123
|
-
|
124
214
|
end
|